Куда пошли стрелы?

Изменить/Примечание: Мне действительно нужен ответ на этот вопрос, и я хотел бы остановиться на "биржевом" API Google Android. Я создал +100 баунти на этом, но если я получу прямое решение этого, используя API-интерфейс запаса в ближайшие несколько дней, я добавлю еще +100, сделав его на 200 пунктов.

Я экспериментирую с элементом управления Android CalendarView. Я сделал небольшое приложение с кнопкой и CalendarView в NestedScrollView. Я сделал кнопку, и ее поля действительно большие, поэтому я мог проверить прокрутку. На Samsung Galaxy S5 работает под управлением Android 6.01, он отлично работает. Но на Samsung S Duo (который является моей целевой целью), работающей 4.2.2, нет способа продвинуть месяц (не заметить стрелку рядом с месяцем)

Вот скриншот от Samsung S5 под управлением Android 6.01

s5 screenshot

и здесь один из Samsung Duo работает 4.2.2

S Duo screenshot

Содержимое 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 в представлении дизайна есть показаны стрелки. Я не знаю, как отладить это.

Ответ 1

Хорошее и глубокое объяснение Iulian Popescu, я просто пытаюсь его упростить. Прежде всего, я хочу прояснить некоторые вещи.

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) мы видим, что класс DayPickerView (android.widget.DayPickerView) используется в качестве контейнера для отображения календаря, который мы видим в теме материала. В DayPickerView class ViewPager используется для отображения одного месяца на одной странице. Также два ImageButton для следующего и предыдущего. Поскольку есть две кнопки для изменения месяца, это не создаст проблем и прекрасно работает в теме MATERIAL.
    private final ViewPager mViewPager;
    private final ImageButton mPrevButton;
    private final ImageButton mNextButton;

  1. Как мы видим в классе CalendarViewLegacyDelegate (android.widget.CalendarViewLegacyDelegate), ListView используется для отображения списка недель (вертикальная прокрутка) в CalenderView. Нет никакого ImageButton для предыдущего и следующего, поскольку он будет прокручиваться по вертикали.
/**
     * The adapter for the weeks list.
     */
    private WeeksAdapter mAdapter;

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

В соответствии с этой ссылкой на веб-сайте разработчика Android, мы никогда не должны использовать ScrollView с ListView, потому что ListView заботится о своей собственной вертикальной прокрутке. Из-за этого в случае HOLO тема CalenderView будет действовать неожиданно при использовании его внутри ScrollView.

Решение: -

Вы можете использовать приведенный пользовательский класс прокрутки вместо класса NestedScrollView. Это сделает вашу CalenderView вертикальную прокрутку гладкой в ​​случае темы HOLO.

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;
    }
}

Надеюсь, это уберет ваши сомнения.

Ответ 2

С быстрым просмотром кода CalendarView похоже, что внешний вид календаря выведен из системы, и я не смог найти способ его изменить. Ниже вы можете узнать, как выбирается вид календаря в зависимости от 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

После некоторого времени, потраченного на проблему, у меня есть несколько пояснений:

  • Предварительный просмотр показывает вид со стрелками, потому что вы не установили правильную версию API. Если вы посмотрите на приведенные ниже изображения, вы увидите, что в версиях до Lollipop кнопки отсутствуют, а после этого все хорошо и хорошо. Чтобы изменить версию API, просто нажмите кнопку, выделенную красным цветом, и выберите нужный уровень API, чтобы увидеть точный предварительный просмотр вашего xml на разных платформах.

    версия KitKat версия Android N

  • Почему стрелки отсутствуют в предыдущих версиях Material Design? Здесь я не смог найти ответ, данный кем-то из Google, но я считаю, что это как-то связано с разрешением экрана (на более старых устройствах у вас не так много места, чтобы показать CalendarView как в новее Android, и именно поэтому они выбрали ListView, так как он может отображать только несколько строк и по-прежнему полностью функционален, тогда как отображение с ViewPager будет обрезать некоторые из последних строк).

    Как я уже сказал в исходном посте, стиль CalendarView выбирается на основе доступности Material Design (Android 5.0+).

    Устаревшая версия (найти ее ниже) имеет ListView, а кнопки next/previous отсутствуют. Поскольку календарь отображается с помощью ListView, вы не можете прокручивать следующий месяц, поскольку родительский вид CalendarView - это Scroll. Чтобы исправить это, вы можете найти некоторые объяснения и решения здесь.

    <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" />
    

    Конструкция материала заменяет ListView на ViewPager и добавляет предыдущие и следующие кнопки.

    <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" />
    

    На данный момент я не смог найти способ стиля CalendarView, так как атрибуты stylable удаляются из общедоступных API.

    Класс android.R.styleable и его поля были удалены из общедоступного API, чтобы обеспечить лучшую совместимость для приложений.

    Сказав, вы можете сохранить стиль старого стиля для старых версий Android, так как вы не можете его изменить, но имейте в виду, что ListView не должен находиться в Scroll или попытаться найти репозиторий что является стабильным и лучше соответствует вашим потребностям.

Изменить II:

CalendarView не разбит, он отлично работает на всех версиях Android, даже если он может создать впечатление, что его поведение нарушено из-за реализации с помощью ListView. Теперь "позвольте немного разобраться в проблеме, чтобы каждый мог понять, что происходит:

  • Проблема №. 1: использование ListView в ScrollView
    Вообще говоря, это плохая практика использования этих элементов вложенных, потому что они не смогут нормально обрабатывать действие прокрутки.

  • Проблема №. 2: использование NestedScrollView не устраняет проблему Начиная с Android 5.0 Google добавила эту библиотеку поддержки для поддержки вложенной прокрутки, но ListView не сможет обрабатывать свиток, если она не реализует NestedScrollingChild. Чтобы сделать этот подкласс классом ListView и реализовать методы интерфейса, и из каждого метода вызовите соответствующий метод из NestedScrollingChildHelper. Следуя этим шагам, вы можете использовать ListView в NestedScrollView, но не в CalendarView (см. Проблему № 3)

  • Проблема №. 3: вы не можете изменить список из CalendarView
    Реальная проблема заключается в том, что CalendarView не имеет методов, которые позволили бы вам изменить список по умолчанию с помощью специального списка, написанного вами. Предположим, что у вас будет такой метод, вы можете сделать работу CalendarView, заменив список по умолчанию своим пользовательским, и прокрутка будет работать отлично, но, к сожалению, вы не можете.

Чтобы решить проблему, у вас есть 2 варианта:
 - перепроектируйте представление таким образом, чтобы CalendarView не был в классе, который содержит Scroll в его имени
 - используйте ответ Pravin Divraniya, который сделает работу в календаре отлично, но имейте в виду, что вложенные свитки плохие (особенно на старых устройствах Android, где экран небольшой)