--- 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
DatePickerDialog
se 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 OnDateSetListener
oyente. 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
DatePickerFragment
no importa. Simplifiqué el problema para ti, pero lo probé.
Respuestas:
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:
OnDateSetListener
en su actividad (o cambie la clase para satisfacer sus necesidades).Activa el diálogo con este código (en este ejemplo, lo uso dentro de a
Fragment
):¡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:Supongo que podría haber sido:
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.java
en el Issue allí.Concepto para evitar el error
Configure el oyente
null
en el constructor y cree su propioBUTTON_POSITIVE
botó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:Por lo tanto, el truco es proporcionar un
null
oyente 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):Ahora funcionará debido a la posible corrección que publiqué anteriormente.
Y dado que
DatePickerDialog.java
verifica un anull
cada vez que leemCallback
( 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:
fuente
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):
fuente
android.R.string.ok
y losandroid.R.string.cancel
campos, en lugar de los propios. Y gracias por la respuesta.dateToShow
? El otro nulo allí es en realidad la "solución", por lo que debería estar allí. ¿En qué versión estás?import
puede decir para qué teníaField
? Tengo 6 opciones y ninguna funcionó.Hasta que se solucione el error, sugiero no usar DatePickerDialog o TimePickerDialog. Utilice AlertDialog personalizado con el widget TimePicker / DatePicker;
Cambiar TimePickerDialog con;
Cambiar DatePickerDialog con;
fuente
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
ampliar clase de padres,
Implementar
ejemplo llamando
(Actualizado para manejar cancelar)
fuente
En caso de que alguien quiera una solución rápida, aquí está el código que utilicé:
}
Donde layout.date_picker_view es un recurso de diseño simple con un DatePicker, ya que es el único elemento:
Aquí está el tutorial completo en caso de que esté interesado.
fuente
Mi solución simple Cuando desee que se active nuevamente, simplemente ejecute "resetFired" (por ejemplo, cuando abra el cuadro de diálogo nuevamente).
fuente
onDateSet
se 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,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 deDe acuerdo con la brillante respuesta de Ankur Chaudhary sobre un
TimePickerDialog
problema similar , si verificamos en el interioronDateSet
si la vista dadaisShown()
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:y, por supuesto, se puede hacer lo
onTimeSet
mismo según la respuesta de Ankurfuente
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.
fuente
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
}
fuente
Prueba los siguientes conceptos.
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
Para cancelar el dilalog de datepicker, funciona para mí. Para el emulador no funciona
Me funciona para un dispositivo real. Pero para el emulador no funciona correctamente. Creo que es un error del emulador de Android.
fuente
Una solución simple sería usar un booleano para omitir la segunda ejecución
fuente
onStop
llamar al método cuando no debería ... disparar dos veces es una consecuencia del error.onDateSet
. Por lo tanto, roto.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 correctaPuede 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.
luego verifique setDate:
fuente
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:
Clase:
}
fuente
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:
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:
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.
fuente
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:
TimePickerDialog:
fuente
Mi versión de trabajo con ClearButton usando expresiones Lambda:
fuente
Para TimePickerDialog, la solución puede ser la siguiente:
Delego todos los eventos al contenedor KitKatSetTimeListener, y solo vuelvo a activar OnTimeSetListener original en caso de que se haga clic en BUTTON_POSITIVE.
fuente
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:
fuente
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,
fuente
timePickerListener
todaví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 configuranull
,tryNotifyTimeSet()
llamará al oyenteonTimeSet()
tanto en suonClick()
como enonStop()
.