Trucos de diseño: Fusión de diseños

Los artículos mostraban cómo utilizar la etiqueta <include /> en los diseños XML, para reutilizar y compartir su código de diseño. Este artículo explica la etiqueta <merge /> y cómo se complementa con la etiqueta <include />.

La etiqueta <merge /> se creó con el fin de optimizar los diseños de Android reduciendo el número de niveles en los árboles de vistas. Es más fácil entender el problema que resuelve esta etiqueta viendo un ejemplo. El siguiente diseño XML declara un diseño que muestra una imagen con su título encima. La estructura es bastante simple; se utiliza un FrameLayout para apilar un TextView encima de un ImageView:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /></FrameLayout>

Este diseño se visualiza muy bien y no parece haber nada malo en él:

Se utiliza un FrameLayout para superponer un título encima de una imagen

Las cosas se ponen más interesantes cuando se inspecciona el resultado con HierarchyViewer. Si observamos el árbol resultante, nos daremos cuenta de que el FrameLayout definido en nuestro archivo XML (resaltado en azul) es el único hijo de otro FrameLayout:

Un layout con un solo hijo de las mismas dimensiones puede ser eliminado

Dado que nuestro FrameLayout tiene la misma dimensión que su padre, por la virtud de usar las restricciones fill_parent, y no define ningún fondo, relleno extra o una gravedad, es totalmente inútil. Sólo hemos hecho la interfaz de usuario más compleja sin ninguna razón. Pero, ¿cómo podríamos deshacernos de este FrameLayout? Después de todo, los documentos XML requieren una etiqueta raíz y las etiquetas en los diseños XML siempre representan instancias de la vista.

Ahí es donde la etiqueta <merge /> resulta útil. Cuando elLayoutInflater encuentra esta etiqueta, se la salta y añade los hijos de <merge /> al padre <merge />. ¿Te has confundido? Reescribamos nuestro diseño XML anterior sustituyendo la etiqueta FrameLayout por <merge />:

<merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /></merge>

Con esta nueva versión, tanto el TextView como elImageView se añadirán directamente al nivel superiorFrameLayout. El resultado será visualmente el mismo pero la jerarquía de vistas es más sencilla:

Jerarquía de vistas optimizada mediante la etiqueta merge

Obviamente, el uso de <merge /> funciona en este caso porque el padre de la vista de contenido de una actividad es siempre un FrameLayout. No podrías aplicar este truco si tu diseño utilizara una etiqueta LinearLayout como raíz, por ejemplo. Sin embargo, la etiqueta <merge /> puede ser útil en otras situaciones. Por ejemplo, funciona perfectamente cuando se combina con la etiqueta <include />. También puedes usar <merge/> cuando creas una vista compuesta personalizada. Veamos cómo podemos utilizar esta etiqueta para crear una nueva vista llamada OkCancelBar que simplemente muestra dos botones con etiquetas personalizables. También puedes descargar el código fuente completo de este ejemplo. Este es el XML utilizado para mostrar esta vista personalizada sobre una imagen:

<merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:okCancelBar="http://schemas.android.com/apk/res/com.example.android.merge"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <com.example.android.merge.OkCancelBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:paddingTop="8dip" android:gravity="center_horizontal" android:background="#AA000000" okCancelBar:okLabel="Save" okCancelBar:cancelLabel="Don't save" /></merge>

Este nuevo diseño produce el siguiente resultado en un dispositivo:

Creación de una vista personalizada con la etiqueta merge

El código fuente de OkCancelBar es muy sencillo porque los dos botones se definen en un archivo XML externo, cargado mediante unLayoutInflate. Como puedes ver en el siguiente fragmento, el XMLlayout R.layout.okcancelbar se infla con elOkCancelBar como padre:

public class OkCancelBar extends LinearLayout { public OkCancelBar(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(HORIZONTAL); setGravity(Gravity.CENTER); setWeightSum(1.0f); LayoutInflater.from(context).inflate(R.layout.okcancelbar, this, true); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OkCancelBar, 0, 0); String text = array.getString(R.styleable.OkCancelBar_okLabel); if (text == null) text = "Ok"; ((Button) findViewById(R.id.okcancelbar_ok)).setText(text); text = array.getString(R.styleable.OkCancelBar_cancelLabel); if (text == null) text = "Cancel"; ((Button) findViewById(R.id.okcancelbar_cancel)).setText(text); array.recycle(); }}

Los dos botones se definen en el siguiente XML layout. Como puede ver, utilizamos la etiqueta <merge /> para añadir los dos botones directamente alOkCancelBar. Cada botón se incluye desde el mismo archivo XMLlayout externo para facilitar su mantenimiento; simplemente anulamos su id:

<merge xmlns:android="http://schemas.android.com/apk/res/android"> <include layout="@layout/okcancelbar_button" android:id="@+id/okcancelbar_ok" /> <include layout="@layout/okcancelbar_button" android:id="@+id/okcancelbar_cancel" /></merge>

Hemos creado una vista personalizada flexible y fácil de mantener que genera una jerarquía de vistas eficiente:

La jerarquía resultante es simple y eficiente

La etiqueta <merge /> es extremadamente útil y puede hacer maravillas en su código. Sin embargo, adolece de un par de limitaciones:

  • <merge />sólo puede utilizarse como etiqueta raíz de un diseño XML
  • Cuando se infla un diseño que comienza con un <merge />, debe especificarse un padre ViewGroup y debe establecerse attachToRoot en true (véase la documentación del método inflate(int, android.view.ViewGroup, boolean))

Leave a Reply