Android >> Notificaciones en Android ( Parte 2 – Dialogs I )

android-dialogs-1

Vamos a ver en este tutorial los distintos mecanismos que nos proporciona Android para mostrar mensaje al usuario. Principalmente, estos mecanismo son tres:

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.

dialogs

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

dialog-no-button

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

dialog-one-button

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

dialog-two-buttons

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

dialog-three-buttons

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étodo setAdapter() para poder trabajar con datos dinámicos (por ejemplo con una base de datos) usando un ListAdapter y un Loader o usar un Cursor con el método setCursor(). Al mostrar una lista no se podrá un mostrar un mensaje, por lo que se recomienda especificar un título con el método setTitle().

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

    dialog-simple-list

  • 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, un ListAdapter o un Cursor. 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();
    	}
    

    dialog-single-choice

  • 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 un Cursor. 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();
    }
    

    dialog-multiple-choices

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

}

custom-dialog

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

Más información | Dialogs | Android Developers & Dialogs | Android Design

Descargar código | GitHub