Crear un diálogo personalizado en Android


Ya hemos visto como crear un diálogo de confirmación de forma “express” a través de un AlertDialog.Builder. Sin embargo, muchas veces necesitaremos que el usuario, además de confirmar una acción, realice una selección rápida de un dato concreto a través de un diálogo. Los ejemplos más típicos suelen ser la selección de una fecha, de un nombre, un número o un color.

En el siguiente ejemplo aprenderemos cómo crear un nuevo diálogo personalizado que permitirá al usuario generar un color a partir de los tres componentes RGB y enviarselo a la actividad que lo solicita. Cuando finalicemos, nuestro diálogo tendrá un aspecto similar al siguiente:

ColorPickerDialog

Todo elemento de interfaz que tenga cierta complejidad debería poseer un layout XML. Comenzaremos, tal y como hacemos cuando creamos una nueva actividad, con la creación de un layout pulsando sobre New > Android > Android XML Layout File.

Creando un layout

 

A continuación maquetaremos el diálogo a nuestro gusto. Lo que necesitaremos a nivel funcional serán los siguientes elementos:

  • Tres controles SeekBar que permitirán al usuario seleccionar la cantidad de color de cada tipo que desea añadir.
  • Tres controles TextView que mostrarán la cantidad de cada color (0-255)
  • Una vista genérica (View) que utilizaremos para componer el color, modificando su color de fondo a partir de los valores de los elementos anteriores.
  • Dos Button: uno para aceptar y enviar el color y otro para cancelar, cerrando el diálogo.

El fichero XML con el layout sería el siguiente:


<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    android:background="@android:drawable/bottom_bar"
	    android:orientation="vertical"
	    android:padding="@dimen/activity_horizontal_margin" >

	    <RelativeLayout
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:gravity="center" >

	        <TextView
	            android:layout_width="wrap_content"
	            android:layout_height="wrap_content"
	            android:layout_alignParentLeft="true"
	            android:layout_alignParentTop="true"
	            android:layout_marginLeft="16dp"
	            android:text="@string/dccTitle"
	            android:textAppearance="?android:attr/textAppearanceMedium" />

	    </RelativeLayout>

	    <LinearLayout
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:layout_marginTop="@dimen/dialog_vertical_margin" >

	        <TextView
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="2"
	            android:text="@string/dccRed" />

	        <SeekBar
	            android:id="@+id/sbRed"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_weight="6"
	            android:progress="0"
	            android:max="255" />

	        <TextView
	            android:id="@+id/tvRed"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="1"
	            android:text="0" />
	    </LinearLayout>

	    <LinearLayout
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:layout_marginTop="@dimen/dialog_vertical_margin" >

	        <TextView
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="2"
	            android:text="@string/dccGreen" />

	        <SeekBar
	            android:id="@+id/sbGreen"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_weight="6"
	            android:progress="0"
	            android:max="255" />

	        <TextView
	            android:id="@+id/tvGreen"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="1"
	            android:text="0" />
	    </LinearLayout>

	    <LinearLayout
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:layout_marginTop="@dimen/dialog_vertical_margin" >

	        <TextView
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="2"
	            android:text="@string/dccBlue" />

	        <SeekBar
	            android:id="@+id/sbBlue"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_weight="6"
	            android:progress="0"
	            android:max="255" />

	        <TextView
	            android:id="@+id/tvBlue"
	            android:layout_width="0dp"
	            android:layout_height="wrap_content"
	            android:layout_gravity="center"
	            android:layout_weight="1"
	            android:text="0" />
	    </LinearLayout>

	    <RelativeLayout
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content" >

	        <View
	            android:id="@+id/colorView"
	            android:layout_width="64dp"
	            android:layout_height="64dp"
	            android:layout_alignParentTop="true"
	            android:layout_centerHorizontal="true"
	            android:background="@android:color/black" />

	    </RelativeLayout>

	    <LinearLayout
	        android:layout_width="fill_parent"
	        android:layout_height="wrap_content"
	        android:orientation="horizontal"
	        android:paddingBottom="1.0dip"
	        android:paddingLeft="4.0dip"
	        android:paddingRight="4.0dip"
	        android:paddingTop="@dimen/activity_vertical_margin" >

	        <Button
	            android:id="@+id/btnOk"
	            android:layout_width="0.0dip"
	            android:layout_height="fill_parent"
	            android:layout_weight="1.0"
	            android:text="@string/dccOk" />

	        <Button
	            android:id="@+id/btnCancel"
	            android:layout_width="0.0dip"
	            android:layout_height="fill_parent"
	            android:layout_weight="1.0"
	            android:text="@string/dccCancel" />
	    </LinearLayout>

	</LinearLayout>

No olvidemos ajustar los valores android:progress=”0″ y android:max=”255″ de los SeekBar, que establecerá su valor inicial y valor máximo, respectivamente (aunque en realidad esto podría realizarse también mediante código más adelante).

Ahora construiremos nuestra clase, que deberá heredar de la clase Dialog e implementar dos interfaces:

El método que pretendemos utilizar para comunicarnos con la actividad que invoque el diálogo será mediante un Intent en broadcast, que la actividad deberá capturar mediante un BroadcastReceiver. Por ello deberemos necesitar también una cadena de texto que informe del ACTION que el Intent deberá tener asociado para que la actividad sepa qué es lo que debe capturar.

Además añadiremos referencias a los controles, otra referencia al contexto y declararemos un entero que almacenará el color generado a partir de los elementos RGB.


	public class DialogColorChooser extends Dialog
	implements OnClickListener, OnSeekBarChangeListener {

		public static final String EXTRA_COLOR_VALUE = "org.danigarcia.android.gui.DialogColorChooser.EXTRA_COLOR_VALUE";

		private Context 	context;		// Contexto
		private int			selectedColor;	// Color RGB
		private String		action;			// Action que dirá a la actividad qué Intent corresponde a la respuesta del diálogo

		private Button 		btnOk;
		private Button 		btnCancel;

		private TextView 	tvRed;
		private TextView 	tvGreen;
		private TextView 	tvBlue;

		private SeekBar		sbRed;
		private SeekBar		sbGreen;
		private SeekBar		sbBlue;

		private View		colorView;

		// Constructor por defecto
		public DialogColorChooser(Context context, String action) {
			super(context);
			this.context = context;
			this.action  = action;
		}
	}

El primer método que codificaremos tendrá como objetivo configurar el diálogo, referenciando los elementos de interfaz y asignando los listeners de los botones y las barras.


	private void configureDialog()
	{
	    requestWindowFeature(Window.FEATURE_NO_TITLE);
	    setContentView(R.layout.dialog_colorchooser);

	    btnOk = (Button) findViewById(R.id.btnOk);
	    btnCancel = (Button) findViewById(R.id.btnCancel);

	    tvRed = (TextView)findViewById(R.id.tvRed);
	    tvGreen = (TextView)findViewById(R.id.tvGreen);
	    tvBlue = (TextView)findViewById(R.id.tvBlue);

	    sbRed = (SeekBar)findViewById(R.id.sbRed);
	    sbGreen = (SeekBar)findViewById(R.id.sbGreen);
	    sbBlue = (SeekBar)findViewById(R.id.sbBlue);

	    colorView = (View)findViewById(R.id.colorView);

	    // Configuramos los Listeners
	    btnOk.setOnClickListener(this);
	    btnCancel.setOnClickListener(this);

	    sbRed.setOnSeekBarChangeListener(this);
	    sbGreen.setOnSeekBarChangeListener(this);
	    sbBlue.setOnSeekBarChangeListener(this);
	}

Sobrecargamos el método onCreate() para que, además de realizar la creación básica de un diálogo, llame al método que acabamos de codificar, referenciando los controles y asociando los listeners.


	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		configureDialog();
	}

El método onProgressChanged() será el encargado de detectar cuándo una barra modifica su posición actual. En él filtraremos por el ID del control que realiza el cambio y modificaremos el texto que indica la cantidad de color de ese elemento (rojo, verde o azul).
Como última acción, calcularemos el color resultante de la combinación de los tres componentes y lo mostraremos en la vista colorView, modificando su color de fondo.


	@Override
	public void onProgressChanged(SeekBar seekBar, int progress, boolean byUser) {
		int red = 0;
		int green = 0;
		int blue = 0;

		switch(seekBar.getId())
		{
			case R.id.sbRed:
			{
				tvRed.setText(String.valueOf(progress));
				break;
			}
			case R.id.sbGreen:
			{
				tvGreen.setText(String.valueOf(progress));
				break;
			}
			case R.id.sbBlue:
			{
				tvBlue.setText(String.valueOf(progress));
				break;
			}
			default:
				break;
		}

		// Extraemos la cantidad de cada color (0-255)
		red   = sbRed.getProgress();
		green = sbGreen.getProgress();
		blue  = sbBlue.getProgress();

		// Asignamos el color a la vista
		selectedColor = Color.rgb(red, green, blue);
		colorView.setBackgroundColor(selectedColor);
	}

Por último, sobrecargamos el método onClick() y nuevamente filtramos por el ID del botón que se pulsa. Si se trata del botón OK, instanciamos un nuevo Intent y le asociamos la acción que recibimos como parámetro en el constructor (será la actividad la que le diga al diálogo la acción que quiere ejecutar).

Tras hacer esto, añadimos un nuevo dato extra de tipo entero que representará el color generado mediante el método putExtra(). Dado que la actividad necesitará saber a qué dato extra queremos acceder, usaremos una constante pública (EXTRA_COLOR_VALUE) para identificarlo. Enviamos el Intent en broadcast y, finalmente, invocamos el método dismiss() para cerrar el diálogo.


	@Override
	public void onClick(View v) {
		switch(v.getId())
		{
			case R.id.btnOk:
			{
				Intent intent = new Intent(this.action);
				intent.putExtra(EXTRA_COLOR_VALUE, this.selectedColor);
				context.sendBroadcast(intent);
				break;
			}
			default:
				break;
		}
		dismiss();
	}

Los métodos onStartTrackingTouch() y onStopTrackingTouch() no hará falta rellenarlos, pero es necesario declararlos debido a que son métodos de la interfaz OnSeekBarChangeListener.


	@Override
	public void onStartTrackingTouch(SeekBar arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onStopTrackingTouch(SeekBar arg0) {
		// TODO Auto-generated method stub

	}

 

Una llamada al diálogo

A continuación pasaremos a la actividad para codificar la invocación del diálogo y la captura de la respuesta. Comenzamos codificando un método que muestre el diálogo, que será tan sencillo como esto:


	private void mostrarDialogoColor(String action)
	{
		DialogColorChooser dialog = new DialogColorChooser(this, action);
		dialog.show();
	}

Después definimos dos constantes que definirán las acciones que el BroadcastReceiver debera filtrar. Por ejemplo, las usaremos para detectar cuando el diálogo de seleccion envía un Intent para cambiar el color de un texto o del fondo.


	protected final String ACTION_TEXT_COLOR = "org.danigarcia.android.examples.almacenamiento.DataActivity.ACTION_TEXT_COLOR";
	protected final String ACTION_BACKGROUND_COLOR = "org.danigarcia.android.examples.almacenamiento.DataActivity.ACTION_BACKGROUND_COLOR";

Definimos un BroadcastReceiver que filtre las acciones anteriores. En su método onReceive() filtraremos por la acción que queremos recibir y codificaremos la funcionalidad asociada al retorno del dialogo, es decir, asignar los colores seleccionados al texto (ACTION_TEXT_COLOR) o al fondo (ACTION_BACKGROUND_COLOR).

Recordemos que el color seleccionado está insertado dentro del Intent como un entero con la clave EXTRA_COLOR_VALUE definida en la clase DialogColorChooser. Por lo tanto, las tareas a realizar sobre el Intent serán:

  • Filtrar por la acción del intent
  • Extraer el color de los datos extra del intent

	protected BroadcastReceiver bReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String 	hexColor;

			// Cambio de color del texto
			if(intent.getAction().equals(ACTION_TEXT_COLOR))
			{
				// Extraemos el color del intent y lo convertimos a hexadecimal
				colorTexto = intent.getIntExtra(DialogColorChooser.EXTRA_COLOR_VALUE, Color.rgb(255, 0, 0));
				hexColor = intColorToHex(colorTexto);

				// Mostramos el color del texto
				tvColorTexto.setText(hexColor);
				cambiarColorTexto();				// Suponemos que este metodo cambia el color del texto
			}

			// Cambio del color del fondo
			else if(intent.getAction().equals(ACTION_BACKGROUND_COLOR))
			{
				// Extraemos el color del intent y lo convertimos a hexadecimal
				colorFondo = intent.getIntExtra(DialogColorChooser.EXTRA_COLOR_VALUE, Color.rgb(255, 255, 255));
				hexColor = intColorToHex(colorFondo);

				// Mostramos el color del fondo
				tvColorFondo.setText(hexColor);
				cambiarColorFondo();
			}
		}
	};

Como último paso, haremos que el BroadcastReceiver se suscriba a estos eventos. Para ello crearemos un filtro y realizaremos la suscripción en el evento onCreate() de la actividad:


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_data);
		// Show the Up button in the action bar.
		setupActionBar();

		// Asociamos las acciones al BroadcastReceiver
		IntentFilter filter = new IntentFilter(ACTION_BACKGROUND_COLOR);
		filter.addAction(ACTION_TEXT_COLOR);
		this.registerReceiver(bReceiver, filter);
	}

Con esto, la funcionalidad del diálogo estaría completa.

2 comments

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s