lunes, 21 de marzo de 2011

Mi Cuaderno Android - Controles básicos

Ahora nos vamos a centrar en los diferentes tipos de botones y cómo podemos personalizarlos. El SDK de Android nos proporciona tres tipos de botones: el clásico (Button), el de tipo on/off (ToggleButton), y el que puede contener una imagen (ImageButton). En la imagen siguiente vemos el aspecto por defecto de estos tres controles.
Control Button [API]
Un control de tipo Button es el botón más básico que podemos utilizar. En el ejemplo siguiente definimos un botón con el texto “Púlsame” asignando su propiedad android:text. Además de esta propiedad podríamos utilizar muchas otras como el color de fondo (android:background), estilo de fuente (android:typeface), color de fuente (android:textcolor), tamaño de fuente (android:textSize), etc.

<Button android:id="@+id/BtnBoton1"
android:text="Púlsame"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

Control ToggleButton [API]
Un control de tipo ToggleButton es un tipo de botón que puede permanecer en dos estados, pulsado/no_pulsado. En este caso, en vez de definir un sólo texto para el control definiremos dos, dependiendo de su estado. Así, podremos asignar las propiedades android:textOn y android:textoOff para definir ambos textos.

<ToggleButton android:id="@+id/BtnBoton2"
android:textOn="ON"
android:textOff="OFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

Control ImageButton [API]
En un control de tipo ImageButton podremos definir una imagen a mostrar en vez de un texto, para lo que deberemos asignar la propiedad android:src. Normalmente asignaremos esta propiedad con el descriptor de algún recurso que hayamos incluido en la carpeta /res/drawable. Así, por ejemplo, en nuestro caso hemos incluido una imagen llamada “ok.png” por lo que haremos referencia al recurso “@drawable/ok“.

<ImageButton android:id="@+id/BtnBoton3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ok" />


Eventos de un botón
Como podéis imaginar, aunque estos controles pueden lanzar muchos otros eventos, el más común de todos ellos y el que querremos capturar en la mayoría de las ocasiones es el evento onClick. Para definir la lógica de este evento tendremos que implementarla definiendo un nuevo objetoView.OnClickListener() y asociándolo al botón mediante el método setOnClickListener(). La forma más habitual de hacer esto es la siguiente:

final Button btnBoton1 = (Button)findViewById(R.id.BtnBoton1);
btnBoton1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View arg0)
    {
        lblMensaje.setText("Botón 1 pulsado!");
    }
});

En el caso de un botón de tipo ToggleButton suele ser de utilidad conocer en qué estado ha quedado el botón tras ser pulsado, para lo que podemos utilizar su método isChecked(). En el siguiente ejemplo se comprueba el estado del botón tras ser pulsado y se realizan acciones distintas según el resultado.

final ToggleButton btnBoton2 = (ToggleButton)findViewById(R.id.BtnBoton2);
btnBoton2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View arg0)
    {
        if(btnBoton2.isChecked())
            lblMensaje.setText("Botón 2: ON");
        else
            lblMensaje.setText("Botón 2: OFF");
    }
});

Personalizar el aspecto un botón [y otros controles]
En la imagen anterior vimos el aspecto que presentan por defecto los tres tipos de botones disponibles. Pero, ¿y si quisiéramos personalizar su aspecto más allá de cambiar un poco el tipo o el color de la letra o el fondo?

Para cambiar la forma de un botón podríamos simplemente asignar una imagen a la propiedad android:background, pero esta solución no nos serviría de mucho porque siempre se mostraría la misma imagen incluso con el botón pulsado, dando poca sensación de elemento “clickable”.

La solución perfecta pasaría por tanto por definir diferentes imágenes de fondo dependiendo del estado del botón. Pues bien, Android nos da total libertad para hacer esto mediante el uso de selectores. Un selector se define mediante un fichero XML localizado en la carpeta /res/drawable, y en él se pueden establecer los diferentes valores de una propiedad determinada de un control dependiendo de su estado.

Por ejemplo, si quisiéramos dar un aspecto plano a un botón ToggleButton, podríamos diseñar las imágenes necesarias para los estados “pulsado” (en el ejemplo toggle_on.png) y “no pulsado” (en el ejemplotoggle_off.png) y crear un selector como el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_checked="false"
    android:drawable="@drawable/toggle_off" />
    <item android:state_checked="true"
    android:drawable="@drawable/toggle_on" />
</selector>

Este selector lo guardamos por ejemplo en un fichero llamado toggle_style.xml y lo colocamos como un recurso más en nuestra carpeta de recursos /res/drawable. Hecho esto, tan sólo bastaría hacer referencia a este nuevo recurso que hemos creado en la propiedad android:background del botón:

<ToggleButton android:id="@+id/BtnBoton4"
    android:textOn="ON"
    android:textOff="OFF"
    android:padding="10dip"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
android:background="@drawable/toggle_style"/>

En la siguiente imagen vemos el aspecto por defecto de un ToggleButton y cómo ha quedado nuestro ToggleButton personalizado.
Control ImageView [API]
El control ImageView permite mostrar imágenes en la aplicación. La propiedad más interesante esandroid:src, que permite indicar la imagen a mostrar. Nuevamente, lo normal será indicar como origen de la imagen el identificador de un recurso de nuestra carpeta /res/drawable, por ejemplo android:src=”@drawable/unaimagen”. Además de esta propiedad, existen algunas otras útiles en algunas ocasiones como las destinadas a establecer el tamaño máximo que puede ocupar la imagen, android:maxWidth y android:maxHeight.

<ImageView android:id="@+id/ImgFoto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
android:src="@drawable/icon" />


En la lógica de la aplicación, podríamos establecer la imagen mediante el método setImageResorce(…), pasándole el ID del recurso a utilizar como contenido de la imagen.

ImageView img= (ImageView)findViewById(R.id.ImgFoto);

Control TextView [API]
El control TextView es otro de los clásicos en la programación de GUIs, las etiquetas de texto, y se utiliza para mostrar un determinado texto al usuario. Al igual que en el caso de los botones, el texto del control se establece mediante la propiedad android:text. A parte de esta propiedad, la naturaleza del control hace que las más interesantes sean las que establecen el formato del texto mostrado, que al igual que en el caso de los botones son las siguientes: android:background (color de fondo), android:textColor (color del texto), android:textSize (tamaño de la fuente) y android:typeface (estilo del texto: negrita, cursiva, …).

<TextView android:id="@+id/LblEtiqueta"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Escribe algo:"
    android:background="#AA44FF"
android:typeface="monospace" />

De igual forma, también podemos manipular estas propiedades desde nuestro código. Como ejemplo, en el siguiente fragmento recuperamos el texto de una etiqueta con getText(), y posteriormente le concatenamos unos números, actualizamos su contenido mediante setText() y le cambiamos su color de fondo con setBackgroundColor().

final TextView lblEtiqueta = (TextView)findViewById(R.id.LblEtiqueta);
String texto = lblEtiqueta.getText().toString();
texto += "123";
lblEtiqueta.setText(texto);

Control EditText [API]
El control EditText es el componente de edición de texto que proporciona la plataforma Android. Permite la introducción y edición de texto por parte del usuario, por lo que en tiempo de diseño la propiedad más interesante a establecer, además de su posición/tamaño y formato, es el texto a mostrar, atributo android:text.

<EditText android:id="@+id/TxtTexto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/LblEtiqueta" />

De igual forma, desde nuestro código podremos recuperar y establecer este texto mediante los métodos getText() y setText(nuevoTexto) respectivamente:

final EditText txtTexto = (EditText)findViewById(R.id.TxtTexto);
String texto = txtTexto.getText().toString();
txtTexto.setText("Hola mundo!");

Un detalle que puede haber pasado desapercibido. ¿Os habéis fijado en que hemos tenido que hacer un toString() sobre el resultado de getText()? La explicación para esto es que el método getText() no devuelve un String sino un objeto de tipo Editable, que a su vez implementa la interfaz Spannable. Y esto nos lleva a la característica más interesante del control EditText, y es que no sólo nos permite editar texto plano sino también texto enriquecido o con formato. Veamos cómo y qué opciones tenemos, y para empezar comentemos algunas cosas sobre los objetos Spannable.


Interfaz Spanned
Un objeto de tipo Spanned es algo así como una cadena de caracteres (deriva de la interfaz CharSequence) en la que podemos insertar otros objetos a modo de marcas o etiquetas (spans) asociados a rangos de caracteres. De esta interfaz deriva la interfaz Spannable, que permite la modificación de estas marcas, y a su vez de ésta última deriva la interfaz Editable, que permite además la modificación del texto.


Aunque en el apartado en el que nos encontramos nos interesaremos principalmente por las marcas de formato de texto, en principio podríamos insertar cualquier tipo de objeto.

Existen muchos tipos de spans predefinidos en la plataforma que podemos utilizar para dar formato al texto, entre ellos:
  • TypefaceSpan. Modifica el tipo de fuente. 
  • StyleSpan. Modifica el estilo del texto (negrita, cursiva, …). 
  • ForegroudColorSpan. Modifica el color del texto. 
  • AbsoluteSizeSpan. Modifica el tamaño de fuente.
De esta forma, para crear un nuevo objeto Editable e insertar una marca de formato podríamos hacer lo siguiente:

//Creamos un nuevo objeto de tipo Editable
Editable str = Editable.Factory.getInstance().newEditable("Esto es un simulacro.");

//Marcamos cono fuente negrita la palabra "simulacro"
str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 11, 19, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

En este ejemplo estamos insertando un span de tipo StyleSpan para marcar un fragmento de texto con estilo negrita. Para insertarlo utilizamos el método setSpan(), que recibe como parámetro el objeto Spana insertar, la posición inicial y final del texto a marcar, y un flag que indica la forma en la que el span se podrá extender al insertarse nuevo texto.


Texto con formato en controles TextView y EditText

Hemos visto cómo crear un objeto Editable y añadir marcas de formato al texto que contiene, pero todo esto no tendría ningún sentido si no pudiéramos visualizarlo. Como ya podéis imaginar, los controles TextView y EditText nos van a permitir hacer esto. Vemos qué ocurre si asignamos al nuestro control EditText el objeto Editable que hemos creado antes:

txtTexto.setText(str);

Tras ejecutar este código veremos como efectivamente en el cuadro de texto aparece el mensaje con el formato esperado:

Ya hemos visto cómo asignar texto con y sin formato a un cuadro de texto, pero ¿qué ocurre a la hora de recuperar texto con formato desde el control?. Ya vimos que la función getText() devuelve un objeto de tipo Editable y que sobre éste podíamos hacer un toString(). Pero con esta solución estamos perdiendo todo el formato del texto, por lo que no podríamos por ejemplo salvarlo a una base de datos.

La solución a esto último pasa obviamente por recuperar directamente el objeto Editable y serializarlo de algún modo, mejor aún si es en un formato estandar. Pues bien, en Android este trabajo ya nos viene hecho de fábrica a través de la clase Html [API], que dispone de métodos para convertir cualquier objeto Spanned en su representación HTML equivalente. Veamos cómo. Recuperemos el texto de la ventana anterior y utilicemos el método Html.toHtml(Spannable) para convertirlo a formato HTML:

//Obtiene el texto del control con etiquetas de formato HTML
String aux2 = Html.toHtml(txtTexto.getText());

Haciendo esto, obtendríamos una cadena de texto como la siguiente, que ya podríamos por ejemplo almacenar en una base de datos o publicar en cualquier web sin perder el formato de texto establecido:

<p>Esto es un <b>simulacro</b>.</p>

La operación contraria también es posible, es decir, cargar un cuadro de texto de Android (EditText) o una etiqueta (TextView) a partir de un fragmento de texto en formato HTML. Para ello podemos utilizar el método Html.fromHtml(String) de la siguiente forma:

//Asigna texto con formato HTML
txtTexto.setText(
     Html.fromHtml("<p>Esto es un <b>simulacro</b>.</p>"),
     BufferType.SPANNABLE);

Desgraciadamente, aunque es de agradecer que este trabajo venga hecho de casa, hay que decir que tan sólo funciona de forma completa con las opciones de formato más básicas, como negritas, cursivas, subrayado o colores de texto, quedando no soportadas otras sorprendentemente básicas como el tamaño del texto, que aunque sí es correctamente traducido por el método toHtml(), es descartado por el método contrario fromHtml().


Control CheckBox [API]

Un control checkbox se suele utilizar para marcar o desmarcar opciones en una aplicación, y en Android está representado por la clase del mismo nombre, CheckBox. La forma de definirlo en nuestra interfaz y los métodos disponibles para manipularlos desde nuestro código son análogos a los ya comentados para el control ToggleButton.

De esta forma, para definir un control de este tipo en nuestro layout podemos utilizar el código siguiente, que define un checkbox con el texto “Márcame”:

<CheckBox android:id="@+id/ChkMarcame"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Márcame!" />

En cuanto a la personalización del control podemos decir que éste extiende [indirectamente] del control TextView, por lo que todas las opciones de formato ya comentadas en artículos anteriores son válidas también para este control.

En el código de la aplicación podremos hacer uso de los métodos isChecked() para conocer el estado del control, y setChecked(estado) para establecer un estado concreto para el control.

if (checkBox.isChecked()) {
    checkBox.setChecked(false);
}

En cuanto a los posibles eventos que puede lanzar este control, el más interesante es sin duda el que informa de que ha cambiado el estado del control, que recibe el nombre de onCheckedChanged. Para implementar las acciones de este evento podríamos utilizar por tanto la siguiente lógica:

final CheckBox cb = (CheckBox)findViewById(R.id.chkMarcame);
cb.setOnCheckedChangeListener(
    new CheckBox.OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView,
                                                  boolean isChecked) {
            if (isChecked) {
                cb.setText("Checkbox marcado!");
            }
            else {
                cb.setText("Checkbox desmarcado!");
        }
    }
});


Control RadioButton [API]

Al igual que los controles checkbox, un radiobutton puede estar marcado o desmarcado, pero en este caso suelen utilizarse dentro de un grupo de opciones donde una, y sólo una, de ellas debe estar marcada obligatoriamente, es decir, que si se marca una de ellas se desmarcará automáticamente la que estuviera activa anteriormente. En Android, un grupo de botones radiobutton se define mediante un elemento RadioGroup, que a su vez contendrá todos los elementos RadioButton necesarios.

<RadioGroup android:id="@+id/gruporb"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <RadioButton android:id="@+id/radio1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Opción 1" />
    <RadioButton android:id="@+id/radio2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Opción 2" />
</RadioGroup>

En primer lugar vemos cómo podemos definir el grupo de controles indicando su orientación (vertical u horizontal) al igual que ocurría por ejemplo con un LinearLayout. Tras esto, se añaden todos los objetos RadioButton necesarios indicando su ID mediante la propiedad android:id y su texto mediante android:text.

Una vez definida la interfaz podremos manipular el control desde nuestro código java haciendo uso de los diferentes métodos del control RadioGroup, los más importantes: check(id) para marcar una opción determinada mediante su ID, clearCheck() para desmarcar todas las opciones, y getCheckedRadioButtonId() que como su nombre indica devolverá el ID de la opción marcada (o el valor -1 si no hay ninguna marcada). Veamos un ejemplo:

final RadioGroup rg = (RadioGroup)findViewById(R.id.gruporb);
rg.clearCheck();
rg.check(R.id.radio1);
int idSeleccionado = rg.getCheckedRadioButtonId();

En cuanto a los eventos lanzados, al igual que en el caso de los checkboxes, el más importante será el que informa de los cambios en el elemento seleccionado, llamado también en este caso onCheckedChange. Vemos cómo tratar este evento del objeto RadioGroup:

final RadioGroup rg = (RadioGroup)findViewById(R.id.gruporb);
rg.setOnCheckedChangeListener(
    new RadioGroup.OnCheckedChangeListener() {
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            lblMensaje.setText("ID opcion seleccionada: " + checkedid);
        }
});

Veamos finalmente una imagen del aspecto de estos dos nuevos tipos de controles básicos que hemos comentado en este artículo:

sgoliver.net