Android 7 Nougat上的DatePickerDialog Holo样式失败

Tru*_* Le 11 java android calendar

根据我们的客户需求,我们希望在所有Android操作系统版本的DatePickerDialog上保留HOLO样式,例如: Android 7上的DatePicker-

但它似乎无法在Android 7上正常工作:

Android 7上的DatePicker

从我的实施:

new DatePickerDialog(getContext(), AlertDialog.THEME_HOLO_LIGHT,
                        mCalendarPickerListener,
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH),
                        calendar.get(Calendar.DAY_OF_MONTH));
Run Code Online (Sandbox Code Playgroud)

它适用于以前的Android 7.任何人都有相同的问题?

已编辑:解决方案已在API 25中修复 https://code.google.com/u/106133255289400340786/

sav*_*nto 30

我用a DatePickerDialog来提示用户过生日.不幸的是,在收集材料主题对话框时,我收到了一些关于材料主题对话框的抱怨,因此切换到它不是我的选择:我必须坚持以Holo为主题的对话框.

事实证明,Android 7.0附带了一个错误:试图在这个平台上使用Holo主题,而不是使用破碎的 Material主题DatePickerDialog.看到这两个错误报告:

我使用了Jeff Lockhart在这些错误报告中引用的这种变通方法的修改形式:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做是:

  • 获取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)
);
Run Code Online (Sandbox Code Playgroud)

备注:

  • 这使用反射来访问私有字段.一般来说,这不是一个强大的方法,你不能指望它.我在这里减轻风险1)将此限制为单个SDK版本,v24; 2)将整个反射代码包裹在一个try {...} catch (Exception e) {/* NOP */}块中,因此如果任何反射失败,则不会发生任何事情,并且将使用(可悲地破坏)默认材料回退.
  • 上面的错误报告声称此问题已在Android 7.1(SDK 25)中得到修复.我没有测试过这个.
  • 最初的解决方法的代码是用于TimePickerDialog从一个类似问题的困扰.我已将其修改为与其一起使用DatePickerDialog,并且还简化了解决方案,使其不那么通用,并且更具体到我的确切用例.但是,您可以使用更完整的原始版本,只需调整它Date而不是Time.