Утилита DatePickerDialog Holo не удалась на Android 7 Nougat

Из нашего потребительского спроса мы хотим сохранить стиль HOLO в DatePickerDialog для всех версий ОС Android, например: DatePicker на Android 7 -

Но, похоже, не работает корректно на Android 7:

DatePicker на Android 7

Из моей реализации:

new DatePickerDialog(getContext(), AlertDialog.THEME_HOLO_LIGHT,
                        mCalendarPickerListener,
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH),
                        calendar.get(Calendar.DAY_OF_MONTH));

Он отлично работает для предыдущего андроида 7. У кого-то такая же проблема?

Отредактировано: решение установлено, что исправлено в API 25 https://code.google.com/u/106133255289400340786/

Ответ 1

Я использую DatePickerDialog, чтобы приглашать пользователей на их дни рождения. К сожалению, у меня появилось множество жалоб от пользователей о диалоговом окне Material-themed при его попытке, поэтому переход на него для меня не вариант: я должен придерживаться диалогового окна Holo.

Оказывается, Android 7.0 поставляется с ошибкой: попытка использовать тему Holo на этой платформе вместо этого возвращается к использованию сломанной темы материала для DatePickerDialog. Смотрите два отчета об ошибках:

Я использовал измененную форму это обходное решение Джеффа Локхарта, упоминаемое в этих отчетах об ошибках:

private static final class FixedHoloDatePickerDialog extends DatePickerDialog {
    private FixedHoloDatePickerDialog(Context context, OnDateSetListener callBack,
                                      int year, int monthOfYear, int dayOfMonth) {
        super(context, callBack, year, monthOfYear, dayOfMonth);

        // Force spinners on Android 7.0 only (SDK 24).
        // Note: I'm using a naked SDK value of 24 here, because I'm
        // targeting SDK 23, and Build.VERSION_CODES.N is not available yet.
        // But if you target SDK >= 24, you should have it.
        if (Build.VERSION.SDK_INT == 24) {
            try {
                final Field field = this.findField(
                        DatePickerDialog.class,
                        DatePicker.class,
                        "mDatePicker"
                );

                final DatePicker datePicker = (DatePicker) field.get(this);
                final Class<?> delegateClass = Class.forName(
                        "android.widget.DatePicker$DatePickerDelegate"
                );
                final Field delegateField = this.findField(
                        DatePicker.class,
                        delegateClass,
                        "mDelegate"
                );

                final Object delegate = delegateField.get(datePicker);
                final Class<?> spinnerDelegateClass = Class.forName(
                        "android.widget.DatePickerSpinnerDelegate"
                );

                if (delegate.getClass() != spinnerDelegateClass) {
                    delegateField.set(datePicker, null);
                    datePicker.removeAllViews();

                    final Constructor spinnerDelegateConstructor =
                            spinnerDelegateClass.getDeclaredConstructor(
                                    DatePicker.class,
                                    Context.class,
                                    AttributeSet.class,
                                    int.class,
                                    int.class
                            );
                    spinnerDelegateConstructor.setAccessible(true);

                    final Object spinnerDelegate = spinnerDelegateConstructor.newInstance(
                            datePicker,
                            context,
                            null,
                            android.R.attr.datePickerStyle,
                            0
                    );
                    delegateField.set(datePicker, spinnerDelegate);

                    datePicker.init(year, monthOfYear, dayOfMonth, this);
                    datePicker.setCalendarViewShown(false);
                    datePicker.setSpinnersShown(true);
                }
            } catch (Exception e) { /* Do nothing */ }
        }
    }

    /**
     * Find Field with expectedName in objectClass. If not found, find first occurrence of
     * target fieldClass in objectClass.
     */
    private Field findField(Class objectClass, Class fieldClass, String expectedName) {
        try {
            final Field field = objectClass.getDeclaredField(expectedName);
            field.setAccessible(true);
            return field;
        } catch (NoSuchFieldException e) { /* Ignore */ }

        // Search for it if it wasn't found under the expectedName.
        for (final Field field : objectClass.getDeclaredFields()) {
            if (field.getType() == fieldClass) {
                field.setAccessible(true);
                return field;
            }
        }

        return null;
    }
}

Что это такое:

  • Получить приватное поле DatePicker mDatePicker, принадлежащее этому диалоговому окну
  • Получить приватное поле DatePickerDelegate mDelegate, принадлежащее этому диалоговому окну
  • Убедитесь, что делегат еще не является экземпляром DatePickerSpinnerDelegate (тип делегата, который нам нужен)
  • Удалите все виды из DatePicker, так как они являются виджецами календаря материалов
  • Создайте новый экземпляр DatePickerSpinnerDelegate и назначьте его в поле mDelegate mDatePicker этого диалога
  • Повторно инициализируйте mDatePicker с информацией календаря и некоторыми параметрами, чтобы заставить его раздувать прядильщики

Чтобы использовать это обходное решение, я создаю ContextThemeWrapper вокруг моего Context, что позволяет мне задавать тему, в данном случае Holo:

final Context themedContext = new ContextThemeWrapper(
        this.getContext(),
        android.R.style.Theme_Holo_Light_Dialog
);

final DatePickerDialog dialog = new FixedHoloDatePickerDialog(
        themedContext,
        datePickerListener,
        calender.get(Calendar.YEAR),
        calendar.get(Calendar.MONTH),
        calendar.get(Calendar.DAY_OF_MONTH)
);

Примечания

  • Это использует отражение для доступа к закрытым полям. Как правило, это не надежный подход, и вы не можете рассчитывать на него. Я уменьшаю риск здесь: 1) ограничивая это одной версией SDK, v24; и 2) обертывание всего фрагмента кода отражения в блоке try {...} catch (Exception e) {/* NOP */}, поэтому, если какое-либо отражение не будет выполнено, ничего не произойдет и будет использоваться (грустно сломанный) вариант по умолчанию Material fallback.
  • В приведенных выше сообщениях об ошибках утверждается, что эта проблема исправлена ​​в Android 7.1 (SDK 25). Я не тестировал это.
  • оригинальный обходной код был для TimePickerDialog, который пострадал от аналогичной проблемы. Я изменил его для работы с DatePickerDialog вместо этого, а также упростил решение, чтобы оно было менее общим и более конкретным для моего конкретного варианта использования. Однако вы можете использовать более полную оригинальную версию и просто настроить ее для Date вместо Time.

Ответ 2

Выбор даты имеет собственный стиль https://developer.android.com/reference/android/R.style.html#Widget_DatePicker

Я думаю, вам нужно R.style.Widget.Holo.DatePicker вместо AlertDialog.THEME_HOLO_LIGHT. Возможно, вам может понадобиться создать свой собственный пустой стиль, который имеет @android:style/Widget.Holo.DatePicker как родительский и использовать его.

Ответ 3

В дополнение к решению от savanto: по состоянию на API 28 тема Holo Light устарела. Если вы ориентируетесь на устройства API 21/Lollipop и новее, вам следует использовать стили дизайна материалов, проверьте это решение. Я определил эти стили и затем связал их с R.style.dlg_datePicker в ContextThemeWrapper:

<style name="dlg_datePicker" parent="Theme.AppCompat.Light.Dialog">
    <item name="android:datePickerStyle">@style/datePicker_style</item>
    <item name="android:textSize">18sp</item>
</style>

<style name="datePicker_style" parent="android:Widget.Material.Light.DatePicker">
    <item name="android:datePickerMode">spinner</item>
</style>

Ответ 4

Попробуйте выполнить его работу. Я протестировал. И у него нет пробелов.

Также mDatePickerListener - мой DatePickerListner, который вы можете создать.

//Setting theme to android.R.style.Theme_Holo_Light_Dialog  
Calendar cal = Calendar.getInstance(TimeZone.getDefault());
DatePickerDialog datePicker = new DatePickerDialog(getContext(), android.R.style.Theme_Holo_Light_Dialog,
                    mDatePickerListener,
                    cal.get(Calendar.YEAR),
                    cal.get(Calendar.MONTH),
                    cal.get(Calendar.DAY_OF_MONTH));

datePicker.setCancelable(false);
datePicker.setTitle(getString(R.string.select_your_dob));
//This line is important to remove blank gaps
datePicker.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
datePicker.show();