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
and here's one from a Samsung s Duo running 4.2.2
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.
Having a quick look in CalendarView
code it looks like the calendar appearance is inferred from the system and I wasn't able to find a way to change it. Below you can find how the calendar's view is selected depending on mode
:
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");
}
Edit:
After some more time spent on the problem I have some better explications:
The preview is showing the view with arrows because you haven't set the proper API version. If you have a look at the below images you'll see that on pre-Lollipop versions the buttons are missing, and after it, everything is nice and good. To change the API version simply click on the button highlighted in red and select your desired API level to see an accurate preview of your xml on different platforms.
Why the arrows are missing from the prior versions of Material Design?
Here, I wasn't able to find an answer given by someone from Google, but I believe that this is somehow related to the screen resolution (on older devices you don't have that much space to show the CalendarView
like in newer Android versions and this is why they chose the ListView
since it can display only a few rows and still being fully functional, whereas displaying with a ViewPager
will crop some of the last lines).
As I said in the original post, the style of the CalendarView
is selected based on the availability of Material Design (Android 5.0+).
The Legacy version (find it below) has a ListView
and the next/previous buttons are missing. Because the calendar is shown using a ListView
you can't scroll to the next month since the parent view of the CalendarView
is a Scroll
. To fix this you can find some explications and solutions here.
<TextView android:id="@+android:id/month_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="10dip"
android:paddingBottom="10dip"
style="@android:style/TextAppearance.Medium" />
<!-- This is the header representing the days of the week -->
<LinearLayout android:id="@+android:id/day_names"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginEnd="2dip"
android:layout_marginStart="2dip"
android:gravity="center" >
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:visibility="gone" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
<TextView android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />
</LinearLayout>
<ImageView android:layout_width="match_parent"
android:layout_height="1dip"
android:scaleType="fitXY"
android:gravity="fill_horizontal"
android:src="?android:attr/dividerHorizontal" />
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"
android:cacheColorHint="@android:color/transparent"
android:fastScrollEnabled="false"
android:overScrollMode="never" />
The Material Design version replaces the ListView
with a ViewPager
and adds the prev and next buttons.
<android.widget.DayPickerViewPager
android:id="@+id/day_picker_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/prev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:src="@drawable/ic_chevron_start"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/date_picker_prev_month_button"
android:visibility="invisible" />
<ImageButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:src="@drawable/ic_chevron_end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/date_picker_next_month_button"
android:visibility="invisible" />
At the moment, I wasn't able to find a way to style the CalendarView
since the stylable
attributes are removed from public APIs.
The android.R.styleable class and its fields were removed from the public API, to better ensure forward-compatibility for applications.
That being said, you can keep the old-look style for older Android versions since you can't change it, but keep in mind that a ListView
should not be in a Scroll
or try to find a repository that is stable and fits better your needs.
Edit II:
The CalendarView
is not broken, it works perfectly on all Android version, even if, it may give the impression that its behaviour is broken because of the implementation using a ListView
. Now' let's dig a little in the problem such that everybody can understand what's happening:
Problem no. 1: using a ListView
in ScrollView
Generally speaking it's a bad practice to use those elements nested because they will not be able to handle the scroll action properly.
Problem no. 2: using a NestedScrollView
doesn't fix the problem
From Android 5.0 Google added that support library to support nested scrolling, but a ListView
will not be able to handle the scroll unless it implements NestedScrollingChild
. To do this subclass the ListView
class and implement the interface methods and from each method call the corresponding method from NestedScrollingChildHelper. Following this steps you can use a ListView
in a NestedScrollView
, but not a CalendarView
(see problem no. 3)
Problem no. 3: you can't change the list from CalendarView
The real problem is that the CalendarView
doesn't have any methods that would allow you to change the default list with a custom list written by you. Supposing that you would have such a method, you could make the CalendarView
work by replacing the default list with you custom one and the scroll would work perfectly, but unfortunately you can't.
To solve the problem you have 2 options:
- redesign you view such that the CalendarView
isn't in a class that contains Scroll
in its name
- use Pravin Divraniya's answer which will make you calendar work perfectly, but keep in mind that nested scrolls are bad (especially on older Android devices where the screen is small)
A nice and deep explanation by Iulian Popescu, I am just trying to simplify it. First of all I want to clarify few things.
CalenderView
widget may vary between OS versions and themes (e.g. Holo versus Material).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"); } }
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;
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With