Como ya has de suponer, los diálogos son elementos en la interfaz de Android que se sobreponen a las actividades para exigir la confirmación de una acción o el ingreso de datos.
Se caracterizan por interrumpir una tarea del usuario, alterando el foco de la aplicación e invitándolo de forma intrusiva a que vea información, decida ante una circunstancia crítica o especifique datos.
Por ejemplo…
Puedes usar un diálogo para recordar la importancia de borrar un elemento de la interfaz antes de continuar con la tarea.
También puedes crear un layout personalizado para el login del usuario al aplicativo.
Son vitales para seleccionar una preferencia:
O incluso si deseas crear formularios para la edición o inserción de datos:
Como ves, existen varios tipos de propósito para los diálogos. Así que el objetivo de este artículo es explicar cómo usar cada uno de las categorías existentes de modo que toda situación quede aclarada.
Comenzaremos con el uso de la clase DialogFragment
para instanciar los diálogos. Aunque Dialog
es la clase que genera la interfaz, DialogFragment
es quien permite controlar los eventos de forma fluida. Por ello no se recomienda crear objetos de clase base.
Luego estudiaremos la subclase AlertDialog
para construir diálogos con título, cuerpo y botones de acción.
A través de ella generaremos diálogos de confirmación, diálogos de selección múltiple, date picker, time pickers y diálogos personalizados.
Cada uno de los ejemplos ha sido incluido dentro de una aplicación llamada Dialogpers:
Para descargar el proyecto en Android Studio completo sigue las instrucciones:
[sociallocker id=»7121″]
Crear Diálogos De Alerta Con La Clase AlertDialog En Android
Un diálogo de alerta es aquel que está diseñado para mostrar un título, un mensaje o cuerpo y hasta 3 botones de confirmación en su zona de acción.
En los botones podremos ver acciones familiares como lo son la aceptación (Botón positivo) que determina que el usuario está de acuerdo con la acción. La cancelación (Botón negativo) para evitar la realización de una acción, y la neutralidad (Botón neutro) para determinar que aún no se está listo para proseguir o cancelar la acción.
Un diálogo de alerta se representa con la clase AlertDialog
. En su construcción debes usar un elemento auxiliar llamado Builder, el cual te ayudará a definir las partes del diálogo con gran facilidad y sus eventos de respuesta para los botones.
Builder te permitirá fabricar las características del diálogo a través de métodos set*()
. Los más frecuentes son:
setTitle()
: Asigna una cadena al título del diálogo.setMessage()
: Setea el mensaje que deseas transmitir en el contenido.setPossitiveButton()
: Crea una instancia del botón de confirmación. El primer parámetro que recibe es el texto del botón y el segundo una escuchaOnClickListener
para determinar qué acción se tomará al ser presionado.setNegativeButton()
: Hace exactamente lo mismo quesetPossitiveButton()
pero para el botón de decisión negativa.setNeutralButton()
: Genera una escucha para el botón neutral. Al igual que los dos métodos anteriores, recibe el nombre y la escucha.
Con estos métodos ya podemos definir un Builder
que implemente un diálogo simple con título, mensaje y dos botones…
/** * Crea un diálogo de alerta sencillo * @return Nuevo diálogo */ public AlertDialog createSimpleDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Titulo") .setMessage("El Mensaje para el usuario") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onPossitiveButtonClick(); } }) .setNegativeButton("CANCELAR", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onNegativeButtonClick(); } }); return builder.create(); }
Luego de setear las características, materializas el diálogo a través de create()
. Puedes crear un método que retorne en la instancia ya creada para organizar tu código.
Crear Un AlertDialog Con Lista
También es posible crear un diálogo con una lista de elementos que permita algún tipo de selección por parte del usuario. Donde es posible crear una lista de elementos comunes, una lista de radio buttons o una lista de checkeo múltiple.
Crear Dialogo con lista simple
Si quieres crear una lista simple usa el método setItems()
en vez de setMessage()
. Este método recibe un arreglo de cadenas que permita definir las etiquetas de los elementos y una escucha OnClickListener
para decidir que sucede…
/** * Crea un Diálogo con una lista de selección simple * * @return La instancia del diálogo */ public AlertDialog createSingleListDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final CharSequence[] items = new CharSequence[3]; items[0] = "Naranja"; items[1] = "Mango"; items[2] = "Banano"; builder.setTitle("Diálogo De Lista") .setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText( getActivity(), "Seleccionaste:" + items[which], Toast.LENGTH_SHORT) .show(); } }); return builder.create(); }
Para saber que ítem fue presionado consulta el parámetro which
. Este te indicará el índice seleccionado del array.
Crear dialogo con lista de radio buttons
Ahora creemos una lista con radio buttons. Esta vez usaremos el método setSingleChoiceItems()
, el cual también recibe un array de caracteres, el índice del radio seleccionado y una escucha para el manejo de los eventos:
/** * Crea un diálogo con una lista de radios * * @return Diálogo */ public AlertDialog createRadioListDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final CharSequence[] items = new CharSequence[3]; items[0] = "Soltero/a"; items[1] = "Casado/a"; items[2] = "Divorciado/a"; builder.setTitle("Estado Civil") .setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText( getActivity(), "Seleccionaste: " + items[which], Toast.LENGTH_SHORT) .show(); } }); return builder.create(); }
Con ello se tendría un resultado así:
Crear dialogo con lista de múltiple selección de checkboxes
La implementación de un diálogo con checklists requiere el uso del método setMultipleChoiceItems()
. Al igual que los otros tipos de lista, este recibe un array con las etiquetas de cada elemento y una escucha para los eventos. Adicionalmente tiene un parámetro para indicar que checkboxes están seleccionados.
/** * Crea un diálogo con una lista de checkboxes * de selección multiple * * @return Diálogo */ public AlertDialog createMultipleListDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final ArrayList itemsSeleccionados = new ArrayList(); CharSequence[] items = new CharSequence[3]; items[0] = "Desarrollo Android"; items[1] = "Diseño De Bases De Datos"; items[2] = "Pruebas Unitarias"; builder.setTitle("Intereses") .setMultiChoiceItems(items, null, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { if (isChecked) { // Guardar indice seleccionado itemsSeleccionados.add(which); Toast.makeText( getActivity(), "Checks seleccionados:(" + itemsSeleccionados.size() + ")", Toast.LENGTH_SHORT) .show(); } else if (itemsSeleccionados.contains(which)) { // Remover indice sin selección itemsSeleccionados.remove(Integer.valueOf(which)); } } }); return builder.create(); }
En este caso puedes tomar la selección de varias casillas. Para contarlas usa un pequeño array para añadir aquellos que estén marcados (esto lo sabes con el parámetro isChecked
). De lo contrario retíralos del array cuando no lo estén.
Crear un diálogo personalizado
La implementación de un dialogo personalizado requiere la creación previa de un layout que ocupe el contenido del dialogo.
Normalmente puedes usar algunas de las medidas estándar de diseño Material Design que se muestra en el apartado de diálogos:
Como ves, el contenido se ve muy bien distribuido si usas 24dp para el padding de contorno. También se usa 20dp entre el título y el contenido.
Desde el punto de vista técnico, se requiere que la definición xml sea inflada a través del método inflate()
del componente LayoutInflater
. Esto lo puede hacer al obtener una instancia con getLayoutInflater()
.
Luego seteas el view construido, con el método setView()
de AlertDialog.Builder
.
Por ejemplo…
El siguiente layout muestra un formulario común donde se pide el inicio de sesión de nuestro usuario o la creación de una cuenta. Como ves, el contenido tiene distribución vertical de los campos y botones coloridos en distintas posiciones.
dialog_signin.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:padding="@dimen/dialog_body"> <TextView android:id="@+id/info_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:gravity="center" android:paddingBottom="@dimen/padding_between" android:paddingTop="@dimen/padding_between" android:text="@string/info_text" android:textAppearance="?android:attr/textAppearanceSmall" /> <Button android:id="@+id/crear_boton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/info_text" android:layout_centerHorizontal="true" android:paddingBottom="@dimen/button_padding" android:paddingTop="@dimen/button_padding" android:text="@string/crear_boton" android:textColor="@android:color/white" /> <View android:id="@+id/divider" android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@+id/crear_boton" android:layout_marginBottom="@dimen/padding_between" android:layout_marginTop="@dimen/padding_between" android:background="#C8C9CB" /> <EditText android:id="@+id/nombre_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/divider" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/normal_padding" android:background="@drawable/edit_text_border" android:ems="10" android:hint="@string/nombre_input" android:inputType="textPersonName" android:padding="@dimen/edit_text_padding" android:textAppearance="?android:attr/textAppearanceSmall" /> <EditText android:id="@+id/contrasena_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/nombre_input" android:layout_centerHorizontal="true" android:layout_marginBottom="@dimen/padding_between" android:layout_marginTop="@dimen/normal_padding" android:background="@drawable/edit_text_border" android:ems="10" android:hint="@string/contrasena_input" android:inputType="textPassword" android:padding="@dimen/edit_text_padding" android:textAppearance="?android:attr/textAppearanceSmall" /> <CheckBox android:id="@+id/recordar_check" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/contrasena_input" android:layout_alignStart="@+id/contrasena_input" android:layout_below="@+id/contrasena_input" android:checked="false" android:text="@string/recordar_check" /> <TextView android:id="@+id/olvidar_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignEnd="@+id/contrasena_input" android:layout_alignRight="@+id/contrasena_input" android:layout_below="@+id/recordar_check" android:paddingTop="@dimen/padding_between" android:text="@string/olvidar_text" android:textAppearance="?android:attr/textAppearanceSmall" /> <Button android:id="@+id/entrar_boton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:paddingBottom="@dimen/button_padding" android:paddingTop="@dimen/button_padding" android:text="@string/entrar_boton" android:textColor="@android:color/white" /> </RelativeLayout>
Para aplicar este contenido sobre nuestro diálogo debemos llevar a cabo los pasos anteriormente descritos…
/** * Crea un diálogo con personalizado para comportarse * como formulario de login * * @return Diálogo */ public AlertDialog createLoginDialogo() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = getActivity().getLayoutInflater(); View v = inflater.inflate(R.layout.dialog_signin, null); builder.setView(v); Button signup = (Button) v.findViewById(R.id.crear_boton); Button signin = (Button) v.findViewById(R.id.entrar_boton); signup.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // Crear Cuenta... dismiss(); } } ); signin.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // Loguear... dismiss(); } } ); return builder.create(); }
Un proceso sencillo. Cada botón tendrá su evento correspondiente a las acciones deseadas (crear cuenta y loguear).
Este ejemplo no ha sido ajustado para landscape. Sin embargo, es importante que optimices tus diálogos personalizados para esta variación.
Creación De Diálogos Con La Clase DialogFragment
DialogFragment
es un fragmento que contiene un dialogo en su layout por defecto. Usar esta clase permite mantener optimizados los estados del diálogo, lo que permite controlar el ciclo de vida de forma oportuna.
Si deseas soportar el uso de DialogFragment
hasta la versión 1.6 (API 4) recuerda usar la librería de soporte con la siguiente directiva:
import android.support.v4.app.DialogFragment;
Para implementarlo simplemente crea una clase y extiéndela de DialogFragment
. Luego sobrescribe el método onCreateDialog()
para que retorne en la instancia de alguna subclase de Dialog
.
Por ejemplo…
import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; /** * Fragmento con diálogo básico */ public class SimpleDialog extends DialogFragment { public SimpleDialog() { } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return createSimpleDialog(); } /** * Crea un diálogo de alerta sencillo * @return Nuevo diálogo */ public AlertDialog createSimpleDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Titulo") .setMessage("El Mensaje para el usuario") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Acciones } }) .setNegativeButton("CANCELAR", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Acciones } }); return builder.create(); } }
Pero… ¿Cómo mostrar el diálogo?
Debes crear una nueva instancia del DialogFragment
en la actividad o fragmento donde desees que se proyecte. A continuación llamas al método show()
, el cual se encarga de mostrar el diálogo contenido en el fragmento.
// En algún lugar de tu actividad new SimpleDialog().show(getSupportFragmentManager(), "SimpleDialog");
show()
recibe la instancia del FragmentManager
y la etiqueta descriptiva del diálogo para su identificación. Para conseguir el fragment manager simplemente usa el método getSupportFragmentManager()
. Si lo vas a mostrar en un fragmento, usa solo getFragmentManager()
.
Enviar eventos desde un DialogFragment hacia su Actividad Contenedora
Si deseas pasar los eventos que ocurren en el diálogo hacia una actividad debes acudir a una interfaz de comunicación que permita compartir los métodos de acción.
A continuación te dejo los pasos a seguir:
Paso #1: Declara una interfaz dentro del DialogFragment
. La declaración de la interfaz debe tener definido un método por cada acción que reciba el diálogo.
Si deseas procesar en la actividad los métodos del botón positivo y el negativo, entonces creas una interfaz con dos métodos respectivos:
public interface OnSimpleDialogListener { void onPossitiveButtonClick();// Eventos Botón Positivo void onNegativeButtonClick();// Eventos Botón Negativo }
Paso #2: Declarar un atributo del tipo de la interfaz para conseguir la instancia directa de la actividad.
// Interfaz de comunicación OnSimpleDialogListener listener;
Paso #3: Comprobar que la actividad ha implementado la interfaz podemos usar el método onAttach()
. Recuerda que este recibe la instancia de la actividad contenedora del fragmento. Simplemente asignas la actividad a la instancia de la interfaz.
Si este proceso no es posible, entonces lanzas una excepción del tipo ClassCastException
.
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { listener = (OnSimpleDialogListener) activity; } catch (ClassCastException e) { throw new ClassCastException( activity.toString() + " no implementó OnSimpleDialogListener"); } }
Paso #4: Invocar los métodos de la interfaz en las secciones del diálogo donde deseamos implicar a la actividad.
.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onPossitiveButtonClick(); } }) .setNegativeButton("CANCELAR", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onNegativeButtonClick(); } });
Paso #5: Implementar sobre la actividad contenedora, la interfaz declarada en el fragmento.
public class MainActivity extends AppCompatActivity implements OnSimpleDialogListener{ //... }
Paso #6: Sobrescribir dentro de la actividad los métodos de la interfaz, con las acciones que requieras.
@Override public void onPossitiveButtonClick() { // Acciones } @Override public void onNegativeButtonClick() { // Acciones }
¿Cómo Comunicar Un DialogFragment Con Un Fragment?
Una de las formas para comunicar un fragmento de diálogo y un fragmento común, es tomar la actividad contenedora como puente entre ambos. Es decir, enviar los eventos desde el DialogFragment
hacia la actividad y luego desde la actividad hacia el Fragment
.
Esta convención es recomendada, ya que en la documentación oficial de Android Developers se menciona que no se deben comunicar dos fragmentos directamente. Quizás porque las esperanzas de vida de los fragmentos pueden variar, así que es mejor asegurar su independencia de transmisión.
Veamos este ejemplo para entender mejor…
Supón que tienes un fragmento sencillo con un text view y un botón. La idea es que al presionar el botón, se despliegue el diálogo de lista simple que hemos construido con anterioridad.
Y una vez seleccionada la opción, se actualizará el texto a través de la actividad.
Lo primero es crear un DialogFragment
que tenga tres opciones. Para este ejemplo usaré el mensaje “¿Qué fruta te gusta más?» y añadiré tres opciones: “Naranja”, “Mango” y “Banano”:
import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; /** * Fragmento con diálogo de lista simple */ public class SimpleListDialog extends DialogFragment { public interface OnSetTitleListener{ void setTitle(String title); } OnSetTitleListener listener; public SimpleListDialog() { } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return createSingleListDialog(); } /** * Crea un Diálogo con una lista de selección simple * * @return La instancia del diálogo */ public AlertDialog createSingleListDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final CharSequence[] items = new CharSequence[3]; items[0] = "Naranja"; items[1] = "Mango"; items[2] = "Banano"; builder.setTitle("¿Qué fruta te gusta más?") .setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.setTitle((String) items[which]); } }); return builder.create(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { listener = (OnSetTitleListener) activity; } catch (ClassCastException e) { throw new ClassCastException( activity.toString() + " no implementó OnSetTitleListener"); } } }
Como ves, la escucha de comunicación onSetTitleListener
provee un método llamado setTitle()
para gestionar la selección del usuario. Justo en el momento que el usuario presiona (método onClick()
), se envía hacia la actividad la cadena contenida en items[which]
.
Ahora en la actividad principal, se implementará la interfaz y se sobrescribirá el método de implementación:
public class MainActivity extends AppCompatActivity implements SimpleListDialog.OnSetTitleListener { // Dentro de una actividad... @Override public void setTitle(String title) { FragmentoObjetivo fragment = getSupportFragmentManager().findFragmentByTag("FragmentoObjetivo"); if(fragment!=null){ fragment.setTitle(title); }else{ // Reporta el error... } } }
Al recibir el evento, debes llamar a la instancia del fragmento al que necesitas pasar los resultados. Para ello llamas al administrador de fragmentos y usas el método findFragmentById()
o findFragmentByTag()
.
El primero obtiene la instancia del fragmento con el id pasado como parámetro y el segundo obtiene el fragmento que contiene la etiqueta especificada.
Con eso ya puedes llamar el método hipotético setTitle()
del fragmento y comunicar la fruta preferida del usuario en el text view.
Crear Un Diálogo Con DatePicker
Un DatePicker
o selector de fecha es un view elaborado para permitir al usuario seleccionar una fecha configurable. Donde puede elegir el año, el mes y el día respectivamente.
Normalmente se puede setear de forma estática sobre un layout. Si deseas encontrarlo en Android Studio, ve a la pestaña de diseño, luego a la paleta y selecciona «DatePicker» en la categoría “Date & Time”:
Pero nuestro objetivo no es usarlo de esa forma. Lo que necesitamos es crear un diálogo que se sobreponga ante el layout para no consumir espacio.
Esto se logra a través de la subclase DatePickerDialog
, la cual representa un diálogo con un selector de fechas en su interior.
Al igual que como hemos hecho hasta ahora con los demás diálogos, creamos un DialogFragment
para que actué como envoltura. Para ello se retorna en una instancia de DatePickerDialog
en el método onCreateDialog()
.
DateDialog.java
import android.annotation.TargetApi; import android.app.DatePickerDialog; import android.app.Dialog; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import java.util.Calendar; /** * Fragmento con un diálogo de elección de fechas */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class DateDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Obtener fecha actual 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); // Retornar en nueva instancia del dialogo selector de fecha return new DatePickerDialog( getActivity(), (DatePickerDialog.OnDateSetListener) getActivity(), year, month, day); } }
Para la construcción solo se requiere añadir la fecha por defecto con que se mostrará el diálogo. La clase Calendar
puede ayudarte a obtener la fecha actual por si deseas usarla.
Ahora, si quieres leer los eventos de selección de fecha, usa la interfaz DatePickerDialog.onDateSetListener
sobre la actividad contenedora. Esta provee el método callback onDateSet()
para la determinar las acciones que se realizarán con la fecha seleccionada.
Con ello puedes sobrescribir el método en tu actividad y realizar la acción que desees. En el siguiente ejemplo despliego la fecha elegida en un Toast
:
@Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { Toast.makeText( this, "Fecha: " + year + "-" + monthOfYear + "-" + dayOfMonth, Toast.LENGTH_LONG) .show(); }
Crear Diálogo Con Un TimePicker En Su Interior
El TimePicker
o selector de tiempo es muy similar al DatePicker
. Este permite seleccionar el tiempo a través de una configuración de las horas y minutos.
Si deseas añadirlo a través del editor de Android Studio debes dirigirte a la paleta, encontrar la categoría “Date & Time” y luego arrastrar el objeto «TimePicker»:
Ahora, para añadirlo a un diálogo haremos el mismo procedimiento anterior. Usaremos la clase análoga TimePickerDialog
con un DialogFragment
.
TimeDialog.java
import android.annotation.TargetApi; import android.app.Dialog; import android.app.TimePickerDialog; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.text.format.DateFormat; import java.util.Calendar; /** * Fragmento con un diálogo de elección de tiempos */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class TimeDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Iniciar con el tiempo actual final Calendar c = Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); // Retornar en nueva instancia del dialogo selector de tiempo return new TimePickerDialog( getActivity(), (TimePickerDialog.OnTimeSetListener) getActivity(), hour, minute, DateFormat.is24HourFormat(getActivity())); } }
El TimePickerDialog
se construye con el tiempo actual detectado con ayuda de la clase Calendar
. Donde su constructor recibe los siguientes parámetros:
context
: Es el contexto donde se relacionará el diálogo. En este caso llamamos a la actividad contenedora con getActivity().callBack
: Es la interfaz por la cual se comunicará el diálogo. En este caso pusimos la actividad contenedora, ya que esta implementará la interfazOnTimeSetListener
.hourOfDay
: La hora inicial.minute
: Los minutos iniciales.is24HourView
: Determina si el time picker tendrá un formato de 24 horas o AM/PM. Para esto se obtuvo las preferencias del usuario del tiempo con el métodois24HourFormat()
de la claseDateFormat
.
El paso a seguir es implementar la interfaz OnTimeSetListener
en tu actividad principal para sobrescribir las acciones del método de acción onTimeSet()
.
@Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { Toast.makeText( this, "Tiempo: " + hourOfDay + ":" + minute, Toast.LENGTH_LONG) .show(); }
Diálogos En Pantalla Completa O Full Screen
El uso de diálogos en pantalla completa se puede dar cuando el diálogo contiene varios elementos agrupados que requieren más espacio. O en los procesos de inserción o edición de datos siempre y cuando no se requiera guardar los datos en tiempo real.
También pueden usarse en situaciones donde el tamaño de la pantalla influye. Es decir, para tamaños pequeños podría mostrarse el diálogo en pantalla completa y para tamaños grandes se desplegaría normalmente.
Lo curioso es que al crear un diálogo en pantalla completa, no podemos usar la clase AlertDialog
junto a Builder
, debido a que el DialogFragment
será embebido a través de una transacción de fragmentos como se hace normalmente.
Esto significa que el método onCreateDialog()
no será sobrescrito, por lo que debemos acudir a onCreateView()
para inflar un layout por completo.
Así que puedes deducir que las áreas de título, contenido y acción no están distribuidas de la misma forma. Esta vez los botones de confirmación y el título van en la action bar; y el contenido será ubicado en todo el espacio de trabajo.
El siguiente ejemplo extraído de la guía de Material Design para diseño de diálogos full screen, muestra cómo se vería este elemento:
Si lees un poco, nos indican que debemos ubicar la acción de confirmación como action button con un verbo descriptivo como “Guardar”, “Crear”, “Enviar”, etc. En caso de la acción negativa, se usa el ícono de cerrar como Up Button.
Ejemplo de un fragmento full screen
Supón que deseas añadir una nueva cita de ventas a tu aplicación gestora de vendedores y has decidido usar un fragmento en pantalla completa para ello.
Sabes que necesitas el nombre del lead, el producto en que está interesado, la fecha en que se acordó la reunión, la prioridad y una sección para describir alguna situación especial.
Para ello creas el siguiente layout:
fullscreen_dialog.xml
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="@dimen/activity_horizontal_margin"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:paddingTop="@dimen/edit_text_padding" android:text="@string/cliente_label" android:textAppearance="?android:attr/textAppearanceSmall" /> <Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/textView" android:entries="@array/clientes" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/spinner" android:paddingTop="@dimen/edit_text_padding" android:text="@string/producto_label" android:textAppearance="?android:attr/textAppearanceSmall" /> <Spinner android:id="@+id/spinner2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/textView2" android:entries="@array/productos" /> <TextView android:id="@+id/fecha_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/spinner2" android:paddingTop="@dimen/edit_text_padding" android:text="@string/fecha_label" android:textAppearance="?android:attr/textAppearanceSmall" /> <View android:id="@+id/centro" android:layout_width="1dp" android:layout_height="1dp" android:layout_centerInParent="true" android:background="@null" /> <TextView android:id="@+id/prioridad_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/fecha_text" android:paddingTop="@dimen/edit_text_padding" android:text="@string/prioridad_label" android:textAppearance="?android:attr/textAppearanceSmall" /> <Spinner android:id="@+id/spinner5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/prioridad_text" android:entries="@array/prioridades" /> <TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/spinner5" android:paddingTop="@dimen/edit_text_padding" android:text="@string/notas_label" android:textAppearance="?android:attr/textAppearanceSmall" /> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/textView5" android:hint="@string/notas_hint" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/fecha_text" style="@android:style/Widget.DeviceDefault.Light.Spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/fecha_label" android:layout_marginRight="@dimen/normal_padding" android:layout_toLeftOf="@+id/hora_text" android:text="Small Text" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/hora_text" style="@android:style/Widget.DeviceDefault.Light.Spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_below="@+id/fecha_label" android:text="Small Text" android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </ScrollView>
Para proseguir con ese diseño, simplemente debes crear un nuevo fragmento que extienda de DialogFragment
y sobrescribes el método onCreateView()
para inflarlo.
FullScreenDialog.java
import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.herprogramacion.dialogpers.R; import java.text.SimpleDateFormat; import java.util.Calendar; public class FullScreenDialog extends DialogFragment { private TextView textFecha; private TextView textTiempo; public FullScreenDialog() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); // Obtener instancia de la action bar ActionBar actionBar = ((AppCompatActivity) getActivity()) .getSupportActionBar(); if (actionBar != null) { // Habilitar el Up Button actionBar.setDisplayHomeAsUpEnabled(true); // Cambiar icono del Up Button actionBar.setHomeAsUpIndicator(R.mipmap.ic_close); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fullscreen_dialog, container, false); iniciarHora(view);// Setear hora inicial iniciarFecha(view);// Setear fecha inicial return view; } private void iniciarHora(View view) { textTiempo = (TextView) view.findViewById(R.id.hora_text); Calendar c = Calendar.getInstance(); SimpleDateFormat format = new SimpleDateFormat("HH:mm a"); textTiempo.setText(format.format(c.getTime())); textTiempo.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { new TimeDialog().show(getFragmentManager(), "TimePickerInFull"); } } ); } private void iniciarFecha(View view) { textFecha = (TextView) view.findViewById(R.id.fecha_text); Calendar c = Calendar.getInstance(); SimpleDateFormat format = new SimpleDateFormat("E MMM d yyyy"); textFecha.setText(format.format(c.getTime())); textFecha.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { new DateDialog().show(getFragmentManager(), "DatePickerInFull"); } } ); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.fullscreen_dialog, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case android.R.id.home: // procesarDescartar() break; case R.id.action_save: // procesarGuardar() break; } return super.onOptionsItemSelected(item); } /** * Actualiza la fecha del view {@code fecha_text} * @param year Nuevo Año * @param monthOfYear Nuevo Mes * @param dayOfMonth Nuevo día */ public void setDateView(int year, int monthOfYear, int dayOfMonth) { Calendar c = Calendar.getInstance(); c.set(year, monthOfYear, dayOfMonth); SimpleDateFormat format = new SimpleDateFormat("E MMM d yyyy"); textFecha.setText(format.format(c.getTime())); } /** * Actualiza la hora del view {@code hora_text} * @param hourOfDay Nueva Hora * @param minute Nuevos Minutos */ public void setTimeView(int hourOfDay, int minute) { Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR, hourOfDay); c.set(Calendar.MINUTE, minute); SimpleDateFormat format = new SimpleDateFormat("HH:mm a"); textTiempo.setText(format.format(c.getTime())); } }
El estilo de la fecha y el tiempo puedes modificarlo a través de la clase SimpleDateFormat, la cual provee el método format()
para transformar información con respecto a un patrón.
Otra aspecto importante es la creación de un DateDialog
y un TimeDialog
al momento de pulsar los text views de fecha y tiempo. Lo que quiere decir que se espera que la actividad implemente las escuchas para la transferencia de los datos.
Ahora debes insertarlo en la actividad donde te encuentras. Lo que requiere una transacción simple de fragmentos.
DetailActivity.java
import android.app.DatePickerDialog; import android.app.TimePickerDialog; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; import android.widget.DatePicker; import android.widget.TimePicker; import com.herprogramacion.dialogpers.R; import com.herprogramacion.dialogpers.dialogos.FullScreenDialog; public class DetailActivity extends AppCompatActivity implements DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); if (savedInstanceState == null) { crearFullScreenDialog(); } } private void crearFullScreenDialog() { FragmentManager fragmentManager = getSupportFragmentManager(); FullScreenDialog newFragment = new FullScreenDialog(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); transaction.add(android.R.id.content, newFragment, "FullScreenFragment") .commit(); } @Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { FullScreenDialog fragment = (FullScreenDialog) getSupportFragmentManager().findFragmentByTag("FullScreenFragment"); if (fragment != null) { fragment.setDateView(year, monthOfYear, dayOfMonth); } } @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { FullScreenDialog fragment = (FullScreenDialog) getSupportFragmentManager().findFragmentByTag("FullScreenFragment"); if (fragment != null) { fragment.setTimeView(hourOfDay, minute); } } }
Los métodos onDateSet()
y onTimeSet()
de las interfaces de los pickers acceden directamente al dialogo FullScreenDialog
para comunicar los datos. Si observaste bien, setDateView()
y setTimeView()
son los métodos encargados de recibir los datos de las interfaces de comunicación.
Por último programamos las acciones de la action bar. Crea un nuevo menú llamado fullscreen_dialog.xml y asígnale un action button para guardar:
fullscreen_dialog.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_save" android:title="@string/action_save" android:orderInCategory="1" app:showAsAction="ifRoom" /> </menu>
Si analizas el fragmento FullScreenFragment
, este contribuye a la action bar con onHasOptionsMenu()
dentro de onCreate()
. Por esa razón se infla el menú en onCreateOptionsMenu()
.
El ícono de cerrar se implementó al obtener la action bar en onCreateView()
para cambiar el icono del up button con el método setHomeAsUpIndicator()
. El drawable puedes encontrarlo al descargar el proyecto.
Recuerda que si necesitas implementar acciones de confirmación y descarte, tienes el método onOptionsItemSelected()
. Donde android.R.id.home
permite para tomar el evento del up button.
Manejar Eventos De Descarte Y Cancelación De Un Diálogo
Afortunadamente los fragmentos son descartados en el momento que sus botones de acción son presionados o incluso si el usuario presiona por fuera de su contenido. Pero en ocasiones muy extremas tal vez requieras cerrar el diálogo manualmente.
Y esto es tan sencillo como usar el método dismiss()
dentro de DialogFragment
. Adicionalmente puedes realizar acciones en ese instante con el método onDismiss()
.
// Dentro de un DialogFragment... @Override public void onDismiss(DialogInterface dialog) { // Tus acciones }
Por otro lado el diálogo puede ser cancelado sin aplicar los cambios a través de cancel()
. Para procesar su comportamiento puedes usar onCancel()
, el cual es invocado si el usuario presiona el Back Button, si es presionado el botón negativo o si se presiona por fuera del diálogo.
// Dentro de un DialogFragment... @Override public void onCancel(DialogInterface dialog) { // Tus acciones }
Conclusiones
- Asegúrate de usar diálogos cuando desees que el usuario decida ante una situación de confirmación o cuando sea necesario el ingreso de datos necesarios para el procesamiento de una acción.
- La clase
DialogFragment
permite la relación de los diálogos con los eventos de un fragmento. Esto reduce la complejidad de interacciones con la actividad. - Asegúrate de seguir los patrones de diseño y las medidas establecidas por Google para mantener una excelente experiencia de usuario en tu aplicación.
- Es importante mantener el diseño de tus diálogos personalizados adaptable a diferentes tipos de pantallas y orientaciones. Esto maximizará la experiencia de usuario en tu app.