web-dev-qa-db-fra.com

Jelly Bean DatePickerDialog - existe-t-il un moyen d'annuler?

--- Note aux modérateurs: aujourd'hui (15 juillet), J'ai remarqué que quelqu'un a déjà fait face à ce problème ici . Mais je ne suis pas sûr qu'il soit approprié de clore ce document en double, car je pense avoir fourni une bien meilleure explication du problème. Je ne sais pas si je devrais éditer l'autre question et y coller ce contenu, mais je ne suis pas à l'aise de trop modifier la question de quelqu'un d'autre. ---

J'ai quelque chose bizarre ici.

Je ne pense pas que le problème dépend du SDK contre lequel vous construisez. La version du système d'exploitation de l'appareil est ce qui compte.

Problème n ° 1: incohérence par défaut

DatePickerDialog a été modifié (?) dans Jelly Bean et ne fournit plus qu'un bouton fait . Les versions précédentes incluaient un bouton Annuler , ce qui pourrait affecter l'expérience utilisateur (incohérence, mémoire musculaire des versions précédentes Android versions).

Répliquer: Créez un projet de base. Mettez ceci dans 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();

Attendu: A bouton Annuler pour apparaître dans le dialogue.

Actuel: A Le bouton Annuler n'apparaît pas .

Captures d'écran: 4.0. (OK) et 4.1.1 (peut-être faux?).

Problème n ° 2: mauvais comportement de renvoi

La boîte de dialogue appelle quel que soit l'écouteur qu'elle appelle, puis toujours appelle OnDateSetListener écouteur. L'annulation appelle toujours la méthode définie et sa définition appelle la méthode deux fois.

Répliquez: Utilisez du code n ° 1, mais ajoutez du code ci-dessous (vous verrez que cela résout le problème n ° 1 , mais uniquement visuellement/UI):

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

Attendu:

  • Appuyez sur la touche RETOUR ou cliquez en dehors de la boîte de dialogue ne faites rien .
  • En appuyant sur "Annuler" devrait imprimer sélecteur Annuler! .
  • En appuyant sur "Set" devrait imprimer Picker Set! .

Courant:

  • Appuyez sur la touche RETOUR ou cliquez en dehors de la boîte de dialogue pour imprimer le sélecteur défini! .
  • Appuyez sur "Annuler" pour imprimer le sélecteur Annuler! , puis le sélecteur défini! .
  • Appuyez sur "Set" pour imprimer Sélecteur! puis Sélecteur! .

Lignes de 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!

Autres notes et commentaires

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

Remarque: Fixé à partir de Lollipop , source ici . Automatisé classe à utiliser dans les clients (compatible avec toutes les Android) mises à jour également.

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

  1. Télécharger this classe.
  2. Implémentez OnDateSetListener dans votre activité (ou modifiez la classe selon vos besoins).
  3. Déclenche le dialogue avec ce code (dans cet exemple, je l’utilise dans 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 que ça prend! La raison pour laquelle je garde toujours ma réponse "acceptée", c'est parce que je préfère toujours ma solution car elle a une très petite empreinte dans le code client, elle s'adresse au problème fondamental (l’auditeur étant appelé dans la classe framework), fonctionne parfaitement avec les changements de configuration et achemine la logique du code vers l’implémentation par défaut des versions précédentes Android ne sont pas affectés par ce bogue (voir ).

Réponse originale (conservée pour des raisons historiques et didactiques):

Source de bug

OK, on ​​dirait bien que c'est un bug et que quelqu'un d'autre l'a déjà rempli. Issue 348 .

J'ai trouvé que le problème est peut-être dans DatePickerDialog.Java. Où il se 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 que cela 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 rapport de correctif/bogue à Android, je serais heureux de le faire. En attendant, j'ai suggéré un correctif possible (simple) en tant que version attachée de DatePickerDialog.Java Dans le numéro ici.

Concept pour éviter le bug

Définissez le programme d'écoute sur null dans le constructeur et créez votre propre bouton BUTTON_POSITIVE Ultérieurement . Ça y est, détails ci-dessous.

Le problème se produit parce que DatePickerDialog.Java, Comme vous pouvez le voir dans la source, appelle une variable globale (mCallBack) qui stocke l'écouteur qui a été transmis 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.
}

L'astuce consiste donc à fournir un auditeur null à stocker en tant qu'écouteur, puis à lancer votre propre jeu de boutons (le code d'origine de # 1, mis à jour ci-dessous):

    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, cela fonctionnera à cause de la correction possible que j'ai postée ci-dessus.

Et puisque DatePickerDialog.Java Recherche un null à chaque fois qu'il lit mCallback ( depuis l'époque de l'API 3/1.5, il semble) = --- ne peut pas vérifier Honeycomb bien sûr), il ne déclenchera pas l'exception. Etant donné que Lollipop a résolu le problème, je ne vais pas me pencher dessus: utilisez simplement l'implémentation par défaut (couvert dans la classe que j'ai fournie).

Au début, j'avais peur de ne pas appeler la clearFocus(), mais j'ai déjà testé ici et les lignes du journal étaient propres. Donc, la ligne que j'ai proposée n'est peut-être même pas nécessaire, mais je ne le sais pas.

Compatibilité avec les niveaux d'API précédents (édité)

Comme je l'ai souligné dans le commentaire ci-dessous, il s'agissait d'un concept et vous pouvez télécharger le cours que j'utilise depuis mon compte Google Drive . La façon dont j'ai utilisé, l'implémentation système par défaut est utilisée sur les versions non affectées par le bogue.

J'ai pris quelques hypothèses (noms de boutons, etc.) qui répondent à mes besoins car je souhaitais réduire au minimum le code passe-partout dans les classes de clients. Exemple d'utilisation complète:

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");
115
davidcesarino

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 souhaitiez un moyen facile de la réparer 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*/ }
  }
}
16
dmon

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

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

Changer 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();
8
cirit

Celui de TimePicker basé sur la solution de David Cesarino, "TL; DR: 1-2-3 étapes faciles pour une solution globale"

TimePickerDialog ne fournit pas les fonctionnalités telles que DatePickerDialog.getDatePicker. Donc, OnTimeSetListener auditeur doit être fourni. Juste pour garder la similitude avec la solution de contournement DatePicker, j’ai maintenu l’ancien concept mListener. Vous pouvez le changer si vous en avez besoin.

Appeler et écouter correspond à la solution originale. Il suffit d'inclure

import Android.app.TimePickerDialog;
import Android.app.TimePickerDialog.OnTimeSetListener;

étendre la classe parente,

... implements OnDateSetListener, OnTimeSetListener

Mettre en place

 @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.
    }
}
4
Tejasvi Hegde

Au cas où quelqu'un voudrait une solution rapide, voici le code que j'ai utilisé:

public void showCustomDatePicker () {

final DatePicker mDatePicker = (DatePicker) getLayoutInflater().
        inflate(R.layout.date_picker_view, null);
//Set an initial date for the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
//Set the date now
mDatePicker.updateDate(year, month, day);

//create the dialog
AlertDialog.Builder mBuilder = new Builder(this);
//set the title
mBuilder.setTitle(getString(R.string.date_picker_title))
    //set our date picker
    .setView(mDatePicker)
    //set the buttons 
.setPositiveButton(Android.R.string.ok, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        //whatever method you choose to handle the date changes
            //the important thing to know is how to retrieve the data from the picker
        handleOnDateSet(mDatePicker.getYear(), 
                mDatePicker.getMonth(), 
                mDatePicker.getDayOfMonth());
    }
})
.setNegativeButton(Android.R.string.cancel, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
})
//create the dialog and show it.
.create().show();

}

Où layout.date_picker_view est une ressource de mise en page simple avec un DatePicker comme seul élément:

<!xml version="1.0" encoding="utf-8">
<DatePicker xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/date_picker"
Android:layout_width="fill_parent"   
Android:spinnersShown="true" 
Android:calendarViewShown="false"
Android:layout_height="fill_parent"/>

Voici le tutoriel complet au cas où cela vous intéresserait.

3
daniel_c05

Selon le brillant answer d'Ankur Chaudhary sur le même problème TimePickerDialog, si nous vérifions à l'intérieur de onDateSet si la vue donnée isShown() ou pas, elle résolvez le problème avec un minimum d'effort, sans avoir besoin d'étendre le sélecteur ou de rechercher des indicateurs hideux contournant le code, ni même de rechercher la version du système d'exploitation, procédez comme suit:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if (view.isShown()) {
        // read the date here :)
    }
}

et bien sûr, la même chose peut être faite pour onTimeSet selon la réponse d'Ankur

3
AbdelHady

Ma solution simple. Lorsque vous souhaitez le réactiver, exécutez simplement "resetFired" (par exemple, lors de la réouverture de la boîte de 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 
    }
}
3
Daniel Ryan

Pour gérer cette situation, j'ai utilisé un indicateur et redéfini les méthodes onCancel et onDismiss.

onCancel est appelé uniquement lorsque l'utilisateur appuie en dehors de la boîte de dialogue ou du bouton Précédent. onDismiss est toujours appelé

La définition d'un indicateur dans la méthode onCancel peut aider à filtrer dans la méthode onDismiss l'intention de l'utilisateur: annuler l'action ou l'action terminée. Ci-dessous un code qui montre l'idée.

public class DatePickerDialogFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    private boolean cancelDialog = false;
    private int year;
    private int month;
    private int day;

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        DatePickerDialog dpd = new DatePickerDialog(getActivity(), this, year, month, day);
        return dpd;
    }

    public void setDatePickerDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        cancelDialog = true;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (!cancelDialog) {
          #put the code you want to execute if the user clicks the done button
        }
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        setDatePickerDate(year, monthOfYear, dayOfMonth);
    }
}
2
Alvaro

Il existe une solution de contournement très simple si votre application n'utilise pas la barre d'actions. Notez en passant que certaines applications dépendent de cette fonctionnalité pour fonctionner, car annuler le sélecteur de date a une signification particulière (par exemple, il efface le champ de date en une chaîne vide qui, pour certaines applications, est un type de saisie valide et significatif ) et l’utilisation d’indicateurs booléens pour éviter que la date soit réglée deux fois sur OK ne vous aidera pas dans ce cas.

Ré. le correctif actuel, vous ne devez pas créer de nouveaux boutons ou votre propre boîte de dialogue. Le but est d’être compatible avec les anciennes versions d’Android, les versions boguées (4 .) et les versions futures, bien que ce dernier soit impossible à déterminer, bien sûr. Notez que dans === Android 2., onStop () pour Android.app.Dialog ne fait rien du tout, et en 4. * il fait mActionBar.setShowHideAnimationEnabled (false), ce qui est important uniquement si votre application a une La barre onStop () de DatePickerDialog, qui hérite de Dialog, contribue uniquement à mDatePicker.clearFocus () (à compter du dernier correctif de Android sources 4.3), ce qui ne semble pas essentiel.

Par conséquent, remplacer onStop () par une méthode qui ne fait rien devrait, dans de nombreux cas, réparer votre application et garantir qu'elle le restera dans un avenir prévisible. Il vous suffit donc d'étendre la classe DatePickerDialog avec votre propre méthode et de remplacer onStop () par une méthode factice. Vous devrez également fournir un ou deux constructeurs, selon vos besoins. Notez également que personne ne doit être tenté d’essayer d’exagérer ce correctif, par exemple. essayer de faire quelque chose directement avec la barre d’activité, car cela limiterait votre compatibilité aux dernières versions de Android seulement. Notez également que ce serait bien de pouvoir appeler le super pour DateStick de OnStop () car le bogue est uniquement dans onStop () dans DatePickerDialog lui-même, mais pas dans la super-classe de DatePickerDialog. Toutefois, cela nécessiterait que vous appeliez super.super.onStop () depuis votre classe personnalisée, ce qui Java ne vous laissera pas faire, cela va à l’encontre de la philosophie de l’encapsulation :) Voici mon petit cours avec lequel j’utilisais Verride DatePickerDialog. J'espère que ce commentaire sera utile à quelqu'un. Wojtek Jarosz

public class myDatePickerDialog extends DatePickerDialog {

public myDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
}

@Override
protected void onStop() {
    // Replacing tryNotifyDateSet() with nothing - this is a workaround for Android bug https://Android-review.googlesource.com/#/c/61270/A

    // Would also like to clear focus, but we cannot get at the private members, so we do nothing.  It seems to do no harm...
    // mDatePicker.clearFocus();

    // Now we would like to call super on onStop(), but actually what we would mean is super.super, because
    // it is super.onStop() that we are trying NOT to run, because it is buggy.  However, doing such a thing
    // in Java is not allowed, as it goes against the philosophy of encapsulation (the Creators never thought
    // that we might have to patch parent classes from the bottom up :)
    // However, we do not lose much by doing nothing at all, because in Android 2.* onStop() in androd.app.Dialog //actually
    // does nothing and in 4.* it does:
    //      if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); 
    // which is not essential for us here because we use no action bar... QED
    // So we do nothing and we intend to keep this workaround forever because of users with older devices, who might
    // run Android 4.1 - 4.3 for some time to come, even if the bug is fixed in later versions of Android.
}   

}

1
Wojtek Jarosz

Ma version de travail avec ClearButton utilisant les expressions lambda:

public class DatePickerFragment extends DialogFragment {
    private OnDateSelectListener dateSelectListener;
    private OnDateClearListener dateClearListener;

    public void setDateSelectListener(OnDateSelectListener dateSelectListener) {
        this.dateSelectListener = dateSelectListener;
    }

    public void setDateClearListener(OnDateClearListener dateClearListener) {
        this.dateClearListener = dateClearListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the current date as the default date in the picker
        final Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);

        // Create a new instance of DatePickerDialog and return it
        DatePickerDialog dialog = new DatePickerDialog(getActivity(), null, year, month, day);
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.setTitle("Select Date");
        dialog.setButton(BUTTON_POSITIVE, ("Done"), (dialog1, which) -> {
            DatePicker dp = dialog.getDatePicker();
            dialog.dismiss();
            dateSelectListener.onDateSelect(dp.getYear(), dp.getMonth(), dp.getDayOfMonth());
        });
        dialog.setButton(BUTTON_NEUTRAL, ("Clear"), (dialog1, which) -> {
            dialog.dismiss();
            dateClearListener.onDateClear();
        });
        dialog.setButton(BUTTON_NEGATIVE, ("Cancel"), (dialog1, which) -> {
            if (which == DialogInterface.BUTTON_NEGATIVE) {
                dialog.cancel();
            }
        });
        dialog.getDatePicker().setCalendarViewShown(false);
        return dialog;
    }


    public interface OnDateClearListener {
        void onDateClear();
    }

    public interface OnDateSelectListener {
        void onDateSelect(int year, int monthOfYear, int dayOfMonth);
    }
}
0
Vasiliy Prokopishin

Voici ma classe de solution de contournement pour DatePickerDialog sur le bouton d'annulation et l'abandon par le bouton Précédent. Copier et utiliser dans le style de DatePickerDialog (étant donné que l'écouteur est à état, nous devons créer une nouvelle instance lors de son utilisation, sinon, davantage de code est nécessaire pour que cela fonctionne.)

Utilisation:

new FixedDatePickerDialog(this,
            new FixedOnDateSetListener() {

                @Override
                public void onDateSet(DatePicker view, int year,
                        int monthOfYear, int dayOfMonth) {
                    if (isOkSelected()) {
                        // when DONE button is clicked
                    }
                }

            }, year, month, day).show();

Classe:

public class FixedDatePickerDialog extends DatePickerDialog {
private final FixedOnDateSetListener fixedCallback;
public FixedDatePickerDialog(Context context,
        FixedOnDateSetListener callBack, int year, int monthOfYear,
        int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
    fixedCallback = callBack;
    this.setButton(DialogInterface.BUTTON_NEGATIVE,
            context.getString(R.string.cancel), this);
    this.setButton(DialogInterface.BUTTON_POSITIVE,
            context.getString(R.string.done), this);
}

@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == BUTTON_POSITIVE) {
        fixedCallback.setOkSelected(true);
    } else {
        fixedCallback.setOkSelected(false);
    }
    super.onClick(dialog, which);
}

public abstract static class FixedOnDateSetListener implements
        OnDateSetListener {
    private boolean okSelected = false;

    @Override
    abstract public void onDateSet(DatePicker view, int year,
            int monthOfYear, int dayOfMonth);

    public void setOkSelected(boolean okSelected) {
        this.okSelected = okSelected;
    }

    public boolean isOkSelected() {
        return okSelected;
    }
}

}

0
Loc Phan


Essayez les concepts ci-dessous.

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


la méthode onDateSet () appelle deux fois (si vous archivez emulator.it appelle deux fois. Si vous utilisez un périphérique réel, il appellera correctement une fois. Si vous utilisez émulateur, utilisez le compteur. Si vous utilisez travailler dans le périphérique réel, puis ignorer la variable de compteur. Pour un périphérique réel, il fonctionne pour moi.)
lorsque l'utilisateur clique sur le bouton dans DatePickerDialog.
Pour cela, vous devez conserver une valeur de compteur et ne rien faire lorsque le premier appel appelle et effectuer l'opération lorsque la méthode appelle une deuxième fois.
Reportez-vous aux extraits de code ci-dessous.

   static int counter=0;       //Counter will be declared globally.

    DatePickerDialog picker = new DatePickerDialog(
            this,
            new OnDateSetListener() {
                @Override
                public void onDateSet(DatePicker v, int y, int m, int d) {

                   counter++;
                   if(counter==1) return;
                   counter=0;
                   //Do the operations here

                }
            },
            2012, 6, 15);
    picker.show();



Pour annuler datepicker dilalog cela fonctionne pour moi.Pour émulateur, il ne se réveille pas

DialogInterface.OnClickListener dialogOnClickListener=new DialogInterface.OnClickListener()
        {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

                if(which==Dialog.BUTTON_NEGATIVE)
                {
                    Log.i(tagName, "dialog negative button clicked");
                    dialog.dismiss();
                }

            }

        };

        mDatePickerDialog.setButton(Dialog.BUTTON_NEGATIVE, "Cancel", dialogOnClickListener);


Cela fonctionne pour moi pour un périphérique réel.Mais pour l'émulateur, il ne fonctionne pas correctement.Je pense que c'est un Android bug de l'émulateur.

0
SIVAKUMAR.J

Vous pouvez remplacer onCancel () et utiliser setOnDismissListener () pour détecter les actions utilisateur négatives. Et avec un DatePickerDialog.BUTTON_POSITIVE, vous savez que l'utilisateur veut définir une nouvelle date.

 DatePickerDialog mDPD = new DatePickerDialog(
                      getActivity(), mOnDateSetListener, mYear, mMonth, mDay);
 mDPD.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        // do something onCancek
        setDate = false;
    }
 });

 mDPD.setOnDismissListener(new OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface arg0) {
        // do something onDismiss
        setDate = false;
    }
});

mDPD.setButton(DatePickerDialog.BUTTON_POSITIVE, "Finish", new DatePickerDialog.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // user set new date
        setDate = true;
    }
});

puis vérifiez setDate:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if(setDate){
        //do something with new date
    }
}
0
Markus Rubey

J'ai aimé la réponse de David Cesarino ci-dessus, mais je voulais quelque chose qui remplaçait sans heurt le dialogue brisé et qui fonctionnerait dans tout dialogue manquant pour annuler/avoir un comportement d'annulation incorrect. Voici les classes dérivées pour DatePickerDialog/TimePickerDialog qui devraient fonctionner comme des remplacements directs. Ce ne sont pas des vues personnalisées. Il utilise la boîte de dialogue système, mais modifie simplement le comportement du bouton d'annulation/de retour pour fonctionner comme prévu.

Cela devrait fonctionner sur les API de niveau 3 et supérieur. Donc, en gros, toute version de Android (je l’ai testée sur jellybean et Lollipop en particulier).

DatePickerDialog:

package snappy_company_name_here;

import Android.content.Context;
import Android.content.DialogInterface;
import Android.widget.DatePicker;

/**
 * This is a modified version of DatePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * KitKat date pickers.
 *
 * Here is the bug: http://code.google.com/p/Android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class DatePickerDialog extends Android.app.DatePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnDateSetListener
    {
        private final OnDateSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnDateSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onDateSet(view, year, monthOfYear, dayOfMonth);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(Android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(Android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context,
                             final OnDateSetListener callBack,
                             final int year,
                             final int monthOfYear,
                             final int dayOfMonth,
                             final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @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(final Context context,
                            final OnDateSetListener callBack,
                            final int year,
                            final int monthOfYear,
                            final int dayOfMonth)
    {
        this(context, callBack, year, monthOfYear, dayOfMonth, new CallbackHelper(callBack));
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context, final int theme, final OnDateSetListener listener, final int year,
                             final int monthOfYear, final int dayOfMonth, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context The context the dialog is to run in.
     * @param theme the theme to apply to this dialog
     * @param listener 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(final Context context, final int theme, final OnDateSetListener listener, final int year,
                            final int monthOfYear, final int dayOfMonth)
    {
        this(context, theme, listener, year, monthOfYear, dayOfMonth, new CallbackHelper(listener));
    }
}

TimePickerDialog:

package snappy_company_name_here;

import Android.content.Context;
import Android.content.DialogInterface;
import Android.widget.TimePicker;

/**
 * This is a modified version of TimePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * KitKat date pickers.
 *
 * Here is the bug: http://code.google.com/p/Android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class TimePickerDialog extends Android.app.TimePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnTimeSetListener
    {
        private final OnTimeSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnTimeSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onTimeSet(view, hourOfDay, minute);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(Android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(Android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private  TimePickerDialog(final Context context,
                              final OnTimeSetListener callBack,
                              final int hourOfDay, final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context,
                            final OnTimeSetListener callBack,
                            final int hourOfDay, final int minute, final boolean is24HourView)
    {
        this(context, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param theme the theme to apply to this dialog
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView)
    {
        this(context, theme, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }
}
0
stuckj

Une solution simple consisterait à utiliser un booléen pour sauter la deuxième manche.

boolean isShow = false; // define global variable


// when showing time picker
TimePickerDialog timeDlg = new TimePickerDialog( this, new OnTimeSetListener()
            {

                @Override
                public void onTimeSet( TimePicker view, int hourOfDay, int minute )
                {
                    if ( isShow )
                    {
                        isShow = false;
                        // your code
                    }

                }
            }, 8, 30, false );

timeDlg.setButton( TimePickerDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = false;
                }
            } );
timeDlg.setButton( TimePickerDialog.BUTTON_POSITIVE, "Set", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = true;
                }
            } );

timeDlg.show();
0
chamikaw

J'utilise des sélecteurs de dates, des sélecteurs de temps et des sélecteurs de numéros. Les sélecteurs de numéros appellent onValueChanged chaque fois que l'utilisateur sélectionne un numéro, avant que le sélecteur ne soit congédié. Je disposais donc déjà d'une structure comme celle-ci pour faire quelque chose avec la valeur uniquement lorsque le sélecteur est congédié:

public int interimValue;
public int finalValue;

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    this.finalValue = this.interimValue;
}

J'ai étendu cette procédure pour définir des onClickListeners personnalisés pour mes boutons, avec un argument pour déterminer le bouton sur lequel l'utilisateur a cliqué. Maintenant, je peux vérifier quel bouton a été tapé avant de définir ma valeur finale:

public int interimValue;
public int finalValue;
public boolean saveButtonClicked;

public void setup() {
    picker.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.BUTTON_SAVE), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(true);
        }
    });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.BUTTON_CANCEL), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(false);
        }
    });
}

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onButtonClicked(boolean save) {
    this.saveButtonClicked = save;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    if (this.saveButtonClicked) {
        // save
        this.finalValue = this.interimValue;
    } else {
        // cancel
    }
}

Et puis j'ai étendu cela pour qu'il fonctionne avec les types de date et d'heure pour les sélecteurs de date et d'heure, ainsi que le type int pour les sélecteurs de numéros.

J'ai posté ceci parce que je pensais que c'était plus simple que certaines des solutions ci-dessus, mais maintenant que j'ai inclus tout le code, je suppose que ce n'est pas beaucoup plus simple! Mais cela correspond bien à la structure que j'avais déjà.

Mise à jour pour Lollipop: Apparemment, ce bogue ne se produit pas sur tous les appareils Android 4.1-4.4, car j'ai reçu quelques rapports d'utilisateurs qui avaient des sélecteurs de date et d'heure ' t appeler les rappels onDateSet et onTimeSet. Et le bogue a été officiellement corrigé dans Android 5.0. Mon approche ne fonctionnait que sur les appareils où le bogue était présent, car mes boutons personnalisés n’appelaient pas onClick de la boîte de dialogue. gestionnaire, qui est le seul endroit où onDateSet et onTimeSet sont appelés lorsque le bogue n'est pas présent.J'ai mis à jour mon code ci-dessus pour appeler la boîte de dialogue onClick, elle fonctionne donc que le bogue soit présent ou non.

0
arlomedia

Après avoir testé certaines des suggestions publiées ici, je pense personnellement que cette solution est la plus simple. Je passe "null" comme auditeur dans le constructeur DatePickerDialog, puis lorsque je clique sur le bouton "OK", j'appelle mon onDateSearchSetListener:

datePickerDialog = new DatePickerDialog(getContext(), null, dateSearch.get(Calendar.YEAR), dateSearch.get(Calendar.MONTH), dateSearch.get(Calendar.DAY_OF_MONTH));
    datePickerDialog.setCancelable(false);
    datePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Correct");
            onDateSearchSetListener.onDateSet(datePickerDialog.getDatePicker(), datePickerDialog.getDatePicker().getYear(), datePickerDialog.getDatePicker().getMonth(), datePickerDialog.getDatePicker().getDayOfMonth());
        }
    });
    datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Cancel");
            dialog.dismiss();
        }
    });
0
Birk Bonnicksen

Pour TimePickerDialog, la solution de contournement peut être la suivante:

TimePickerDialog createTimePickerDialog(Context context, int themeResId, TimePickerDialog.OnTimeSetListener orignalListener,
                                                         int hourOfDay, int minute, boolean is24HourView) {
        class KitKatTimeSetListener implements TimePickerDialog.OnTimeSetListener {
            private int hour;
            private int minute;

            private KitKatTimeSetListener() {
            }

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

            private int getHour() { return hour; }
            private int getMinute() {return minute; }
        };

        KitKatTimeSetListener kitkatTimeSetListener = new KitKatTimeSetListener();
        TimePickerDialog timePickerDialog = new TimePickerDialog(context, themeResId, kitkatTimeSetListener, hourOfDay, minute, is24HourView);

        timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(Android.R.string.ok), (dialog, which) -> {
            timePickerDialog.onClick(timePickerDialog, DialogInterface.BUTTON_POSITIVE);
            orignalListener.onTimeSet(new TimePicker(context), kitkatTimeSetListener.getHour(), kitkatTimeSetListener.getMinute());
            dialog.cancel();
        });
        timePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(Android.R.string.cancel), (dialog, which) -> {
            dialog.cancel();
        });

        return timePickerDialog;
    }

Je délègue tous les événements à l'encapsuleur KitKatSetTimeListener, et ne renvoie à l'OnTimeSetListener d'origine que si vous cliquez sur BUTTON_POSITIVE.

0
southerton