147 votes

Jelly Bean DatePickerDialog --- est-il un moyen d'annuler?

--- Note aux modérateurs: aujourd'Hui (le 15 juillet), j'ai remarqué que quelqu'un a déjà été confronté à ce problème ici. Mais je ne suis pas sûr si c'est approprié pour fermer cette comme un doublon, car je crois que j'ai fourni une bien meilleure explication de la question. Je ne suis pas sûr si je devrais modifier l'autre question, et de coller ce contenu là-bas, mais je ne suis pas à l'aise de changer de quelqu'un d'autre question. ---

J'ai quelque chose de bizarre ici.

Je ne pense pas que le problème dépend de la SDK vous construire contre. L'appareil version du système d'exploitation est ce qui compte.

Problème #1: incohérence par défaut

DatePickerDialog a été changé (?) dans Jelly Bean et maintenant seulement un Fait bouton. Les versions précédentes inclus un Annuler bouton, et cela peut affecter l'expérience utilisateur (incohérence, la mémoire musculaire des précédentes versions d'Android).

Répliquer: Créer un projet de base. Mettre cela en onCreate:

DatePickerDialog picker = new DatePickerDialog(
        this,
        new OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker v, int y, int m, int d) {
                Log.d("Picker", "Set!");
            }
        },
        2012, 6, 15);
picker.show();

Prévu: Un Annuler bouton apparaisse dans la boîte de dialogue.

Courant: Un Annuler bouton n'apparaît pas.

Captures d'écran: 4.0.3 (OK) et 4.1.1 (éventuellement mal?).

Problème n ° 2: faux rejeter comportement

Dialogue des appels selon l'auditeur, il doit appeler en effet, et puis toujours des appels OnDateSetListener auditeur. L'annulation appelle encore la méthode de jeu, et appelle la méthode à deux reprises.

Répliquer: Utilisez le #1 du code, mais ajoutez le code ci-dessous (vous verrez cela résout #1, mais seulement visuellement/UI):

picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });

Prévu:

  • En appuyant sur la touche RETOUR ou en cliquant à l'extérieur de la boîte de dialogue devrait ne rien faire.
  • Appuyant sur la touche "Annuler" doit imprimer le Sélecteur d'Annuler!.
  • En appuyant sur "Set" doit imprimer Sélecteur de Jeu!.

Courant:

  • En appuyant sur la touche RETOUR ou en cliquant à l'extérieur de la boîte de dialogue imprime Sélecteur de Jeu!.
  • Appuyant sur la touche "Annuler" imprime Sélecteur d'Annuler! et puis Sélecteur de Jeu!.
  • En appuyant sur "Set" imprime Sélecteur de Jeu! et puis Sélecteur de Jeu!.

Les lignes du journal montrant le comportement:

07-15 12:00:13.415: D/Picker(21000): Set!

07-15 12:00:24.860: D/Picker(21000): Cancel!
07-15 12:00:24.876: D/Picker(21000): Set!

07-15 12:00:33.696: D/Picker(21000): Set!
07-15 12:00:33.719: D/Picker(21000): Set!

D'autres notes et commentaires

  • L'enroulant autour d'un DatePickerFragment n'a pas d'importance. J'ai simplifié le problème pour vous, mais je l'ai testé.

115voto

David Cesarino Points 7258

TL;DR: 1-2-3 morts étapes faciles pour une solution globale:

  1. Télécharger cette classe.
  2. Mettre en oeuvre OnDateSetListener de votre activité (ou de changer de classe pour répondre à vos besoins).
  3. Déclencher le dialogue avec ce code (dans cet exemple, je l'utilise à l'intérieur d'un Fragment):

    Bundle b = new Bundle();
    b.putInt(DatePickerDialogFragment.YEAR, 2012);
    b.putInt(DatePickerDialogFragment.MONTH, 6);
    b.putInt(DatePickerDialogFragment.DATE, 17);
    DialogFragment picker = new DatePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getActivity().getSupportFragmentManager(), "frag_date_picker");
    

Et c'est tout ce qu'il faut! La raison, je garde toujours ma réponse comme "accepté" c'est parce que je préfère encore ma solution, car il a une très petite empreinte dans le code client, il traite de la question fondamentale (à l'auditeur d'être appelé dans le cadre de la classe), fonctionne très bien à travers les modifications de configuration et il route le code de la logique à l'implémentation par défaut dans les précédentes versions d'Android ne souffre pas de ce bug (voir le source de la classe).

Réponse originale à cette question (conservé dans l'historique et didactique des raisons):

--- Remarque: je suis en utilisant une solution de contournement temporaire. Voir à la fin de la réponse. ---

Bug source

OK, on dirait qu'il est en effet un bug et que quelqu'un d'autre est déjà rempli. Question 34833.

J'ai trouvé que le problème est probablement dans DatePickerDialog.java. Où il lit:

private void tryNotifyDateSet() {
    if (mCallBack != null) {
        mDatePicker.clearFocus();
        mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
                mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
    }
}

@Override
protected void onStop() {
    tryNotifyDateSet();
    super.onStop();
}

Je suppose qu'il aurait pu être:

@Override
protected void onStop() {
    // instead of the full tryNotifyDateSet() call:
    if (mCallBack != null) mDatePicker.clearFocus();
    super.onStop();
}

Maintenant, si quelqu'un peut me dire comment je peux proposer un patch/rapport de bug pour Android, je serais heureux de. Pendant ce temps, j'ai proposé une solution possible (simple) comme une version ci-jointe de l' DatePickerDialog.java dans la Question.

Concept pour éviter le bug

Définir l'auditeur null dans le constructeur et créer votre propre BUTTON_POSITIVE bouton plus tard. Ça y est, les détails ci-dessous.

Le problème se produit parce que l' DatePickerDialog.java, comme vous pouvez le voir dans la source, appelle une variable globale (mCallBack) qui stocke l'auditeur qui a été adoptée dans le constructeur:

    /**
 * @param context The context the dialog is to run in.
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    this(context, 0, callBack, year, monthOfYear, dayOfMonth);
}

    /**
 * @param context The context the dialog is to run in.
 * @param theme the theme to apply to this dialog
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        int theme,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    super(context, theme);

    mCallBack = callBack;
    // ... rest of the constructor.
}

Donc, l'astuce est de fournir un null auditeur à être stocké en tant qu'auditeur, et puis rouler votre propre ensemble de boutons (ci-dessous, le code original du n ° 1, mis à jour):

    DatePickerDialog picker = new DatePickerDialog(
        this,
        null, // instead of a listener
        2012, 6, 15);
    picker.setCancelable(true);
    picker.setCanceledOnTouchOutside(true);
    picker.setButton(DialogInterface.BUTTON_POSITIVE, "OK",
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Correct behavior!");
            }
        });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });
picker.show();

Maintenant, il va travailler en raison de la correction que j'ai posté ci-dessus.

Et depuis DatePickerDialog.java vérifie null chaque fois qu'il lit mCallback (depuis l'époque de l'API de 3/1.5 il me semble --- ne peut pas vérifier, en Nid d'abeille, bien sûr), il ne déclenche pas l'exception.

Au début, j'avais peur de ne pas l'appel de la clearFocus(), mais j'ai testé ici et les lignes du Journal étaient propres. De sorte que la ligne que j'ai proposée peut même ne pas être nécessaire, mais je ne sais pas.

La compatibilité avec les précédentes API niveaux (édité)

Comme je l'ai indiqué dans le commentaire ci-dessous, qui a été un concept, et vous pouvez télécharger la classe je suis à l'aide de mon compte Google Drive ou de l'utilisation DatePickerDialogFragment.

J'ai pris quelques hypothèses (les noms de bouton etc.) qui sont adaptés à mes besoins, car je voulais réduire code réutilisable dans les classes clientes à un minimum. Complet exemple d'utilisation:

class YourActivity extends SherlockFragmentActivity implements OnDateSetListener

// ...

Bundle b = new Bundle();
b.putInt(DatePickerDialogFragment.YEAR, 2012);
b.putInt(DatePickerDialogFragment.MONTH, 6);
b.putInt(DatePickerDialogFragment.DATE, 17);
DialogFragment picker = new DatePickerDialogFragment();
picker.setArguments(b);
picker.show(getActivity().getSupportFragmentManager(), "fragment_date_picker");

16voto

dmon Points 20795

Je vais ajouter mon propre riff sur la solution publiée par David Cesarino, au cas où vous n'utiliseriez pas Fragments, et que vous voulez un moyen facile de le corriger dans toutes les versions (2.1 à 4.1):

 public class FixedDatePickerDialog extends DatePickerDialog {
  //I use a Calendar object to initialize it, but you can revert to Y,M,D easily
  public FixedDatePickerDialog(Calendar dateToShow, Context context, OnDateSetListener callBack) {
    super(context, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  public FixedDatePickerDialog(Calendar dateToShow, Context context, int theme,
    OnDateSetListener callBack) {
    super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  private void initializePicker(final OnDateSetListener callback) {
    try {
      //If you're only using Honeycomb+ then you can just call getDatePicker() instead of using reflection
      Field pickerField = DatePickerDialog.class.getDeclaredField("mDatePicker");
      pickerField.setAccessible(true);
      final DatePicker picker = (DatePicker) pickerField.get(this);
      this.setCancelable(true);
      this.setButton(DialogInterface.BUTTON_NEGATIVE, getContext().getText(android.R.string.cancel), (OnClickListener) null);
      this.setButton(DialogInterface.BUTTON_POSITIVE, getContext().getText(android.R.string.ok),
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                picker.clearFocus(); //Focus must be cleared so the value change listener is called
                callback.onDateSet(picker, picker.getYear(), picker.getMonth(), picker.getDayOfMonth());
              }
          });
    } catch (Exception e) { /* Reflection probably failed*/ }
  }
}
 

8voto

cirit Points 331

Jusqu'à ce que le bug soit corrigé, je suggère de ne pas utiliser DatePickerDialog ou TimePickerDialog. Utilisez AlertDialog personnalisé avec le widget TimePicker / DatePicker;

Changez TimePickerDialog avec;

     final TimePicker timePicker = new TimePicker(this);
    timePicker.setIs24HourView(true);
    timePicker.setCurrentHour(20);
    timePicker.setCurrentMinute(15);

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", timePicker.getCurrentHour() + ":"
                            + timePicker.getCurrentMinute());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(timePicker).show();
 

Modifier DatePickerDialog avec;

     final DatePicker datePicker = new DatePicker(this);
    datePicker.init(2012, 10, 5, null);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        datePicker.setCalendarViewShown(false);
    }

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", datePicker.getYear() + " "
                            + (datePicker.getMonth() + 1) + " "
                            + datePicker.getDayOfMonth());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(datePicker).show();
 

4voto

Tejasvi Hegde Points 141

L'un pour TimePicker basé sur la solution par David Cesarino , "TL;DR: 1-2-3 morts étapes faciles pour une solution globale"

TimePickerDialog ne fournit pas la fonctionnalité comme DatePickerDialog.getDatePicker. Donc, OnTimeSetListener auditeur doit être fourni. Juste pour garder la similitude avec DatePicker solution de rechange, j'ai conservé l'ancien mListener concept. Vous pouvez le changer si vous en avez besoin.

L'appel et l'Auditeur est le même que l'original de la solution. Il suffit d'inclure

import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;

étendre la classe parent,

... implements OnDateSetListener, OnTimeSetListener

Mettre en œuvre

 @Override
 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
 ...
 }

exemple d'appel

    Calendar cal = Calendar.getInstance();
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int minute = cal.get(Calendar.MINUTE);


    Bundle b = new Bundle();
    b.putInt(TimePickerDialogFragment.HOUR, hour);
    b.putInt(TimePickerDialogFragment.MINUTE, minute);

    DialogFragment picker = new TimePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getSupportFragmentManager(), "frag_time_picker");

(Mise à jour pour gérer annuler)

public class TimePickerDialogFragment extends DialogFragment {

    public static final String HOUR = "Hour";
    public static final String MINUTE = "Minute";

    private boolean isCancelled = false; //Added to handle cancel
    private TimePickerDialog.OnTimeSetListener mListener;

    //Added to handle parent listener
    private TimePickerDialog.OnTimeSetListener mTimeSetListener = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            if (!isCancelled)
            {
                mListener.onTimeSet(view,hourOfDay,minute);
            }
        }
    };
    //
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mListener = (TimePickerDialog.OnTimeSetListener) activity;
    }

    @Override
    public void onDetach() {
        this.mListener = null;
        super.onDetach();
    }

    @TargetApi(11)
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Bundle b = getArguments();
        int h = b.getInt(HOUR);
        int m = b.getInt(MINUTE);

        final TimePickerDialog picker = new TimePickerDialog(getActivity(), getConstructorListener(), h, m,DateFormat.is24HourFormat(getActivity()));

        //final TimePicker timePicker = new TimePicker(getBaseContext());
        if (hasJellyBeanAndAbove()) {
            picker.setButton(DialogInterface.BUTTON_POSITIVE,
                    getActivity().getString(android.R.string.ok),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = false; //Cancel flag, used in mTimeSetListener
                        }
                    });
            picker.setButton(DialogInterface.BUTTON_NEGATIVE,
                    getActivity().getString(android.R.string.cancel),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = true; //Cancel flag, used in mTimeSetListener
                        }
                    });
        }
        return picker;
    }
    private boolean hasJellyBeanAndAbove() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    private TimePickerDialog.OnTimeSetListener getConstructorListener() {
        return hasJellyBeanAndAbove() ? mTimeSetListener : mListener; //instead of null, mTimeSetListener is returned.
    }
}

3voto

Zammbi Points 2307

Ma solution simple. Lorsque vous voulez le faire à nouveau, lancez "resetFired" (disons quand vous ouvrez à nouveau le dialogue).

 private class FixedDatePickerDialogListener implements DatePickerDialog.OnDateSetListener{
    private boolean fired;

    public void resetFired(){
        fired = false;
    }

    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (fired) {
            Log.i("DatePicker", "Double fire occurred.");
            return;//ignore and return.
        } 
        //put your code here to handle onDateSet
        fired = true;//first time fired 
    }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X