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:
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
:
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:
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:
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 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 padreViewGroup
y debe establecerseattachToRoot
entrue
(véase la documentación del métodoinflate(int, android.view.ViewGroup, boolean)
)
Leave a Reply