Tip Android #15: Dialog personalizado

android tip

Pese a que los AlertDialog son muy fáciles y cómodos de utilizar, resulta muy díficil conseguir una personalización a fondo de los mismos sobre todo por la aplicación de estilos, siendo necesario modificar estilos por defecto y aún así el resultado depende tanto de de la versión de Android como del fabricante del dispositivo. En este tip vamos a “emular” un AlertDialog pero diseñando nosotros todo el layout así como sus estilos usando directamente la clase Dialog. Esta técnica permitirá controlar completamente tanto el formato del Dialog como sus estilos, haciendo que estos sean los mismos en cualquier dispositivo independientemente de la versión de Android que ejecuten y que, por lo tanto,siempre “encajen” con el estilo de nuestra app.

El ejemplo que se mostrará consistirá en un Dialog de color blanco con un borde gris y redondeado. Tendrá un título negro separado del cuerpo del mensaje por un View tal y como vimos en el tip #4 , y un par de botones Aceptar-Cancelar con un estilo también personalizado. El layout es el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/dialog_container"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/titulo"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textColor="@android:color/black" 
        android:layout_weight="0"/>

    <View
        android:id="@+id/divider"
        android:layout_width="fill_parent"
        android:layout_height="5dp"
        android:layout_marginBottom="3dp"
        android:layout_marginTop="3dp"
        android:background="@drawable/dialog_divider" />

    <ScrollView
        android:id="@+id/cuerpo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:layout_weight="1">

        <TextView
            android:id="@+id/contenido"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="@android:color/black" />
    </ScrollView>

    <LinearLayout
        android:id="@+id/botonera"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:layout_weight="0">

        <Button
            android:id="@+id/aceptar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/dialog_button_selector"
            android:text="@string/aceptar"
            android:textColor="@android:color/white" />

        <Button
            android:id="@+id/cancelar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_weight="1"
            android:background="@drawable/dialog_button_selector"
            android:text="@string/cancelar"
            android:textColor="@android:color/white" />
    </LinearLayout>

</LinearLayout>

Lo más reseñable es que ha habido que jugar con los weight (técnica ya comentada en este artículo) para que el contenido sea “scrolable” pero haciendo que los botones siempre salgan debajo incluso cuando el texto se desborde. Con respecto a los drawables (res/drawable) aplicados, no tienen nada de particular, es cuestión de ir “jugando” hasta que demos con un diseño apropiado que encaje con el de nuestra aplicación. Los incluyo “minimizados” aunque se pueden consultar en el proyecto completo de ejemplo que se enlaza al final del tip.

  • dialog_container.xml
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <!-- color de fondo -->
        <solid android:color="@android:color/white" />
    
        <!-- curvatura de las esquinas -->
        <corners android:radius="10dp" />
        
        <!-- padding para dejar una separacin prudencial entre los elementos conetenidos y el borde -->
        <padding android:left="8dp" android:top="4dp" android:right="8dp" android:bottom="8dp"/>
        
        <!-- borde -->
        <stroke
            android:width="3dp"
            android:color="@android:color/darker_gray" />
    
    </shape>
    
  • dialog_divider.xml
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <solid android:color="@color/dialog_divider" />
    
        <corners android:radius="5dp" />
    
    </shape>
    
  • dialog_button_selector.xml
    
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:state_pressed="true">
            <shape>
                <solid android:color="@color/button_pressed" />
    
                <corners android:radius="12dp" />
    
                <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
            </shape>
            
            </item>
            
        <item android:state_focused="true">
            <shape>
                <solid android:color="@color/button_pressed" />
    
                <corners android:radius="12dp" />
    
                <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
            </shape>
            
            </item>
            
        <item>
            <shape>
                <solid android:color="@color/button_normal" />
    
                <corners android:radius="12dp" />
    
                <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
            </shape>
            
         </item>
    
    </selector>
    

Como buena práctica, los colores se han definido en el fichero /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <color name="button_normal">#929392</color>
    <color name="button_pressed">#c0c1c0</color>
    <color name="dialog_divider">@android:color/black</color>

</resources>

Por último, veamos el código con el que creamos y mostramos el Dialog. Lo haremos desde un botón que invocará al método mostrar y tan sólo hay que tener en cuenta lo resaltado en el código y el “truco” para que no se muestre el título y los bordes por defecto que tienen de forma natural los Dialog.

package com.danielme.tipsandroid.dialog;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.danieme.tipsandroid.dialog.R;


public class MainActivity extends Activity
{

	Dialog customDialog = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);		
	}
	
	public void mostrar(View view)
	{
		// con este tema personalizado evitamos los bordes por defecto
		customDialog = new Dialog(this,R.style.Theme_Dialog_Translucent);
		//deshabilitamos el título por defecto
		customDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
		//obligamos al usuario a pulsar los botones para cerrarlo
		customDialog.setCancelable(false);
		//establecemos el contenido de nuestro dialog
		customDialog.setContentView(R.layout.dialog);
		
		TextView titulo = (TextView) customDialog.findViewById(R.id.titulo);
		titulo.setText("Título del Dialog");
		
		TextView contenido = (TextView) customDialog.findViewById(R.id.contenido);
		contenido.setText("Mensaje con el contenido del dialog");
		
	    ((Button) customDialog.findViewById(R.id.aceptar)).setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View view)
			{
				customDialog.dismiss();
				Toast.makeText(MainActivity.this, R.string.aceptar, Toast.LENGTH_SHORT).show();
				
			}
		});
	    
	    ((Button) customDialog.findViewById(R.id.cancelar)).setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View view)
			{
				customDialog.dismiss();
				Toast.makeText(MainActivity.this, R.string.cancelar, Toast.LENGTH_SHORT).show();
				
			}
		});
		
		customDialog.show();
	}	
	
}
	

El “tema” que aplicamos al Dialog es el siguiente (/res/values/styles.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- http://stackoverflow.com/questions/1883425/android-borderless-dialog -->
    <style name="Theme_Dialog_Translucent" parent="android:Theme.Dialog">
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

</resources>

Aquí podemos ver “cara a cara” el resultado final en Android 4.1 y 2.1 para comprobar que hemos conseguido crear un Dialog independiente de la plataforma con un estilo apropiado para nuestra aplicación:

android custom dialog

El proyecto completo para Eclipse ADT se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

<< TIPS ANDROID

7 Responses to Tip Android #15: Dialog personalizado

  1. Yo dice:

    Quiero dar las gracias al autor de este tema, me sirvió de mucha utilidad para hacer mi aplicación mas atractiva.

  2. Martin Rios dice:

    Primero quiero darte las Gracias por esta Espectacular web.
    Y lo segundo quiero pedirte orientación de como hacer para capturar el valor de un EditText desde este dialogo, ya que lo he hecho de diferentes formas pero al enviar el valor a una base de dato o mostrar el valor mediante Toast.makeText().show(); se cierra la aplicación.. Soy nuevo en programación Android y me seria muy Útil tu ayuda… de Antemano Gracias…

    • danielme.com dice:

      En principio el EditText dentro del cuadro de diálogo se utiliza como cualquier editText, sólo hay que obtenerlo del layout del dialog:
      EditText editText = (EditText) customDialog.findViewById(R.id.editText);

  3. Joaquín dice:

    Hola Daniel
    Me parece que la idea no funciona del todo bien. Sí, es cierto que en las capturas de pantalla que se muestran da la sensación de que funciona. Pero sólo es la sensación, ya que en realidad el fallo es ocultado por el fondo negro. Si cambiamos a un fondo blanco, vemos como el fondo negro original del dialogo sigue apareciendo por las esquinas. Por lo tanto, creo que no funciona.

    En este enlace dejo la captura del resultado de la aplicación con fondo blanco:

    Espero que publiques la corrección de este fallo.
    Un saludo.

  4. Joaquín dice:

    Hola de nuevo
    En el estilo ‘Theme_Dialog_Translucent’ que se le asigna al diálogo, basta con cambiar

    @null

    por

    #00000000

    Donde se asigna un color transparente al fondo por defecto del diálogo.
    De esta manera si que queda realmente un diálogo transparente.
    Dejo enlace a imagen con el resultado de este cambio:

    Un saludo

  5. Sergio Cerda dice:

    Muchas gracias por el aporte, me ha servido, sólo bastó cambiar algunos colores para aplicar al tema que uso en la app. Excelente !!!

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

A %d blogueros les gusta esto: