Ответ 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
.