web-dev-qa-db-fra.com

Le style DatePickerDialog Holo a échoué sur Android 7 Nougat

De la part de nos clients, nous souhaitons conserver le style HOLO sur le DatePickerDialog pour toutes les versions d’OS Android, comme par exemple: DatePicker sur Android 7 -

Mais cela ne semble pas fonctionner correctement sur Android 7:

DatePicker sur Android 7

Depuis ma mise en œuvre:

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

Cela fonctionne bien pour les versions antérieures d'Android 7. Quelqu'un a le même problème?

Modifié: la solution est corrigée dans l'API 25 https://code.google.com/u/106133255289400340786/

11
Trung Le

J'utilise un DatePickerDialog pour inviter les utilisateurs pour leurs anniversaires. Malheureusement, lors de l’essai, plusieurs utilisateurs se sont plaints du dialogue sur le thème Matériau. Je ne peux donc pas y basculer: je dois m'en tenir au dialogue sur le thème Holo.

Il s'avère que Android 7.0 est livré avec un bogue: essayer d'utiliser le thème Holo sur cette plate-forme revient plutôt à utiliser un thème matériel broken pour la DatePickerDialog. Voir ces deux rapports de bugs:

J'ai utilisé une forme modifiée de cette solution de contournement de Jeff Lockhart référencée dans ces rapports de bogue:

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

Ce que cela fait est:

  • Obtenir le champ privé DatePicker mDatePicker appartenant à cette boîte de dialogue
  • Obtenir le champ privé DatePickerDelegate mDelegate appartenant à cette boîte de dialogue
  • Vérifiez que le délégué n'est pas déjà une instance de DatePickerSpinnerDelegate (le type de délégué que nous voulons)
  • Supprimer toutes les vues de la DatePicker, car ce sont les widgets de calendrier Matériau
  • Créez une nouvelle instance de DatePickerSpinnerDelegate et affectez-la au champ mDelegate de mDatePicker de cette boîte de dialogue.
  • Réinitialisez mDatePicker avec les informations de calendrier et quelques paramètres pour le gonfler.

Pour utiliser cette solution de contournement, je crée une ContextThemeWrapper autour de ma Context, ce qui me permet de définir un thème, dans ce cas 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)
);

Remarques:

  • Ceci utilise la réflexion pour accéder aux champs privés. Généralement, ce n'est pas une approche robuste et vous ne pouvez pas compter sur elle. Je minimise le risque ici en 1) limitant cela à une seule version du SDK, v24; et 2) en enveloppant la totalité du code de réflexion dans un bloc try {...} catch (Exception e) {/* NOP */}. Ainsi, en cas d'échec de la réflexion, rien ne se passera et la solution de secours par défaut (malheureusement cassée) sera utilisée.
  • Les rapports de bogue ci-dessus indiquent que ce problème a été résolu dans Android 7.1 (SDK 25). Je n'ai pas testé cela.
  • Le code de solution de contournement original correspond à TimePickerDialog qui présentait un problème similaire. Je l'ai modifiée pour fonctionner avec DatePickerDialog à la place et j'ai également simplifié la solution pour qu'elle soit moins générique et plus spécifique à mon cas d'utilisation exact. Cependant, vous pouvez utiliser la version originale plus complète et le modifier pour Date au lieu de Time.
26
savanto

Le sélecteur de date a son propre style https://developer.Android.com/reference/Android/R.style.html#Widget_DatePicker

Je pense que vous avez besoin de R.style.Widget.Holo.DatePicker au lieu de AlertDialog.THEME_HOLO_LIGHT. Il est possible que vous deviez créer votre propre style vide avec @Android:style/Widget.Holo.DatePicker comme parent et l'utiliser. 

0
Ben

Essayez de suivre cela fonctionne. J'ai testé. Et il n'y a pas de lacunes.

De plus, mDatePickerListener est mon DatePickerListner que vous pouvez créer.

//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
Akshay Taru