Jelly Bean DatePickerDialog: ¿hay alguna forma de cancelar?

148

--- Nota para los moderadores: Hoy (15 de julio), he notado que alguien ya enfrentó este problema aquí . Pero no estoy seguro de si es apropiado cerrar esto como un duplicado, ya que creo que proporcioné una explicación mucho mejor del problema. No estoy seguro de si debería editar la otra pregunta y pegar este contenido allí, pero no me siento cómodo cambiando demasiado la pregunta de otra persona. ---

Tengo algo raro aquí.

No creo que el problema dependa de con qué SDK compiles. La versión del sistema operativo del dispositivo es lo que importa.

Problema # 1: inconsistencia por defecto

DatePickerDialogse cambió (?) en Jelly Bean y ahora solo proporciona un botón Listo . Las versiones anteriores incluían un botón Cancelar , y esto puede afectar la experiencia del usuario (inconsistencia, memoria muscular de versiones anteriores de Android).

Replicar: crea un proyecto básico. Pon esto enonCreate:

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

Esperado: unbotón Cancelar para aparecer en el cuadro de diálogo.

Actual: no apareceunbotón Cancelar .

Capturas de pantalla: 4.0.3 (OK) y 4.1.1 (¿posiblemente incorrecto?).

Problema # 2: comportamiento de descarte incorrecto

El cuadro de diálogo llama al oyente al que debería llamar, y luego siempre llama al OnDateSetListeneroyente. La cancelación aún llama al método establecido, y al configurarlo se llama al método dos veces.

Replicar: use el código n. ° 1, pero agregue el código a continuación (verá que esto resuelve el n. ° 1, pero solo visualmente / UI):

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

Esperado:

  • Presionar la tecla ATRÁS o hacer clic fuera del cuadro de diálogo no debería hacer nada .
  • Al presionar "Cancelar" se imprimirá Picker Cancel .
  • ¡Presionando "Set" debería imprimir Picker Set! .

Actual:

  • Al presionar la tecla ATRÁS o al hacer clic fuera del cuadro de diálogo, se imprime el Selector. .
  • Al pulsar "Cancelar" se imprime el selector ¡Cancelar! y luego Picker Set! .
  • ¡Al presionar "Set" se imprime Picker Set! y luego Picker Set! .

Líneas de registro que muestran el comportamiento:

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!

Otras notas y comentarios

  • Envolverlo alrededor de un DatePickerFragmentno importa. Simplifiqué el problema para ti, pero lo probé.
davidcsb
fuente
Felicidades, parece que has encontrado un error en Android. Puedes denunciarlo aquí .
Michael Hampton
1
Informe de errores muy bien escrito. Puedo entenderlo completamente sin tener que probar ejecutar el código.
Cheok Yan Cheng
66
¡Insto a todos a votar este tema! Edición 34833
Bradley
¿No podría simplemente anular la función del botón para actuar como si se hubiera descartado debido a tocar fuera del diálogo?
Karthik Balakrishnan
2
Bug sigue abierto después de 2 años ... increíble.
erdomester

Respuestas:

115

Nota: fijo a partir del Lollipop , fuente aquí . Clase automatizada para uso en clientes (compatible con todas las versiones de Android) actualizada también.

TL; DR: 1-2-3 pasos fáciles para una solución global:

  1. Descargue esta clase.
  2. Implemente OnDateSetListeneren su actividad (o cambie la clase para satisfacer sus necesidades).
  3. Activa el diálogo con este código (en este ejemplo, lo uso dentro de a 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");
    

¡Y eso es todo lo que se necesita! La razón por la que aún mantengo mi respuesta como "aceptada" es porque todavía prefiero mi solución, ya que tiene una huella muy pequeña en el código del cliente, aborda el problema fundamental (se llama al oyente en la clase de marco), funciona bien en los cambios de configuración y enruta la lógica del código a la implementación predeterminada en versiones anteriores de Android no plagadas por este error (ver fuente de la clase).

Respuesta original (guardada por razones históricas y didácticas):

Fuente de error

OK, parece que es realmente un error y alguien más ya lo ha llenado. Edición 34833 .

He descubierto que el problema posiblemente está en DatePickerDialog.java. Donde se lee:

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

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

Supongo que podría haber sido:

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

Ahora, si alguien puede decirme cómo puedo proponer un informe de parche / error a Android, me encantaría hacerlo. Mientras tanto, sugerí una posible solución (simple) como una versión adjunta DatePickerDialog.javaen el Issue allí.

Concepto para evitar el error

Configure el oyente nullen el constructor y cree su propio BUTTON_POSITIVEbotón más adelante . Eso es todo, detalles a continuación.

El problema ocurre porque DatePickerDialog.java, como puede ver en la fuente, llama a una variable global ( mCallBack) que almacena el oyente que se pasó en el constructor:

    /**
 * @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.
}

Por lo tanto, el truco es proporcionar un nulloyente para que se almacene como oyente, y luego rodar su propio conjunto de botones (a continuación se muestra el código original del # 1, actualizado):

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

Ahora funcionará debido a la posible corrección que publiqué anteriormente.

Y dado que DatePickerDialog.javaverifica un a nullcada vez que lee mCallback( desde los días de API 3 / 1.5 parece --- no puede verificar Honeycomb, por supuesto), no activará la excepción. Teniendo en cuenta que Lollipop solucionó el problema, no voy a analizarlo: solo use la implementación predeterminada (cubierta en la clase que proporcioné).

Al principio tenía miedo de no llamar al clearFocus(), pero lo probé aquí y las líneas de registro estaban limpias. Entonces, esa línea que propuse puede no ser necesaria después de todo, pero no lo sé.

Compatibilidad con niveles de API anteriores (editado)

Como señalé en el comentario a continuación, ese era un concepto, y puedes descargar la clase que estoy usando desde mi cuenta de Google Drive . De la forma en que solía, la implementación predeterminada del sistema se usa en versiones no afectadas por el error.

Tomé algunas suposiciones (nombres de botones, etc.) que son adecuadas para mis necesidades porque quería reducir al mínimo el código repetitivo en las clases de clientes. Ejemplo de uso completo:

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
fuente
11
¡Impresionante trabajo en la investigación! Triste que fuera necesario, pero impresionante de todos modos.
CommonsWare
1
¡Muy agradable! He estado golpeando mi cabeza contra la pared sobre este problema que estaba actualizando mis campos de texto y llamando / no llamando las devoluciones de llamada correctas. ¡Gracias! Me pregunto si la solución sugerida es "a prueba de futuro" o si cree que causará problemas cuando se solucione el error.
abarcan el
1
@RomainGuidoux ver respuesta actualizada al final. La clase en el enlace tiene la inteligencia de llamar solo a ese método en jelly bean. En lo que sigue, lo omitirá y usará la implementación predeterminada del sistema, enrutando la llamada del sistema para ondateset a su actividad. Es solo que en jelly bean toma medidas adicionales (evitando el error) antes de enrutar la devolución de llamada, y eso implica llamar a ese método de panal +. Pero de nuevo, solo en JB.
davidcsb
1
Impresionante trabajo aquí. No tengo las palabras para lo ridículo que es este problema.
Bill Phillips
55
¿Qué tal TimePickerDialog? Parece que TimePickerDialog no tiene getTimePicker ()
lokoko
16

Voy a agregar mi propio riff en la solución que David Cesarino publicó, en caso de que no esté usando Fragments, y quiera una forma fácil de solucionarlo en todas las versiones (2.1 a 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
fuente
Solo recordando: si está cableando los nombres de los botones para aceptar y cancelar, probablemente sea mejor usar el estándar android.R.string.oky los android.R.string.cancelcampos, en lugar de los propios. Y gracias por la respuesta.
davidcsb
Ah, bien, no me he dado cuenta de que proporcionaron OK y texto de cancelación. ¡Gracias!
dmon
obtener una excepción de puntero nulo en super (contexto, tema, nulo, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore
Errr ... ¿estás pasando un nulo dateToShow? El otro nulo allí es en realidad la "solución", por lo que debería estar allí. ¿En qué versión estás?
dmon
¿Me importpuede decir para qué tenía Field? Tengo 6 opciones y ninguna funcionó.
Adil Malik
8

Hasta que se solucione el error, sugiero no usar DatePickerDialog o TimePickerDialog. Utilice AlertDialog personalizado con el widget TimePicker / DatePicker;

Cambiar TimePickerDialog con;

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

Cambiar DatePickerDialog con;

    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
fuente
4

El de TimePicker basado en la solución de David Cesarino, "TL; DR: 1-2-3 pasos fáciles para una solución global"

TimePickerDialog no proporciona la funcionalidad como DatePickerDialog.getDatePicker. Por lo tanto, se debe proporcionar el escucha OnTimeSetListener . Solo para mantener la similitud con la solución de DatePicker, he mantenido el viejo concepto mListener. Puede cambiarlo si lo necesita.

Llamar y escuchar es lo mismo que la solución original. Solo incluye

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

ampliar clase de padres,

... implements OnDateSetListener, OnTimeSetListener

Implementar

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

ejemplo llamando

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

(Actualizado para manejar cancelar)

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
fuente
No me funciona cuando lo intento en s3 API 18
AbdelHady
3

En caso de que alguien quiera una solución rápida, aquí está el código que utilicé:

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

}

Donde layout.date_picker_view es un recurso de diseño simple con un DatePicker, ya que es el único elemento:

<!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"/>

Aquí está el tutorial completo en caso de que esté interesado.

daniel_c05
fuente
Esto funcionó para mí maravillosamente. ¡Fácil de entender, fácil de implementar! Probado en 4.4
erdomester
3

Mi solución simple Cuando desee que se active nuevamente, simplemente ejecute "resetFired" (por ejemplo, cuando abra el cuadro de diálogo nuevamente).

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
fuente
@AbdelHady, la edición de su código era incorrecta, pero la edité para que quede más clara. No es necesario agregar un método "isJellyBeanOrAbove ()", no hay ventaja, solo agrega complejidad innecesaria. Llamar a resetFired es barato.
Daniel Ryan
el código aquí es incorrecto, porque onDateSetse llamará una vez cuando se cierre el cuadro de diálogo por cualquier medio, y si eso significaba establecer la hora, se disparará nuevamente, por lo que debemos capturar la segunda llamada, no la primera como lo hiciste,
AbdelHady
En cuanto a isJellyBeanOrAbove(), las versiones inferiores a Jellybean no tienen el error del que se trata toda esta pregunta, y teniendo en cuenta que queremos capturar la segunda llamada, el código no se ejecutará a menos que hagamos esta comprobación, créanme, he intentado mi código en emuladores y dispositivos reales (con diferentes versiones) varias veces y funciona de
maravilla
No estoy seguro de si eso merece un voto negativo. Publiqué esto hace 2 años, esto ha estado en nuestra aplicación comercial desde entonces funcionando bien. Ningún error reportado por nuestros equipos de control de calidad o nuestros miles de usuarios. Esto está destinado a ser una respuesta simple que las personas pueden extender. Llame a "resetFired" cuando desee que se active nuevamente.
Daniel Ryan
En nuestra aplicación no se necesita "isJellyBeanOrAbove". La aplicación funcionará bien en todas las versiones si llama "resetFired" en las áreas correctas.
Daniel Ryan
3

De acuerdo con la brillante respuesta de Ankur Chaudhary sobre un TimePickerDialogproblema similar , si verificamos en el interior onDateSetsi la vista dada isShown()o no, resolverá todo el problema con el mínimo esfuerzo, sin necesidad de extender el selector o buscar algunas banderas horribles alrededor del código o incluso buscando la versión del sistema operativo, simplemente haga lo siguiente:

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

y, por supuesto, se puede hacer lo onTimeSetmismo según la respuesta de Ankur

AbdelHady
fuente
1
¡La mejor respuesta de todas las demás!
hiew1
2

La forma en que manejé esta situación fue usando una bandera y anulando los métodos onCancel y onDismiss.

Se llama a onCancel solo cuando el usuario toca fuera del diálogo o el botón Atrás. onDismiss siempre se llama

Establecer una marca en el método onCancel puede ayudar a filtrar en el método onDismiss la intención del usuario: cancelar la acción o la acción realizada. Debajo de un código que muestra la idea.

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
fuente
1

Hay una solución muy simple, si su aplicación no utiliza la barra de acción. Tenga en cuenta, por cierto, que algunas aplicaciones dependen de esta funcionalidad para funcionar, porque cancelar fuera del selector de fecha tiene un significado especial (por ejemplo, borra el campo de fecha en una cadena vacía, que para algunas aplicaciones es un tipo de entrada válido y significativo ) y el uso de indicadores booleanos para evitar que la fecha se establezca dos veces en Aceptar no lo ayudará en este caso.

Re. la solución real, no tiene que crear botones nuevos ni su propio diálogo. El punto es ser compatible tanto con las versiones anteriores de Android, las con errores (4. ) como con las futuras, aunque, por supuesto, es imposible estar seguro de esto último. Tenga en cuenta que en Android 2. , onStop () para android.app.Dialog no hace nada en absoluto, y en 4. * hace mActionBar.setShowHideAnimationEnabled (falso), que es importante solo si su aplicación tiene una barra de acción. El onStop () en DatePickerDialog, que hereda de Dialog, solo aporta mDatePicker.clearFocus () (a partir de la última solución para las fuentes de Android 4.3), lo que no parece esencial.

Por lo tanto, reemplazar onStop () con un método que no hace nada debería, en muchos casos, arreglar su aplicación y garantizar que seguirá siendo así en el futuro previsible. Por lo tanto, simplemente extienda la clase DatePickerDialog con la suya y anule onStop () con un método ficticio. También deberá proporcionar uno o dos constructores, según sus requisitos. Tenga en cuenta también que no se debe tener la tentación de intentar exagerar esta solución, por ejemplo, intentando hacer algo con la barra de actividad directamente, ya que esto limitaría su compatibilidad con las últimas versiones de Android solamente. También tenga en cuenta que sería bueno poder llamar al súper para onStop () de DatePicker porque el error solo está en onStop () en DatePickerDialog, pero no en la súper clase de DatePickerDialog. Sin embargo, esto requeriría que llame a super.super.onStop () desde su clase personalizada, que Java no le permitirá hacer, ya que va en contra de la filosofía de encapsulación :) A continuación se muestra mi pequeña clase que usé para verificar DatePickerDialog. Espero que este comentario sea útil para alguien. 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
fuente
Incluso si se usa una ActionBar, esta podría ser una solución aceptable si nunca oculta / muestra la ActionBar. Para hacer las cosas más seguras, puede anular onStart para no hacer nada también (entonces las llamadas para la animación se desengancharán de manera segura). E incluso si lo oculta / muestra, es solo la animación la que está deshabilitada.
Daniel
0


Prueba los siguientes conceptos.

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


el método onDateSet () llama dos veces (si está ingresando en emulator.it llama dos veces. Si está usando un dispositivo real, entonces llamará correctamente una sola vez. Si está usando el emulador, use el contador. Si está trabajando en un dispositivo real, entonces ignore la variable del contador. Para un dispositivo real, funciona para mí)
cuando el usuario hace clic en el botón en DatePickerDialog.
para esto, debe mantener un valor de contador y no hacer nada cuando el mothod llame por primera vez y realizar la operación cuando el método llame por segunda vez.
Consulte los siguientes fragmentos de codificación

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



Para cancelar el dilalog de datepicker, funciona para mí. Para el emulador no funciona

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


Me funciona para un dispositivo real. Pero para el emulador no funciona correctamente. Creo que es un error del emulador de Android.

SIVAKUMAR.J
fuente
0

Una solución simple sería usar un booleano para omitir la segunda ejecución

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
fuente
Una vez más, tal como le dije al otro chico antes que usted, esto no resuelve el error. No quiero parecer grosero, pero ustedes deberían probar su propia solución antes de publicar aquí ... solo para probar mi punto, use su código y deje que el usuario presione para cancelar el diálogo y ver a qué me refiero. La causa del error es onStopllamar al método cuando no debería ... disparar dos veces es una consecuencia del error.
davidcsb
Si no estaba lo suficientemente claro, déjame ser: presionando hacia atrás para cancelar las llamadas de diálogo onDateSet. Por lo tanto, roto.
davidcsb
Gracias por mostrar el error. Edito la respuesta para que funcione con el botón Atrás. Su respuesta está funcionando pero DatePicker dp = picker.getDatePicker (); no funciona con TimePickers ya que no se agrega el método getTimePicker (). Así que esta sería una respuesta válida
chamikaw
¡¿Cómo estamos tratando de omitir la segunda ejecución? !!, lo he intentado varias veces, cuando se cierra el diálogo por cualquier medio onDateSet, se llama una vez, pero al elegir "hecho" o "establecer", se llama dos veces. Por lo tanto, debemos omitir solo el primero, por lo que si se llama dos veces y solo entonces tenemos la fecha correcta
AbdelHady
0

Puede anular onCancel () y usar setOnDismissListener () para detectar acciones negativas del usuario. Y con un DatePickerDialog.BUTTON_POSITIVE sabes que el usuario quiere establecer una nueva fecha.

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

luego verifique setDate:

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

Aquí está mi clase de solución para DatePickerDialog en el botón cancelar, así como abandonarla por el botón Atrás. Copie y use al estilo de DatePickerDialog (debido a que el oyente tiene estado, debemos crear una nueva instancia cuando se usa, de lo contrario, se requiere más código para que funcione)

Utilizar:

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

Clase:

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
fuente
0

Estoy usando selectores de fecha, hora y número. Los selectores de números llaman onValueChanged cada vez que el usuario selecciona un número, antes de que se descarte el selector, por lo que ya tenía una estructura como esta para hacer algo con el valor solo cuando se descarta el selector:

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

Extendí esto para establecer onClickListeners personalizados para mis botones, con un argumento para ver en qué botón se hizo clic. Ahora puedo verificar qué botón se tocó antes de establecer mi valor final:

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

Y luego extendí eso para trabajar con los tipos de fecha y hora para los recolectores de fecha y hora, así como el tipo int para los recolectores de números.

Publiqué esto porque pensé que era más simple que algunas de las soluciones anteriores, pero ahora que he incluido todo el código, ¡supongo que no es mucho más simple! Pero encaja muy bien en la estructura que ya tenía.

Actualización para Lollipop: Aparentemente, este error no ocurre en todos los dispositivos Android 4.1-4.4, porque recibí algunos informes de usuarios cuyos selectores de fecha y hora no estaban llamando a las devoluciones de llamada onDateSet y onTimeSet. Y el error se corrigió oficialmente en Android 5.0. Mi enfoque solo funcionó en dispositivos donde el error está presente, porque mis botones personalizados no llamaron al controlador onClick del diálogo, que es el único lugar donde se llama a onDateSet y onTimeSet cuando el error no está presente. Actualicé mi código anterior para llamar al diálogo onClick, por lo que ahora funciona si el error está presente o no.

arlomedia
fuente
0

Me gustó la respuesta de David Cesarino anterior, pero quería algo que reemplazara el cuadro de diálogo roto y funcionara en cualquier cuadro de diálogo que pudiera faltar cancelar / tener un comportamiento de cancelación incorrecto. Aquí hay clases derivadas para DatePickerDialog / TimePickerDialog que deberían funcionar como soltar reemplazos. Estas no son vistas personalizadas. Utiliza el cuadro de diálogo del sistema, pero solo cambia el comportamiento del botón cancelar / retroceder para que funcione como se espera.

Esto debería funcionar en API nivel 3 y superior. Entonces, básicamente cualquier versión de Android (lo probé específicamente en jellybean y lollipop).

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));
    }
}
pegado
fuente
Esto es bastante similar a la respuesta anterior de The Sea.
stuckj
0

Mi versión de trabajo con ClearButton usando expresiones 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
fuente
0

Para TimePickerDialog, la solución puede ser la siguiente:

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

Delego todos los eventos al contenedor KitKatSetTimeListener, y solo vuelvo a activar OnTimeSetListener original en caso de que se haga clic en BUTTON_POSITIVE.

Southerton
fuente
0

Después de probar algunas de las sugerencias publicadas aquí, personalmente creo que esta solución es la más simple. Paso "nulo" como mi oyente en el constructor DatePickerDialog, y luego cuando hago clic en el botón "Aceptar" llamo a mi 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
fuente
-1

Sé que esta publicación ha estado aquí durante casi un año, pero pensé que debería publicar mis hallazgos. Todavía puede mantener el oyente (en lugar de configurarlo para reflexionar) y aún así tener este trabajo como se esperaba. La clave es establecer implícitamente los botones "Aceptar" o (y) los botones "cancelar". Lo probé y funciona agradecido para mí. El oyente no es despedido dos veces.

Mira este ejemplo,

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();
}
Cocodrilo
fuente
Claro y simple, no funciona de la manera que usted lo dice. timePickerListenertodavía se llama independientemente de lo que haga en su diálogo. Ni siquiera necesito probar para saber eso, solo necesita mirar las fuentes : si no lo configura null, tryNotifyTimeSet()llamará al oyente onTimeSet()tanto en su onClick()como en onStop().
davidcsb
En otras palabras, no permita que la clase nativa mantenga una referencia a su oyente (es decir, pasarla al constructor). La forma en que manejas los botones es irrelevante.
davidcsb
Hola David, no probaste esto y asumiste que no funcionaría. Este es el código exacto que estoy usando actualmente en mi aplicación y funciona como un campeón.
Cocodrilo
1) Nunca dije que no funcionó. Dije que no funcionó como dijiste. Por favor lea de nuevo. 2) usted violó LSP y SRP, desperdiciando horas-hombre para la falta que cambiar toda su totalidad la lógica de cliente que no necesita ningún cambio, para empezar. 3) su respuesta aborda la pregunta, sí, de lo contrario lo marcaría para su eliminación como "no es una respuesta", pero 4) su respuesta sigue siendo (lo siento) muy ineficiente y no aborda el problema fundamental del diseño (el oyente llamó ), de ahí solo el voto negativo. 6) desactivó y lo convirtió en una ventana modal para forzar el valor booleano. Entonces, ¡6 problemas serios allí!
davidcsb