Android Tutorial: Dialog personalizado

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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
    1
    2
    3
    4
    5
    6
    7
    8
    <?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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?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:

1
2
3
4
5
6
7
8
9
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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):

1
2
3
4
5
6
7
8
9
<?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">@null</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:

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