Vamos a ver en este tutorial los distintos mecanismos que nos proporciona Android para mostrar mensaje al usuario. Principalmente, estos mecanismo son tres:
- Toast
- Diálogos I
- Diálogos II
- Notificaciones
En la primera parte de este tutorial vimos el mecanismo más sencillo: los mensajes Toast. En la segunda parte vamos a ver el uso de los cuadros de diálogos (Dialogs) en Android. Un diálogo es una pequeña ventana donde el usuario tiene que realizar alguna elección o introducir información extra. Gráficamente, un diálogo se caracteriza por ocupar una porción de la pantalla, en vez de la pantalla completa.
Diseñando diálogos
La clase principal que se usa para crear diálogos es la clase Dialog
, pero no se recomienda usar de manera directa dicha clase. Lo recomendable es usar una de las siguientes subclases:
AlertDialog
: Un diálogo que puede mostrar un título, un máximo de tres botones, una lista de items o un layout personalizado.
DatePickerDialog
o TimePickerDialog
: Un diálogo con una interfaz predefinida que permite al usuario seleccionar una fecha o una hora.
Estas clases definen el estilo y la estructura de tu diálogo, pero la clase DialogFragment
será usada como contenedor del diálogo. Dicha clase proporciona todos los controles que necesitamos para crear un diálogo y modificar su apariencia. La clase DialogFragment
fue introducida en Android 3.0 (API 11), si usas una versión más antigua, podrás usar la clase DialogFragment
sin problemas, ya que está disponible en la Librería de soporte de Android.
Usar la clase DialogFragment
te asegura la correcta gestión de los eventos que puedan ocurrir, como por ejemplo pulsar el botón Atrás o rotar la pantalla. Además, usando dicha clase podemos reusar el interfaz de los diálogos como componente en un interfaz más grande.
Creando un DialogFragment básico
Lo primero que deberemos hacer es descargar la librería de soporte de Android si estamos usando una versión de Android anterior a Android 3.0 (API 11).
Para crear un diálogo creamos una clase que herede de la clase DialogFragment
y crear un objeto AlertDialog
en el método onCreateDialog()
:
TwoActionButtonsDialog.java
package com.amatellanes.android; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.widget.Toast; public class TwoActionButtonsDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the Builder class for convenient dialog construction AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); // Set the message to display. builder.setMessage("Are you sure?"); // Set a listener to be invoked when the positive button of the dialog // is pressed. builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "Yes", Toast.LENGTH_SHORT).show(); } }); // Set a listener to be invoked when the negative button of the dialog // is pressed. builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "No", Toast.LENGTH_SHORT).show(); } }); // Create the AlertDialog object and return it return builder.create(); } }
Al igual que ocurría al crear un Toast
, cuando definimos un diálogo podremos encadenar varios métodos:
builder.setMessage("Are you sure?") .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "Yes", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "No", Toast.LENGTH_SHORT).show(); } });
Para mostrar el diálogo que acabamos de crear tendremos que crear una instancia de la clase en nuestra actividad e invocar al método show()
para que el diálogo aparezca:
DialogFragment dialog = new TwoActionButtonsDialog(); dialog.show(getSupportFragmentManager(), "dialog");
Con la llamada al método getSupportFragmentManager()
obtenemos el acceso al FragmentManager
que se ocupa de gestionar los fragments de la aplicación.
El segundo argumento “dialog”
es la etiqueta única que se usa para guardar y cargar el estado del fragment que se acaba de crear. La etiqueta te permite obtener acceso al fragment usando el método findFragmentByTag()
del FragmentManager
.
A continuación vamos a ver como modificar la apariencia del diálogo. De manera general podemos encontrar tres partes diferenciadas en un cuadro de diálogo:
- Título: Para modificar el título del diálogo usamos el método
setTitle()
. - Icono: Para mostrar un icono en el diálogo usamos el método
setIcon()
. - Contenido: Para modificar el contenido del diálogo usamos el método
setMessage()
. - Botones de acción: Disponemos de diferentes métodos para añadir botones a nuestro diálogo como veremos a continuación.
Añadiendo botones
Cuando definimos nuestro diálogo podemos añadir hasta tres botones de acción:
- Positive: Normalmente usado para aceptar y continuar con una acción. Se añade con el método
setPositiveButton()
. - Negative: Normalmente usado para cancelar una acción. Se añade con el método
setNegativeButton()
- Neutral: Normalmente usado para una acción intermedia como puede ser “Recordar más tarde”. Se añade con el método
setNeutralButton()
.
Por lo tanto podremos tener las siguientes combinaciones de botones (siempre que no utilicemos un layout personalizado):
NoActionButtonsDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Information") .setIcon( getResources().getDrawable( android.R.drawable.ic_dialog_info)) .setMessage("Message saved as draft."); return builder.create(); }
OneActionButtonDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Information") .setIcon( getResources().getDrawable( android.R.drawable.ic_dialog_info)) .setMessage("Message saved as draft.") .setNeutralButton(android.R.string.ok, new OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { } }); return builder.create(); }
TwoActionButtonsDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage("Are you sure?") .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }); return builder.create(); }
ThreeActionButtonsDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Information") .setIcon( getResources().getDrawable( android.R.drawable.ic_dialog_info)) .setMessage("Message saved as draft.") .setNeutralButton("Neutral", new OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { } }) .setPositiveButton(android.R.string.ok, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton(android.R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); return builder.create(); }
Añadiendo una lista
Podemos añadir 3 tipos diferentes de listas a nuestros diálogos:
- Una lista donde se pueda seleccionar un únicamente elemento: Para crear este tipo de diálogo debemos usar el método
setItems()
pasándole un array con los elementos de la lista, también se puede especificar una lista con el métodosetAdapter()
para poder trabajar con datos dinámicos (por ejemplo con una base de datos) usando unListAdapter
y unLoader
o usar unCursor
con el métodosetCursor()
. Al mostrar una lista no se podrá un mostrar un mensaje, por lo que se recomienda especificar un título con el métodosetTitle()
.
SimpleListDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Selecciona un color...").setItems( R.array.array_colors, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // The 'which' argument contains the index position // of the selected item } }); return builder.create(); }
- Una lista donde se pueda seleccionar únicamente un elemento (Radio Buttons): Para crear este tipo de diálogos deberemos de usar el método
setSingleChoiceItems()
. Al igual que en el punto anterior podremos usar un array con los elementos de la lista, unListAdapter
o unCursor
. Se debe especificar que elemento estará seleccionado inicialmente.
SingleChoiceListDialog.java
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Selecciona un color...") .setSingleChoiceItems(R.array.array_colors, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { } }) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { } }); return builder.create(); }
- Una lista donde se pueda seleccionar múltiples elementos (Checkboxes): Para este tipo de diálogo utilizamos el método
setMultiChoiceItems()
, en el que se podrá especificar un array, o unCursor
. Se debe especificar un array con los elementos seleccionados inicialmente.
MultipleChoiceListDialog.java
private List mSelectedItems; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { mSelectedItems = new ArrayList(); // Where we track the // selected items AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Selecciona varios colores...") .setMultiChoiceItems(R.array.array_colors, null, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { if (isChecked) { // If the user checked the item, add it to // the selected items mSelectedItems.add(which); } else if (mSelectedItems.contains(which)) { // Else, if the item is already in the // array, remove it mSelectedItems.remove(Integer .valueOf(which)); } } }) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // User clicked OK, so save the mSelectedItems // results somewhere // or return them to the component that opened // the dialog } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { } }); return builder.create(); }
Creando un layout personalizado
En algún momento necesitaremos mostrar algo más que un simple mensaje en el diálogo como por ejemplo un pequeño formulario. En tal caso necesitaremos definir nosotros mismos un layout:
res / layout / dialog_signin.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="64dp" android:background="#357ebd" android:contentDescription="@string/app_name" android:fontFamily="sans-serif" android:scaleType="center" android:text="@string/dialog_header" android:textColor="#FFFFFF" android:textSize="35sp" /> <EditText android:id="@+id/email" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_marginTop="16dp" android:fontFamily="sans-serif" android:hint="@string/email" android:inputType="textEmailAddress" /> <EditText android:id="@+id/password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_marginTop="4dp" android:fontFamily="sans-serif" android:hint="@string/password" android:inputType="textPassword" /> <CheckBox android:id="@+id/checkbox_cheese" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onCheckboxClicked" android:text="@string/remember_me" /> </LinearLayout>
Una vez definido nuestro layout se lo asignamos a nuestro diálogo. Para ello debemos de inflar el layout que acabamos de crear, obtenemos un LayoutInflater
con el método getLayoutInflater()
y a continuación llamamos a inflate()
, donde especificamos el ID del layout y en el segundo la su vista padre. Por último llamamos al método setView()
para colocar el layuot en el diálogo:
CustomDialog.java
package com.amatellanes.android; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.widget.Toast; public class CustomDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the Builder class for convenient dialog construction AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); // Get the layout inflater LayoutInflater inflater = getActivity().getLayoutInflater(); // Inflate and set the layout for the dialog // Pass null as the parent view because its going in the dialog layout builder.setView(inflater.inflate(R.layout.dialog_signin, null)) // Add action buttons .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "Yes", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Toast.makeText(getActivity(), "No", Toast.LENGTH_SHORT) .show(); } }); // Create the AlertDialog object and return it return builder.create(); } }
Algunas veces nos puede interesar mostrar alguna actividad como un diálogo, por ejemplo, al crear las pantallas para una tablet nos puede interesar mostrar alguna como un diálogo. Podemos conseguirlo indicando en el atributo de nuestra actividad el valor Theme.Holo.Dialog
en el fichero AndroidManifest:
<a style="font-weight:inherit;line-height:inherit;color:#0088cc;"ctivity android:theme="@android:style/Theme.Holo.Dialog" >
Puedes también mostrar una actividad como un diálogo únicamente cuando la pantalla del dispositivo donde se esta ejecutando la aplicación sea grande (large screen) indicado esta vez el valor Theme.Holo.DialogWhenLarge
:
<a style="font-weight:inherit;line-height:inherit;color:#0088cc;"ctivity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
Comunicando un diálogo con una actividad
Cuando pulsamos cualquiera de los botones de un diálogo o se selecciona cualquier item de una lista, realizamos un acción determinada, a menudo es frecuente que la acción se ejecute en la actividad o fragment que abrió el diálogo. Para ello debemos definir un interfaz con un método para cada uno de los eventos que queramos lanzar. Luego este interfaz debe ser implementado por la actividad o fragment que va a recibir el evento. Vamos a ver un ejemplo. Definimos en primer lugar nuestro clase que hereda de DialogFragment
y definimos el interfaz DialogListener
con dos métodos. A continuación sobrescribimos el método onAttach()
para obligar que la actividad sobrescriba el interfaz DialogListener
y por último, llamamos al correspondiente método del interfaz DialogListener
cuando se pulse algún botón del diálogo:
TwoActionButtonsDialog.java
package com.amatellanes.android; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; public class TwoActionButtonsDialog extends DialogFragment { private DialogListener listener; public interface DialogListener { public void onDialogPositiveClick(DialogFragment dialog); public void onDialogNegativeClick(DialogFragment dialog); } // Override the Fragment.onAttach() method to instantiate the // NoticeDialogListener @Override public void onAttach(Activity activity) { super.onAttach(activity); // Verify that the host activity implements the callback interface try { // Instantiate the NoticeDialogListener so we can send events to the // host listener = (DialogListener) activity; } catch (ClassCastException e) { // The activity doesn't implement the interface, throw exception throw new ClassCastException(activity.toString() + " must implement DialogListener"); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the Builder class for convenient dialog construction AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage("Are you sure?") .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { listener.onDialogPositiveClick(TwoActionButtonsDialog.this); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { listener.onDialogNegativeClick(TwoActionButtonsDialog.this); } }); // Create the AlertDialog object and return it return builder.create(); } }
A continuación, en la actividad que lanzó la actividad implementamos el interfaz DialogListener
:
MainActivity.java
public class MainActivity extends FragmentActivity implements TwoActionButtonsDialog.DialogListener { private static final String TAG = "dialog"; public void showTwoActionButton(View v) { DialogFragment dialog = new TwoActionButtonsDialog(); dialog.show(getSupportFragmentManager(), TAG); } // The dialog fragment receives a reference to this Activity through the // Fragment.onAttach() callback, which it uses to call the following methods // defined by the NoticeDialogFragment.NoticeDialogListener interface @Override public void onDialogPositiveClick(DialogFragment dialog) { // User touched the dialog's positive button Toast.makeText(getApplicationContext(), android.R.string.yes, Toast.LENGTH_SHORT) .show(); } @Override public void onDialogNegativeClick(DialogFragment dialog) { // User touched the dialog's negative button Toast.makeText(getApplicationContext(), android.R.string.no, Toast.LENGTH_SHORT) .show(); } }
Mostrando el diálogo a pantalla completa
Dependiendo de nuestro diseño, podríamos querer que alguna parte de nuestro interfaz aparezca algunas veces a pantalla completa y otras como un fragmento (dependiendo por ejemplo del tamaño de la pantalla del dispositivo). Pues bien, la clase DialogFragment
nos permite esta flexibilidad, ya que también puede comportarse como un fragment empotrable en nuestro interfaz.
Para ello debemos definir el layout y luego cargarlo en el método onCreateView()
, a diferencia de como lo hacíamos antes al crear un diálogo personalizado. A continuación vamos a modificar el ejemplo de diálogo personalizado anterior para este ejemplo:
CustomDialog.java
package com.amatellanes.android; import android.app.Dialog; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; public class CustomDialog extends DialogFragment { /** * The system calls this to get the DialogFragment's layout, regardless of * whether it's being displayed as a dialog or an embedded fragment. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout to use as dialog or embedded fragment return inflater.inflate(R.layout.dialog_signin, container, false); } /** The system calls this only when creating the layout in a dialog. */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // The only reason you might override this method when using // onCreateView() is to modify any dialog characteristics. For example, // the dialog includes a title by default, but your custom layout might // not need it. So here you can remove the dialog title, but you must // call the superclass to get the Dialog. Dialog dialog = super.onCreateDialog(savedInstanceState); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); return dialog; } }
Ahora vamos a ver como decidir si mostrar el diálogo como un diálogo o a pantalla completa basándonos en el tamaño de la pantalla. Para este fin vamos a usar la variable mIsLargeLayout
que definiremos como una variable booleana en los recursos XML de nuestra aplicación. Tendremos dos recursos en diferentes directorios, uno en el directorio values que contendrá el valor por defecto, que en este caso será false
y el directorio values-large que almacenará los recursos para pantallas grandes. Cuando se carguen los recursos, primero se buscará en los directorios cuyas características sean más especificas, por lo que si nuestro dispositivo tiene una pantalla grande, cargará en primer lugar los recursos disponibles en el directorio values-large:
res / values / bools.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Default boolean values --> <bool name="large_layout">false</bool> </resources>
res / values-large / bools.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Large screen boolean values --> <bool name="large_layout">true</bool> </resources>
Luego se inicializa la variable mIsLargeLayout
en el método onCreate()
de la actividad:
MainActivity.java
boolean mIsLargeLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mIsLargeLayout = getResources().getBoolean(R.bool.large_layout); }
Y el código que decide si el diálogo se muestra como un diálogo o a pantalla completa es el siguiente:
MainActivity.java
private static final String TAG = "dialog"; public void showDialog() { FragmentManager fragmentManager = getSupportFragmentManager(); CustomDialog newFragment = new CustomDialog(); if (mIsLargeLayout) { // The device is using a large layout, so show the fragment as a // dialog newFragment.show(fragmentManager, TAG); } else { // The device is smaller, so show the fragment fullscreen FragmentTransaction transaction = fragmentManager .beginTransaction(); // For a little polish, specify a transition animation transaction .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); // To make it fullscreen, use the 'content' root view as the // container for the fragment, which is always the root view for the // activity transaction.add(android.R.id.content, newFragment) .addToBackStack(null).commit(); } }