箭去哪儿了?

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:

  1. 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.

  2. 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:

  1. 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.

  2. 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)

  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 的精彩而深刻的解释,我只是想简化它。首先我想澄清几件事。

  1. 根据 this 官方 Android 开发人员 link,CalenderView 小部件的确切外观和交互模型可能因 OS 版本和主题而异(例如 Holo 与 Material).
  2. 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");
        }
    }
  1. 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;

  1. 正如我们在 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,我们不应该将 ScrollViewListView 一起使用,因为 ListView 会处理它自己的垂直滚动。因此,在 HOLO 的情况下,主题 CalenderViewScrollView.

中使用时会出现意外行为

解法:-

您可以使用下面给定的自定义滚动视图 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;
    }
}

我希望这会消除你的疑虑。