箭去哪儿了?
Where did the arrows go?
Edit/Note: 我真的需要一个答案,我想留在 "stock"GoogleAndroidAPI。我已经为此创建了一个 +100 的赏金,但如果我在接下来的几天内使用股票 API 得到一个直接的解决方案,我将再增加一个 +100,使其价值 200 点。
我正在试验 Android CalendarView 控件。我在 NestedScrollView 中制作了一个带有按钮和 CalendarView 的小应用程序。我把按钮和它的边距做得很大,这样我就可以验证滚动是否有效。在 Samsung Galaxy S5 运行 Android 6.01 上运行正常。但是在 Samsung S Duo(这是我的预期目标)运行 4.2.2 上无法提前一个月(注意月份旁边没有箭头)
这是三星 S5 的屏幕截图 运行 Android 6.01
这是三星的 Duo 运行 4.2.2
content_main.xml 看起来像这样。 . .
<?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>
...在 Android Studio 中,设计视图 确实 显示箭头。我不知道如何调试它。
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)
Iulian Popescu 的精彩而深刻的解释,我只是想简化它。首先我想澄清几件事。
- 根据 this 官方 Android 开发人员 link,
CalenderView
小部件的确切外观和交互模型可能因 OS 版本和主题而异(例如 Holo 与 Material).
- Iulian Popescu 粘贴了 Android
android.widget.CalendarView
class 的代码,可以看到 CalendarViewLegacyDelegate
class 负责渲染 CalenderView 以防万一HOLO
主题和 CalendarViewMaterialDelegate
class 负责在 MATERIAL
主题的情况下呈现 CalenderView。我再次发布该代码仅供参考。
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中我们可以看到DayPickerView
(android.widget.DayPickerView)class作为容器渲染我们在 Material theme.In DayPickerView
class ViewPager
中可以看到的日历视图,用于在一页中显示一个月。还有两个 ImageButton
用于下一个和上一个。由于有两个更改月份的按钮,因此不会产生任何问题并且在 MATERIAL
主题中工作得很好。
private final ViewPager mViewPager;
private final ImageButton mPrevButton;
private final ImageButton mNextButton;
- 正如我们在
CalendarViewLegacyDelegate
(android.widget.CalendarViewLegacyDelegate) class 中看到的那样,ListView
用于在 CalenderView
中显示周列表(垂直滚动) .上一个和下一个没有任何 ImageButton,因为它会垂直滚动。
/**
* The adapter for the weeks list.
*/
private WeeksAdapter mAdapter;
/**
* The weeks list.
*/
private ListView mListView;
根据 Android 开发者网站的这个 link,我们不应该将 ScrollView
与 ListView
一起使用,因为 ListView
会处理它自己的垂直滚动。因此,在 HOLO
的情况下,主题 CalenderView
在 ScrollView
.
中使用时会出现意外行为
解法:-
您可以使用下面给定的自定义滚动视图 class 而不是 NestedScrollView
class。在 HOLO
主题的情况下,它将使您的 CalenderView
垂直滚动平滑。
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;
}
}
我希望这会消除你的疑虑。
Edit/Note: 我真的需要一个答案,我想留在 "stock"GoogleAndroidAPI。我已经为此创建了一个 +100 的赏金,但如果我在接下来的几天内使用股票 API 得到一个直接的解决方案,我将再增加一个 +100,使其价值 200 点。
我正在试验 Android CalendarView 控件。我在 NestedScrollView 中制作了一个带有按钮和 CalendarView 的小应用程序。我把按钮和它的边距做得很大,这样我就可以验证滚动是否有效。在 Samsung Galaxy S5 运行 Android 6.01 上运行正常。但是在 Samsung S Duo(这是我的预期目标)运行 4.2.2 上无法提前一个月(注意月份旁边没有箭头)
这是三星 S5 的屏幕截图 运行 Android 6.01
这是三星的 Duo 运行 4.2.2
content_main.xml 看起来像这样。 . .
<?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>
...在 Android Studio 中,设计视图 确实 显示箭头。我不知道如何调试它。
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 theListView
since it can display only a few rows and still being fully functional, whereas displaying with aViewPager
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 aListView
you can't scroll to the next month since the parent view of theCalendarView
is aScroll
. 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 aViewPager
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 thestylable
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 aScroll
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
inScrollView
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 aListView
will not be able to handle the scroll unless it implementsNestedScrollingChild
. To do this subclass theListView
class and implement the interface methods and from each method call the corresponding method from NestedScrollingChildHelper. Following this steps you can use aListView
in aNestedScrollView
, but not aCalendarView
(see problem no. 3)Problem no. 3: you can't change the list from
CalendarView
The real problem is that theCalendarView
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 theCalendarView
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)
Iulian Popescu 的精彩而深刻的解释,我只是想简化它。首先我想澄清几件事。
- 根据 this 官方 Android 开发人员 link,
CalenderView
小部件的确切外观和交互模型可能因 OS 版本和主题而异(例如 Holo 与 Material). - Iulian Popescu 粘贴了 Android
android.widget.CalendarView
class 的代码,可以看到CalendarViewLegacyDelegate
class 负责渲染 CalenderView 以防万一HOLO
主题和CalendarViewMaterialDelegate
class 负责在MATERIAL
主题的情况下呈现 CalenderView。我再次发布该代码仅供参考。
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中我们可以看到DayPickerView
(android.widget.DayPickerView)class作为容器渲染我们在 Material theme.InDayPickerView
classViewPager
中可以看到的日历视图,用于在一页中显示一个月。还有两个ImageButton
用于下一个和上一个。由于有两个更改月份的按钮,因此不会产生任何问题并且在MATERIAL
主题中工作得很好。
private final ViewPager mViewPager; private final ImageButton mPrevButton; private final ImageButton mNextButton;
- 正如我们在
CalendarViewLegacyDelegate
(android.widget.CalendarViewLegacyDelegate) class 中看到的那样,ListView
用于在CalenderView
中显示周列表(垂直滚动) .上一个和下一个没有任何 ImageButton,因为它会垂直滚动。
/** * The adapter for the weeks list. */ private WeeksAdapter mAdapter; /** * The weeks list. */ private ListView mListView;
根据 Android 开发者网站的这个 link,我们不应该将 ScrollView
与 ListView
一起使用,因为 ListView
会处理它自己的垂直滚动。因此,在 HOLO
的情况下,主题 CalenderView
在 ScrollView
.
解法:-
您可以使用下面给定的自定义滚动视图 class 而不是 NestedScrollView
class。在 HOLO
主题的情况下,它将使您的 CalenderView
垂直滚动平滑。
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;
}
}
我希望这会消除你的疑虑。