user316117 user316117 - 3 months ago 18
Android Question

Where did the arrows go?

Edit/Note: I really need an answer to this, and I'd like to stay with the "stock" Google Android API. I've created a +100 bounty on this but if I get a straightforward solution to this using the stock API in the next few days I'll add another +100, making it worth 200 points.

I'm experimenting with the Android CalendarView control. I made a little app with a button and a CalendarView in a NestedScrollView. I made the button and its margins really big so I could verify scrolling worked. On a Samsung Galaxy S5 running Android 6.01 it works fine. But on a Samsung S Duo (which is my intended target) running 4.2.2 there's no way to advance the month (notice no arrow next to the month)

Here's a screenshot from a Samsung S5 running Android 6.01

s5 screenshot

and here's one from a Samsung s Duo running 4.2.2

S Duo screenshot

The content_main.xml looks like this . . .

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<!-- This linear layout is because the scrollview can have only 1 direct child -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/recordEnd"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:layout_marginBottom="100dp"
android:gravity="center"
android:text="Record End"/>

<CalendarView
android:id="@+id/thecalendar"
android:layout_width="240dp"
android:layout_height="300dp"
android:minDate="01/01/2016"
android:maxDate="11/30/2016"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>


...In Android Studio the design view does show the arrows. I have no idea how to debug this.

Answer

A nice and deep explanation by Iulian Popescu, I am just trying to simplify it. First of all I want to clarify few things.

  1. According to this official Android developer link, The exact appearance and interaction model of CalenderView widget may vary between OS versions and themes (e.g. Holo versus Material).
  2. As Iulian Popescu pasted the code from android.widget.CalendarView class of Android, You can see that CalendarViewLegacyDelegate class is responsible for rendering CalenderView in case of HOLO theme and CalendarViewMaterialDelegate class is responsible for rendering CalenderView in case of MATERIAL theme. I am again posting that code for just reference purpose.
public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes);
        final int mode = a.getInt(R.styleable.CalendarView_calendarViewMode, MODE_HOLO);
        a.recycle();
        switch (mode) {
            case MODE_HOLO:
                mDelegate = new CalendarViewLegacyDelegate(
                        this, context, attrs, defStyleAttr, defStyleRes);
                break;
            case MODE_MATERIAL:
                mDelegate = new CalendarViewMaterialDelegate(
                        this, context, attrs, defStyleAttr, defStyleRes);
                break;
            default:
                throw new IllegalArgumentException("invalid calendarViewMode attribute");
        }
    }
  1. In CalendarViewMaterialDelegate(android.widget.CalendarViewMaterialDelegate) class we can see that DayPickerView(android.widget.DayPickerView) class is used as a container to render the calender view that we can see in Material theme.In DayPickerView class ViewPager used to display one month in one page. Also two ImageButton for next and previous. As there is two buttons to change the month, it will not create any problem and works just fine in MATERIAL theme.
    private final ViewPager mViewPager;
    private final ImageButton mPrevButton;
    private final ImageButton mNextButton;

  1. As we can see in CalendarViewLegacyDelegate(android.widget.CalendarViewLegacyDelegate) class, ListView is used to display list of weeks(Vertical scrolling) in CalenderView. There is no any ImageButton for previous and next as it will scroll vertically.
/**
     * The adapter for the weeks list.
     */
    private WeeksAdapter mAdapter;

    /**
     * The weeks list.
     */
    private ListView mListView;

As per this link of Android developer website, We should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Because of this in case of HOLO theme CalenderView will behave unexpectedly when using it inside ScrollView.

Solution:-

You can use below given custom scroll view class instead of NestedScrollView class. It will make your CalenderView vertical scrolling smooth in case of HOLO theme.

public class VerticalScrollView extends ScrollView{

    public VerticalScrollView(Context context) {
        super(context);
    }

    public VerticalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticalScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                super.onTouchEvent(ev);
                break;

            case MotionEvent.ACTION_MOVE:
                return false; // redirect MotionEvents to ourself

            case MotionEvent.ACTION_CANCEL:
                super.onTouchEvent(ev);
                break;

            case MotionEvent.ACTION_UP:
                return false;

            default: 
                break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        super.onTouchEvent(ev);
        return true;
    }
}

I hope this will clear your doubts.