DatePickerDialog Holo styling failed on Android 7 Nougat

前端 未结 4 1171
悲&欢浪女
悲&欢浪女 2020-12-29 11:34

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-

But it seems to not to

相关标签:
4条回答
  • 2020-12-29 11:56

    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.

    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:

    • Issue 222808
    • Issue 222208

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

    What this does is:

    • 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

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

    Notes:

    • This uses reflection to access private fields. Generally, this is not a robust approach and you can't count on it. I'm mitigating the risk here by 1) restricting this to a single SDK version, v24; and 2) wrapping the entire bit of reflection code in a try {...} catch (Exception e) {/* NOP */} block, so if any of the reflection fails, nothing will happen and the (sadly broken) default Material fallback will be used.
    • The bug reports above claim that this issue has been fixed in Android 7.1 (SDK 25). I have not tested this.
    • The original workaround code was for TimePickerDialog that suffered from a similar problem. I've modified it to work with DatePickerDialog instead, and also simplified the solution to be less generic, and more specific to my exact use case. However, you could use the more complete original version and just tweak it for Date instead of Time.
    0 讨论(0)
  • 2020-12-29 12:13

    In addition to the solution by savanto: As of API 28 Holo Light Theme is deprecated. If you target API 21 / Lollipop devices and newer you should use Material design styles, check this solution. I defined these styles and then linked to R.style.dlg_datePicker in the 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>
    
    0 讨论(0)
  • 2020-12-29 12:13

    Try following it is working. I have tested. And it does not have blank gaps.

    Also mDatePickerListener is my DatePickerListner which you can create.

    //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();
    
    0 讨论(0)
  • 2020-12-29 12:21

    Date picker has it's own separate style https://developer.android.com/reference/android/R.style.html#Widget_DatePicker

    I think you need R.style.Widget.Holo.DatePickerinstead of AlertDialog.THEME_HOLO_LIGHT. It's possible you may need to create your own empty style that has @android:style/Widget.Holo.DatePicker as it's parent and use that.

    0 讨论(0)
提交回复
热议问题