DatePickerDialog Holo 样式在 Android 7 Nougat 上失败

时间:2023-04-18
本文介绍了DatePickerDialog Holo 样式在 Android 7 Nougat 上失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时送ChatGPT账号..

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

From our customer demand we want to keep the HOLO style on the DatePickerDialog for all Android OS version, sth like: DatePicker on Android 7-

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

But it seems to not to work correctly on Android 7:

Android 7 上的日期选择器

来自我的实现:

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

在之前的 android 7 上运行良好.有人有同样的问题吗?

It's working fine for prior android 7. Anybody has the same issue?

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

Edited: solution is found to be fixed in API 25 https://code.google.com/u/106133255289400340786/

推荐答案

我使用 DatePickerDialog 来提示用户生日.不幸的是,我在尝试使用 Material 主题对话框时收到了很多用户投诉,因此切换到它不是我的选择:我必须坚持使用 Holo 主题对话框.

I use a DatePickerDialog to prompt users for their birthdays. Unfortunately, I've received a number of complaints from users about the Material-themed dialog when trying it, so switching to it is not an option for me: I have to stick to the Holo-themed dialog.

事实证明,Android 7.0 附带了一个错误:尝试在此平台上使用 Holo 主题而不是为 DatePickerDialog 使用 broken Material 主题.请参阅以下两个错误报告:

It turns out that Android 7.0 shipped with a bug: trying to use the Holo theme on this platform instead falls back to using a broken Material theme for the DatePickerDialog. See these two bug reports:

  • 问题 222808
  • 问题 222208

我使用了 Jeff Lockhart 提出的这个解决方法 的修改形式,在这些错误报告中引用:

I used a modified form of this workaround by Jeff Lockhart referenced in those bug reports:

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 中移除所有视图,因为它们是 Material 日历小部件
  • 创建一个新的DatePickerSpinnerDelegate实例,并将其分配给该对话框的mDatePickermDelegate字段
  • 使用日历信息和一些参数重新初始化 mDatePicker 以使其为微调器充气
  • Get the private DatePicker mDatePicker field belonging to this dialog
  • Get the private DatePickerDelegate mDelegate field belonging to this dialog
  • Check that the delegate is not already an instance of DatePickerSpinnerDelegate (the type of delegate we want)
  • Remove all views from the DatePicker, since they are the Material calendar widgets
  • Create a new instance of DatePickerSpinnerDelegate, and assign it to the mDelegate field of mDatePicker of this dialog
  • Re-initialize mDatePicker with calendar info and some params to get it to inflate the spinners

为了使用这个解决方法,我在我的 Context 周围创建了一个 ContextThemeWrapper,它允许我设置一个主题,在本例中是 Holo:

To use this workaround, I create a ContextThemeWrapper around my Context, which allows me to set a theme, in this case 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 *