jueves, 2 de octubre de 2014

Plantillas

Una plantilla nos permite definir el diseño de la interfaz de usuario de una actividad o de un widget de aplicación. La declaración de una plantilla se puede hacer de dos formas:
  • Desde un archivo XML. Android nos proporciona de un sencillo espacio de nombres XML basado en clases del entorno de trabajo.
  • Desde el código en tiempo de ejecución. Podríamos crear las vistas necesarias, definir sus dependencias y especificar sus propiedades desde el código si fuera necesario.
El entorno de trabajo de Android nos ofrece la posibilidad de utilizar cualesquiera de las dos formas anteriores para gestionar nuestra interfaz de usuario. Por ejemplo, podríamos crear un diseño por defecto en XML que incluyera los elementos visuales que queremos mostrar al usuario y más tarde, podríamos vernos obligados a añadir, modificar o eliminar ciertos elementos de la interfaz en tiempo de ejecución como consecuencia de la interacción con el usuario.

Declarar nuestra interfaz de usuario en un archivo XML nos garantiza la separación entre la presentación y el código que controla su comportamiento (MVC). Por otro lado, que la definición de la interfaz se haga en un archivo XML externo a nuestro código fuente significa que éste no tendrá que compilarse cada vez que hagamos modificaciones en el diseño de la plantilla.

Podremos definir plantillas XML distintas en función de la orientación o el tamaño de la pantalla, el idioma, etc. Además, escribir las plantillas en formato XML nos facilitará su lectura y su depuración.

Como norma general, el espacio de nombres utilizado en el XML de una plantilla está estrechamente ligado a la jerarquía de clases, métodos y propiedades definidos en el entorno de trabajo Android. De hecho, la correspondencia es prácticamente idéntica salvo algunas excepciones: el elemento XML <EditText> tiene un atributo text que se correspondería con el método EditText.setText() del entorno de trabajo, por ejemplo.

Escribiendo la plantilla.

Cada plantilla está compuesta por un conjunto de vistas relacionadas entre sí de manera jerárquica (árbol). El elemento raíz de una plantilla XML puede ser tanto un objeto de tipo View, como un objeto de tipo ViewGroup. Por ejemplo, la siguiente plantilla distribuye verticalmente un texto y un botón:
<?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:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>
Las plantillas se almacenan en la carpeta de recursos res/layout/.

Cargando la plantilla.

La plantilla asociada a una actividad se carga desde el método onCreate(). Para ello, utilizaremos el método setContentView() al que le pasaremos como parámetro el ID de recurso asociado a la plantilla: R.layout.nombre_archivo_plantilla. Por ejemplo, si queremos cargar la plantilla res/layout/main_layout.xml desde nuestra actividad, lo haremos de la siguiente manera:
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

Atributos.

Las vistas de una plantilla cuentan con una serie de atributos. Algunos son específicos de la vista (el atributo textSize para una vista TextView, por ejemplo) y otros son heredados. También los hay que son comunes a todas las vistas (el atributo id, por ejemplo). Otros sin embargo, describen el posicionamiento y tamaño de la vista con respecto a su vista padre.

ID.

Las vistas pueden tener un identificador único dentro de la plantilla. Cuando compilamos nuestra aplicación, internamente se trabajará con un valor numérico aunque nosotros hayamos especificado un nombre en la plantilla. Este atributo es común a todas las vistas y lo utilizaremos a menudo. La sintaxis para este atributo sería la siguiente:
android:id="@+id/my_button"
donde:
  • El carácter @ indica que el resto de la expresión debe ser interpretada como un identificador de recurso.
  • El carácter + indica que se trata de un recurso nuevo y que se cree un nuevo identificador para él en la clase especial R.

El entorno de trabajo Android define ciertos identificadores especiales que podremos utilizar en nuestros diseños. Para referenciar un identificador de este tipo utilizaremos el espacio de nombres android de la forma:
android:id="@android:id/empty"
Para hacer referencia a un identificador de este tipo desde el código, haremos uso de la clase android.R.

Para usar una vista desde nuestro código fuente, seguiremos los siguientes pasos:
  1. Definimos la vista en el archivo de la plantilla con su id correspondiente:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. Asignamos la plantilla a la actividad desde su método onCreate() y accedemos a la vista  que nos interese utilizando el método findViewById():
    Button myButton = (Button) findViewById(R.id.my_button);
    

El atributo id es imprescindible a la hora de utilizar vistas de grupo con distribución relativa RelativeLayout. En una plantilla de este tipo, cada vista contenida define su posición en función de la posición de otra vista hija y para ello ésta última debe contar con un id para que se pueda hacer referencia a ella.

El id de una vista no tiene por qué ser único.

Atributos de posicionamiento y tamaño.

Una vista de grupo cuenta con una serie de atributos que serán heredados por sus vistas hija para que éstas puedan especificar sus propios tamaños y posiciones en relación a su contenedor. El nombre de estos atributos tiene como prefijo la palabra layout (layout_algo). Como podemos observar en la figura 1, una vista de grupo LinearLayout (raíz de la plantilla) define parámetros de posicionamiento y tamaño para cada vista hija (incluyendo la vista de grupo RelativeLayout):
Vistas y sus atributos de posicionamiento y tamaño heredados
Imagen 1. Jerarquía de las vistas de una plantilla con sus atributos de posicionamiento y tamaño heredados.

El código XML para la plantilla de la imagen 1 podría quedar:
<LinearLayout>
    <RelativeLayout atributoLinearLayout="valor" >
        <View layout_width="valor&quot layout_height="valor&quot />
        <View layout_width="valor&quot layout_height="valor&quot />
        <View layout_width="valor&quot layout_height="valor&quot />
    </RelativeLayout>
    <View layout_width="valor&quot layout_height="valor&quot />
    <View layout_width="valor&quot layout_height="valor&quot />
</LinearLayout>
Todas las vistas de grupo definen los atributos layout_width y layout_height. Cada una de las vistas contenidas tendrá que asignar un valor a estos dos atributos. Otros atributos heredados también pueden hacer referencia a márgenes y bordes opcionales.

Para especificar el ancho y el alto de una vista podremos hacerlo dando una medida exacta o mediante constantes predefinidas. En la mayoría de los casos, utilizaremos estas constantes:
  • wrap_content. Ajusta el tamaño de la vista a su contenido.
  • match_content (también llamado fill_parent antes de la versión 8 de la API). Ajusta el tamaño de la vista al tamaño de su vista padre.

Especificar el ancho y el alto de una vista en pixeles por ejemplo, no es muy recomendable. En su lugar, utilizaremos medias relativas independientes de los aspectos físicos de la pantalla del dispositivo. Valores dados en función de la densidad de la pantalla (dp), wrap_content o match_parent, son mejores opciones para mostrar el contenido a lo largo de un amplio abanico de dispositivos con diferentes tamaños de pantalla.

Posicionamiento de las vistas.

Podemos ver a las vistas como rectángulos que cuentan con un posicionamiento y unas dimensiones, ambos expresados en pixeles.

Para obtener el posicionamiento de una vista desde el código utilizaremos los métodos getLeft() para obtener la coordenada X y getTop() para obtener la coordenada Y. El valor devuelto es relativo al vértice superior izquierdo del rectángulo que representa a la vista. Ambos métodos nos devuelven las coordenadas relativas al borde superior de la vista contenedora. Por ejemplo, si el método getLeft() nos devuelve el valor 20, significa que la vista estará situada 20 pixeles a la derecha del borde izquierdo de su vista padre.

También podremos utilizar los métodos complementarios getRight() y getBottom() para obtener el posicionamiento de una vista. En este caso, el valor devuelto es relativo al vértice inferior derecho del rectángulo que representa la vista. Como ejemplo, el valor devuelto por el método getRight() es equivalente al valor devuelto por la expresión getLeft() + getWidth().

Tamaño, rellenos y márgenes.

El tamaño de una vista viene determinado por sus dimensiones de ancho y alto. Las dimensiones de una vista pueden ser especificadas a través de:
  • Dimensiones relativas a la vista contenedora. Estas dimensiones definen el tamaño que queremos que tenga nuestra vista con respecto a su vista padre. Para acceder a estos valores usaremos los métodos getMeasuredWidth() y getMeasuredHeight().
  • Dimensiones relativas a la propia vista. Definen el tamaño de la vista en pantalla. Estos valores pueden, aunque no tiene por qué ser así, ser diferentes a los valores anteriores. Para obtener estos valores usaremos los métodos getWidth() y getHeight().

A la hora de calcular las dimensiones de una vista, tendremos que tener en cuenta su relleno. El relleno es el margen interior que guarda el contenido con respecto a los bordes de su vista. Para asignar el relleno a una vista, usaremos el método setPadding(int, int, int, int). Para obtener los distintos rellenos de una vista, utilizaremos los métodos getPaddingLeft(), getPaddingRight(), getPaddingTop() y getPaddingBottom().

Solo las vistas de grupo (ViewGroup) tienen márgenes.

Plantillas comunes.

Los distintos tipos plantillas se diferencian por la manera en la que distribuyen sus vistas contenidas. El entorno de trabajo Android incorpora una serie de vistas de grupo de uso muy común y que veremos a continuación.

Nota. Aunque podemos especificar los niveles de anidación que queramos en una plantilla, deberíamos crear nuestros diseños con una jerarquía lo más plana posible. Cuantos menos niveles de anidación haya, mayor rapidez a la hora de pintar la plantilla.

Plantillas con distribución lineal.

Nos permiten organizar el contenido en una única fila orientada de manera horizontal o vertical. Si el tamaño de la ventana es mayor al tamaño de la pantalla, se mostrará la barra de desplazamiento.


Plantillas con distribución relativa.

Nos permiten posicionar una vista hija con respecto a otra vista hija o con respecto a su propio borde superior.


Vistas de grupo web.

Sirven para mostrar páginas web.

Vistas con adaptadores.

Cuando el contenido de nuestra vista es dinámico y no lo conocemos de antemano, utilizaremos vistas de tipo AdapterView que nos permitirán actualizar sus contenidos en tiempo de ejecución. Cada subclase de AdapterView utilizan un adaptador para enlazar la fuente de datos a su vista. El adaptador Adapter se comporta como un intermediario entre la fuente de datos y la vista AdapterView, es decir, el adaptador será el encargado de obtener los datos de la fuente de datos (un array o una base de datos) y de generar la vistas para cada uno de los resultados que formarán nuestra vista final AdapterView.

Las vistas con adaptadores más comunes son:
  • ListView. Que muestra los resultados en una lista. Cuenta con barra de desplazamiento.
  • GridView. Que muestra los resultados en una cuadrícula. Cuenta con barra de desplazamiento.

ListView
GridView

Publicar datos en la vista.

Podemos rellenar con datos una vista de tipo ListView o GridView asignándole un adaptador Adapter.

Contamos con múltiples tipos de adaptadores que nos serán muy útiles a la hora de obtener los datos de cierto tipo de fuentes. Los dos adaptadores más utilizados son:

ArrayAdapter
Utilizaremos este adaptador cuando los datos se encuentren almacenados en un array. Por defecto, ArrayAdapter crea para cada elemento del array una vista de tipo TextView a la que le asigna como texto el valor devuelto por el método toString() del propio elemento.

Por ejemplo, si tenemos un array de cadenas de texto y las queremos mostrar en una vista de tipo ListView, crearemos un adaptador de tipo ArrayAdapter especificando en su constructor los argumentos necesarios:
ArrayAdapter adapter = new ArrayAdapter(this,
        android.R.layout.simple_list_item_1, myStringArray);
donde:
  • this es el contexto de la aplicación.
  • android.R.layout.simple_list_item_1. Es la una plantilla predefinida que Android nos facilita y que utilizaremos en este caso para visualizar cada elemento del array.
  • myStringArray. El array con las cadenas de texto.

Una vez creado el adaptador, se lo asignamos a la vista ListView con el método setAdapter():
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

Para personalizar el valor mostrado para cada uno los elementos, podemos modificar la implementación del método toString de su clase. O por ejemplo, para crear una vista donde cada elemento de la lista no sea una vista de tipo ImageView, creamos una subclase de ArrayAdapter e implementaremos su método getView() para que devuelva una instancia de tipo ImageView.
SimpleCursorAdapter
Utilizaremos este adaptador cuando la fuente de datos provenga de una consulta a un proveedor de contenido y el valor devuelto sea un objeto de tipo Cursor. A la hora de utilizar este cursor, tendremos que especificar la plantilla que utilizaremos para mostrar cada registro y su correspondencia con columnas del cursor. Por ejemplo, si queremos crear un listado de personas con sus nombres y números de teléfono, vamos a necesitar: la consulta necesaria para obtener el cursor, un array con las columnas del cursor que queremos mostrar y un array con los identificadores a cada una de las vistas de la plantilla que se encargarán de mostrar los datos de cada columna:
String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};
A la hora de crear el adaptador SimpleCursorAdapter, especificaremos como parámetro la plantilla que usaremos para mostrar los resultados junto a los dos arrays anteriores:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);
El adaptador SimpleCursorAdapter construye la vista para cada registro del cursor utilizando la plantilla especificada ayudándose para ello de los arrays fromColumns y toView.
Cada vez que modifiquemos la fuente de datos de un adaptador y queremos que esos cambios se vean reflejados en la interfaz , utilizaremos el método notifyDataSetChanged().

Selección de elementos.

Podemos especificar qué hacer cuando el usuario pulsa sobre uno de los elementos de nuestra vista AdapterView implementando la interfaz AdapterView.OnItemClickListener. Por ejemplo:
// Creamos el objeto con la implementación de una clase anónima.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Código que se ejecutará cada vez que el usuario pulsa un elemento.
    }
};

...

// Desde alguna parte de nuestro código, asignamos la implementación del adaptador
// a la vista adaptada.
listView.setOnItemClickListener(mMessageClickedHandler);

...

No hay comentarios:

Publicar un comentario