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

148

--- Note aux modérateurs: Aujourd'hui (15 juillet), j'ai remarqué que quelqu'un était déjà confronté à ce problème ici . Mais je ne suis pas sûr qu'il soit approprié de fermer cela en double, car je pense avoir fourni une bien meilleure explication du problème. Je ne suis pas sûr de devoir modifier l'autre question et y coller ce contenu, mais je ne suis pas à l'aise de trop changer la question de quelqu'un d'autre. ---

J'ai quelque chose de bizarre ici.

Je ne pense pas que le problème dépende du SDK sur 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

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

Répliquer: créez un projet de base. Mettez ceci dansonCreate:

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: unbouton Annuler apparaît dans la boîte de dialogue.

Actuel: Unbouton Annuler n'apparaît pas.

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

Problème n ° 2: mauvais comportement de renvoi

Dialog appelle l'auditeur qu'il doit effectivement appeler, puis appelle toujours l'OnDateSetListener auditeur. L'annulation appelle toujours la méthode set et sa définition appelle la méthode deux fois.

Répliquer: utilisez le code n ° 1, mais ajoutez le code ci-dessous (vous verrez que cela résout le 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:

  • Appuyer sur la touche RETOUR ou cliquer en dehors de la boîte de dialogue ne devrait rien faire .
  • Appuyer sur "Annuler" devrait imprimer Picker Cancel! .
  • Appuyer sur "Set" devrait imprimer Picker Set! .

Actuel:

  • Appuyer sur la touche RETOUR ou cliquer en dehors de la boîte de dialogue imprime le jeu de sélecteurs! .
  • Appuyez sur "Annuler" pour imprimer le sélecteur Annuler! puis Picker Set! .
  • En appuyant sur "Set" imprime Picker Set! puis Picker Set! .

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'envelopper DatePickerFragmentn'a pas d'importance. J'ai simplifié le problème pour vous, mais je l'ai testé.
davidcsb
la source
Félicitations, vous semblez avoir trouvé un bogue dans Android. Vous pouvez le signaler ici .
Michael Hampton
1
Rapport de bogue très bien écrit. Je peux le comprendre complètement sans avoir à tester le code.
Cheok Yan Cheng
6
J'exhorte tout le monde à voter sur cette question! Numéro 34833
Bradley
Ne pourriez-vous pas simplement remplacer la fonction du bouton pour agir comme si elle avait été rejetée en raison d'un toucher en dehors de la boîte de dialogue?
Karthik Balakrishnan
2
Bug est toujours ouvert après 2 ans ... incroyable.
erdomester

Réponses:

115

Remarque: corrigé à partir de Lollipop , source ici . Classe automatisée pour une utilisation dans les clients (compatible avec toutes les versions d'Android) également mise à jour.

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

  1. Téléchargez cette classe.
  2. Implémentez OnDateSetListenervotre activité (ou modifiez la classe en fonction de vos besoins).
  3. Déclenchez 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 qu'il faut! La raison pour laquelle je garde toujours ma réponse comme «acceptée» est parce que je préfère toujours ma solution car elle a une très petite empreinte dans le code client, elle résout le problème fondamental (l'auditeur étant appelé dans la classe du framework), fonctionne bien à travers les changements de configuration et il achemine la logique du code vers l'implémentation par défaut dans les versions précédentes d'Android non affectées par ce bogue (voir la source de la classe).

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

Source de bogue

OK, on ​​dirait que c'est en effet un bug et que quelqu'un d'autre l'a déjà rempli. Émission 34833 .

J'ai constaté que le problème était peut-être là 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 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, j'en serais ravi. Pendant ce temps, j'ai suggéré une solution possible (simple) en tant que version jointe deDatePickerDialog.java dans le problème.

Concept pour éviter le bug

Définissez l'écouteur sur nulldans le constructeur et créez votre propre BUTTON_POSITIVEbouton ultérieurement . Voilà, détails ci-dessous.

Le problème se produit car DatePickerDialog.java, comme vous pouvez le voir dans la source, appelle une variable globale ( mCallBack) qui stocke l'écouteur qui a été passé 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 consiste à fournir un nullauditeur à stocker en tant qu'auditeur, puis à lancer votre propre ensemble de boutons (ci-dessous se trouve le code d'origine 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, cela fonctionnera à cause de la correction possible que j'ai publiée ci-dessus.

Et comme il DatePickerDialog.javavérifie un à nullchaque fois qu'il lit mCallback( depuis les jours de l'API 3 / 1.5, il semble --- impossible de vérifier Honeycomb bien sûr), cela ne déclenchera pas l'exception.Étant donné que Lollipop a résolu le problème, je ne vais pas l'examiner: utilisez simplement l'implémentation par défaut (couverte dans la classe que j'ai fournie).

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

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

Comme je l'ai souligné dans le commentaire ci-dessous, c'était un concept, et vous pouvez télécharger la classe que j'utilise à partir de 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 conviennent à mes besoins car je voulais réduire au minimum le code standard dans les classes clientes. Exemple d'utilisation complet:

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");
David Cesarino
la source
11
Super travail sur la recherche! Triste que ce soit nécessaire, mais génial quand même.
CommonsWare
1
Très agréable! Je me suis cogné la tête contre le mur sur ce problème qui mettait à jour mes champs de texte et appelait / n'appelait pas les bons rappels. Je vous remercie! Je me demande si la solution de contournement suggérée est «à l'épreuve du futur» ou si vous pensez qu'elle causera des problèmes lorsque le bogue est corrigé?
span
1
@RomainGuidoux voir la réponse mise à jour à la fin. La classe dans le lien a l'intelligence d'appeler cette méthode uniquement dans jelly bean. Sur tout ce qui se trouve en dessous, il contournera cela et utilisera l'implémentation système par défaut, acheminant l'appel système pour ondateset vers votre activité. C'est juste que dans jelly bean, il prend des mesures supplémentaires (en évitant le bogue) avant de router le rappel, et cela implique d'appeler cette méthode en nid d'abeille +. Mais encore une fois, uniquement dans JB.
davidcsb
1
Excellent travail ici. Je n'ai pas les mots pour dire à quel point cette question est ridicule.
Bill Phillips
5
Que diriez-vous de TimePickerDialog? Il semble que TimePickerDialog n'ait pas de getTimePicker ()
lokoko
16

Je vais ajouter mon propre riff sur la solution publiée par David Cesarino, au cas où vous n'utilisez pas Fragments, et que vous souhaitez un moyen simple de le 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*/ }
  }
}
dmon
la source
Rappelez-vous simplement: si vous configurez en dur les noms des boutons pour valider et annuler, il est probablement préférable d'utiliser les champs standard android.R.string.oket android.R.string.cancelplutôt que ceux de l'utilisateur. Et merci pour la réponse.
davidcsb
Ah, bien, je n'ai pas remarqué qu'ils ont fourni le texte OK et Annuler. Merci!
dmon
obtenir une exception de pointeur nul au super (contexte, thème, null, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore
Euh ... passez-vous un nul dateToShow? L'autre null est en fait le "correctif", donc cela devrait être là. Sur quelle version êtes-vous?
dmon
pouvez-vous s'il vous plaît me faire savoir ce importque vous aviez pour Field? J'ai 6 options et aucune d'elles n'a fonctionné.
Adil Malik
8

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

Changez 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();
cirit
la source
4

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. Ainsi, l' écouteur OnTimeSetListener doit être fourni. Juste pour garder la similitude avec la solution de contournement de DatePicker, j'ai conservé l'ancien concept mListener. Vous pouvez le modifier si vous en avez besoin.

L'appel et l'auditeur sont identiques à la solution d'origine. Incluez simplement

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

(Mis à jour pour gérer l'annulation)

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.
    }
}
Tejasvi Hegde
la source
Cela ne fonctionne pas pour moi lorsque je l'essaye sur s3 API 18
AbdelHady
3

Au cas où quelqu'un voudrait une solution de contournement 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ù vous seriez intéressé.

daniel_c05
la source
Cela a fonctionné à merveille pour moi. Facile à comprendre, facile à mettre en œuvre! Testé le 4.4
erdomester
3

Ma solution simple. Lorsque vous voulez le faire redémarrer, lancez simplement "resetFired" (par exemple, lorsque vous ouvrez à nouveau 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 
    }
}
Daniel Ryan
la source
@AbdelHady votre modification de code était incorrecte mais je l'ai modifiée pour la rendre plus claire. Il n'est pas nécessaire d'ajouter une méthode "isJellyBeanOrAbove ()", il n'y a aucun avantage, ajoute juste une complexité inutile. L'appel de resetFired est bon marché.
Daniel Ryan
le code ici est incorrect, car onDateSetil sera appelé une fois lorsque la boîte de dialogue est fermée par quelque moyen que ce soit, et si ce moyen était de régler l'heure, il sera à nouveau déclenché, nous devons donc attraper le deuxième appel, pas le premier comme vous l'avez fait,
AbdelHady
En ce qui concerne isJellyBeanOrAbove(), les versions inférieures à Jellybean n'ont pas le bogue que toute cette question concerne, et étant donné que nous voulons attraper le deuxième appel, le code ne fonctionnera pas à moins que nous ne fassions cette vérification, croyez-moi, j'ai essayé mon code sur les émulateurs et les appareils réels (avec différentes versions) plusieurs fois et cela fonctionne comme un charme
AbdelHady
Je ne sais pas si cela vaut un vote défavorable. J'ai posté ceci il y a 2 ans, cela a été dans notre application commerciale depuis lors fonctionne très bien. Aucun bogue signalé par nos équipes QA ou nos milliers d'utilisateurs. Ceci est censé être une réponse simple que les gens peuvent étendre. Appelez "resetFired" lorsque vous souhaitez qu'il se déclenche à nouveau.
Daniel Ryan
Dans notre application "isJellyBeanOrAbove" n'est pas nécessaire. L'application fonctionnera correctement dans toutes les versions si vous appelez "resetFired" dans les zones appropriées.
Daniel Ryan
3

Selon la brillante réponse d'Ankur Chaudhary sur le TimePickerDialogproblème similaire , si nous vérifions à l'intérieur onDateSetsi la vue donnée isShown()ou non, cela résoudra tout le problème avec un minimum d'effort, sans avoir besoin d'étendre le sélecteur ou de vérifier certains drapeaux hideux autour du code. ou même en vérifiant 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 onTimeSetselon la réponse d'Ankur

AbdelHady
la source
1
Meilleure réponse de toutes les autres!
hiew1
2

La façon dont j'ai géré cette situation consistait à utiliser un indicateur et à remplacer les méthodes onCancel et onDismiss.

onCancel est appelé uniquement lorsque l'utilisateur touche en dehors de la boîte de dialogue ou du bouton de retour. 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);
    }
}
Alvaro
la source
1

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

Ré. le correctif réel, vous n'avez pas à créer de nouveaux boutons ou votre propre boîte de dialogue. Le but est d'être compatible avec les deux, les anciennes versions d'Android, les buggy (4. ) et les futures, bien que ce dernier soit impossible à certifier, bien sûr. Notez que dans Android 2. , onStop () pour android.app.Dialog ne fait rien du tout, et dans 4. * il fait mActionBar.setShowHideAnimationEnabled (false), ce qui n'est important que si votre application a une barre d'action. OnStop () dans DatePickerDialog, qui hérite de Dialog, ne contribue qu'à mDatePicker.clearFocus () (à partir du dernier correctif des sources Android 4.3), ce qui ne semble pas indispensable.

Par conséquent, le remplacement de 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. Ainsi, étendez simplement la classe DatePickerDialog avec la vôtre et remplacez onStop () avec une méthode factice. Vous devrez également fournir un ou deux constructeurs, selon vos besoins. Notez également qu'il ne faut pas être tenté d'essayer d'exagérer ce correctif en essayant par exemple de faire quelque chose avec la barre d'activité directement, car cela limiterait votre compatibilité aux dernières versions d'Android uniquement. Notez également que ce serait bien de pouvoir appeler le super pour onStop () de DatePicker car le bogue est uniquement dans le onStop () de DatePickerDialog lui-même, mais pas dans la super classe de DatePickerDialog. Cependant, cela vous obligerait à appeler super.super.onStop () à partir de votre classe personnalisée, ce que Java ne vous laissera pas faire, car cela va à l'encontre de la philosophie d'encapsulation :) Voici ma petite classe que j'ai utilisée pour verrouiller 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.
}   

}

Wojtek Jarosz
la source
Même si une ActionBar est utilisée, cela peut être une solution de contournement acceptable si vous ne masquez / n'affichez jamais l'ActionBar. Pour rendre les choses plus sûres, vous pouvez remplacer onStart pour ne rien faire également (les appels pour l'animation seraient alors décrochés en toute sécurité). Et même si vous le masquez / l'affichez, c'est juste l'animation qui est désactivée.
Daniel le
0


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 enregistrez des appels emulator.it deux fois.Si vous utilisez un appareil réel, il appellera correctement une seule fois.Si vous utilisez l'émulateur, utilisez le compteur.Si vous travaillez dans un appareil réel, alors ignorer la variable de compteur. Pour un appareil réel, cela fonctionne pour moi.)
lorsque l'utilisateur clique sur le bouton dans DatePickerDialog.
pour cela, vous devez maintenir une valeur de compteur et ne rien faire lorsque le mothod appelle la première fois et effectuer l'opération lorsque la méthode appelle la 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 le dilalogue datepicker, cela fonctionne pour moi.Pour l'émulateur, il ne fonctionne 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 vrai appareil.Mais pour l'émulateur, cela ne fonctionne pas correctement.Je pense que c'est un bogue d'émulateur Android.

SIVAKUMAR.J
la source
0

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

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();
Chamikaw
la source
Encore une fois, comme je l'ai dit à l'autre gars avant vous, cela ne résout pas le bogue. Je ne veux pas paraître impoli, mais vous devriez tester votre propre solution avant de publier ici ... juste pour prouver mon point, utilisez votre code et laissez l'utilisateur appuyer pour annuler la boîte de dialogue et voir ce que je veux dire. La cause du bogue est d' onStopappeler la méthode alors qu'elle ne devrait pas ... elle se déclenche deux fois est une conséquence du bogue.
davidcsb
Si je n'étais pas assez clair, laissez-moi être: en appuyant sur retour pour annuler les appels de dialogue onDateSet. Ainsi, cassé.
davidcsb
Merci d'avoir montré l'erreur. J'édite la réponse pour qu'elle fonctionne avec le bouton retour. Votre réponse fonctionne mais DatePicker dp = picker.getDatePicker (); ne fonctionne pas avec TimePickers car la méthode getTimePicker () n'est pas ajoutée. Donc ce serait une réponse valide
chamikaw
Comment essayons-nous de sauter la deuxième exécution? !!, je l'ai essayé plusieurs fois, lorsque la fermeture de la boîte de dialogue onDateSetsera appelée une fois par quelque moyen que ce soit, mais lorsque vous choisissez "done" ou "set", elle sera appelée deux fois. Par conséquent, nous devons sauter uniquement le premier, donc s'il est appelé deux fois alors et alors seulement nous avons la date correcte
AbdelHady
0

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 souhaite 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
    }
}
Markus Rubey
la source
0

Voici ma classe de contournement pour DatePickerDialog sur le bouton d'annulation ainsi que son abandon par le bouton de retour. Copier et utiliser dans le style de DatePickerDialog (comme l'écouteur est avec état, nous devons créer une nouvelle instance lors de l'utilisation, sinon plus de code est nécessaire pour le faire fonctionner)

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

}

Loc Phan
la source
0

J'utilise des sélecteurs de dates, des sélecteurs de temps et des sélecteurs de nombres. 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 rejeté, donc j'avais déjà une structure comme celle-ci pour faire quelque chose avec la valeur uniquement lorsque le sélecteur est rejeté:

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 cela pour définir des onClickListeners personnalisés pour mes boutons, avec un argument pour voir quel bouton a été cliqué. Maintenant, je peux vérifier quel bouton a été touché 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 travailler 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 nombres.

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 s'intègre parfaitement dans 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 dont les sélecteurs de date et d'heure n'appelaient pas les rappels onDateSet et onTimeSet. Et le bogue a été officiellement corrigé dans Android 5.0. Mon approche ne fonctionnait que sur les appareils sur lesquels le bogue est présent, car mes boutons personnalisés n'appelaient pas le gestionnaire onClick de la boîte de dialogue, 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 onClick de la boîte de dialogue, donc maintenant cela fonctionne, que le bogue soit présent ou non.

arlomedia
la source
0

J'ai aimé la réponse de David Cesarino ci-dessus, mais je voulais quelque chose qui remplacerait la boîte de dialogue cassée et qui fonctionnerait sur n'importe quelle boîte de dialogue qui pourrait manquer d'annuler / avoir un comportement d'annulation incorrect. Voici les classes dérivées pour DatePickerDialog / TimePickerDialog qui devraient fonctionner comme des remplacements. 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 / retour pour qu'il fonctionne comme prévu.

Cela devrait fonctionner au niveau d'API 3 et supérieur. Donc, pratiquement n'importe quelle version d'Android (je l'ai testé sur jellybean et sucette spécifiquement).

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));
    }
}
coincé
la source
C'est assez similaire à la réponse de The Sea ci-dessus.
stuckj
0

Ma version de travail avec ClearButton utilisant des 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);
    }
}
Vasiliy Prokopishin
la source
0

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 au wrapper KitKatSetTimeListener, et je ne renvoie à OnTimeSetListener d'origine qu'en cas de clic sur BUTTON_POSITIVE.

Southerton
la source
0

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();
        }
    });
Birk Bonnicksen
la source
-1

Je sais que cet article est ici depuis presque un an, mais j'ai pensé que je devrais publier mes conclusions. Vous pouvez toujours conserver l'auditeur (au lieu de le définir sur mull) et continuer à travailler comme prévu. La clé est de définir implicitement les boutons "OK" ou (et) les boutons "Annuler". Je l'ai testé et cela fonctionne avec gratitude pour moi. L'auditeur n'est pas renvoyé deux fois.

Regardez cet exemple,

private void setTime(){
final Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);

final TimePickerDialog timepicker = new TimePickerDialog(this.getActivity(),
        timePickerListener,
        hour, 
        minute, 
        DateFormat.is24HourFormat(getActivity()));

timepicker.setButton(DialogInterface.BUTTON_POSITIVE, "Print", new    
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which) {
            print = true;
            timepicker.dismiss();           
        }
});

timepicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new 
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which){
            print = false;
            timepicker.dismiss();       
        }
});

timepicker.setCancelable(false);
timepicker.show();
}
Crocodile
la source
Clair et simple, cela ne fonctionne pas comme vous le dites. timePickerListenerest toujours appelé indépendamment de ce que vous faites dans votre boîte de dialogue. Je n'ai même pas besoin de tester pour le savoir, il vous suffit de regarder les sources : si vous ne le définissez pas sur null, tryNotifyTimeSet()appellera l'auditeur à la onTimeSet()fois dans son onClick()et onStop().
davidcsb
En d'autres termes, ne laissez pas la classe native contenir une référence à votre auditeur (c'est-à-dire en la passant dans le constructeur). La façon dont vous gérez les boutons n'a pas d'importance.
davidcsb
Salut David, Vous n'avez pas testé cela et vous avez supposé que cela ne fonctionnera pas. C'est le morceau de code exact que j'utilise actuellement dans mon application et cela fonctionne comme un champion.
Crocodile
1) Je n'ai jamais dit que cela ne fonctionnait pas. J'ai dit que cela ne fonctionnait pas comme vous l'aviez dit. Veuillez relire. 2) vous avez violé LSP et SRP, d'heures perdre de changer inutilement tout votre entière logique client qui n'a pas besoin de changements pour commencer. 3) votre réponse répond à la question, oui, sinon je la marquerais pour suppression comme "pas une réponse", mais 4) votre réponse est toujours (désolé) très inefficace et ne résout pas le problème de conception fondamental (auditeur appelé ), d'où seulement le vote défavorable. 6) vous avez désactivé le retour et en avez fait une fenêtre modale pour forcer le booléen. Donc, 6 problèmes sérieux là-bas!
davidcsb