¿Cuál es el propósito de la etiqueta <merge> de Android en diseños XML?

Respuestas:

586

<merge/> es útil porque puede deshacerse de ViewGroups innecesarios, es decir, diseños que simplemente se utilizan para ajustar otras vistas y no sirven para nada.

Por ejemplo, si estuviera en <include/>un diseño desde otro archivo sin usar la combinación, los dos archivos podrían verse así:

layout1.xml:

<FrameLayout>
   <include layout="@layout/layout2"/>
</FrameLayout>

layout2.xml:

<FrameLayout>
   <TextView />
   <TextView />
</FrameLayout>

que es funcionalmente equivalente a este diseño único:

<FrameLayout>
   <FrameLayout>
      <TextView />
      <TextView />
   </FrameLayout>
</FrameLayout>

Es posible que FrameLayout en layout2.xml no sea útil. <merge/>Ayuda a deshacerse de él. Esto es lo que parece usar merge (layout1.xml no cambia):

layout2.xml:

<merge>
   <TextView />
   <TextView />
</merge>

Esto es funcionalmente equivalente a este diseño:

<FrameLayout>
   <TextView />
   <TextView />
</FrameLayout>

pero como lo está utilizando <include/>, puede reutilizar el diseño en otro lugar. No tiene que usarse para reemplazar solo FrameLayouts: puede usarlo para reemplazar cualquier diseño que no agregue algo útil a la forma en que se ve / se comporta su vista.

blazeroni
fuente
17
En este ejemplo, podría hacer que layout2.xml contenga simplemente <TextView />nada más.
Karu
21
Es cierto que se podría usar un TextView simple en su lugar en layout2, sin embargo, eso sería algo completamente diferente y no sería útil como ejemplo en la respuesta a esta pregunta.
Dave
Junto con la etiqueta <include> siempre es útil usar la etiqueta <merge>.
Anshul
38
@Karu: tienes razón, la etiqueta de fusión no es necesaria en este ejemplo, pero eso es solo porque hay un elemento en el diseño2. Si layout2 tenía varios elementos, DEBE tener un nodo raíz para ser XML válido y es entonces cuando la etiqueta de combinación resulta útil.
gMale
3
Entonces, ¿cómo especificaría si <merge> tiene orientación vertical u horizontal? ¿Y cómo le das a un layout_weight?
IgorGanapolsky
304

La etiqueta de inclusión

La <include>etiqueta le permite dividir su diseño en varios archivos: ayuda a lidiar con una interfaz de usuario compleja o demasiado larga.

Supongamos que divide su diseño complejo utilizando dos archivos de inclusión de la siguiente manera:

top_level_activity.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout1" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- First include file -->
    <include layout="@layout/include1.xml" />

    <!-- Second include file -->
    <include layout="@layout/include2.xml" />

</LinearLayout>

Entonces necesitas escribir include1.xmly include2.xml.

Tenga en cuenta que el xml de los archivos de inclusión simplemente se vierte en su top_level_activitydiseño en el momento del renderizado (muy parecido a la #INCLUDEmacro para C).

Los archivos de inclusión son xml de diseño jane simple.

include1.xml :

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/textView1"
    android:text="First include"
    android:textAppearance="?android:attr/textAppearanceMedium"/>

... e include2.xml :

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/button1"
    android:text="Button" />

¿Ver? Nada sofisticado. Tenga en cuenta que aún tiene que declarar el espacio de nombres de Android con xmlns:android="http://schemas.android.com/apk/res/android.

Entonces, la versión renderizada de top_level_activity.xml es:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout1" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- First include file -->
    <TextView
        android:id="@+id/textView1"
        android:text="First include"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

    <!-- Second include file -->
    <Button
        android:id="@+id/button1"
        android:text="Button" />


</LinearLayout>

En su código java, todo esto es transparente: findViewById(R.id.textView1)en su clase de actividad devuelve el widget correcto (incluso si ese widget se declaró en un archivo xml diferente del diseño de la actividad).

Y la guinda del pastel: el editor visual maneja la cosa maravillas. El diseño de nivel superior se representa con el xml incluido.

La trama se complica

Como un archivo de inclusión es un archivo xml de diseño clásico, significa que debe tener un elemento superior. Entonces, en caso de que su archivo necesite incluir más de un widget, deberá usar un diseño.

Digamos que include1.xmlahora tiene dos TextView: se debe declarar un diseño. Elijamos a LinearLayout.

include1.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout2" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:text="Second include"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

    <TextView
        android:id="@+id/textView2"
        android:text="More text"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

</LinearLayout>

El top_level_activity.xml se representa como:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout1" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- First include file -->
    <LinearLayout 
        android:id="@+id/layout2" 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

       <TextView
            android:id="@+id/textView1"
            android:text="Second include"
            android:textAppearance="?android:attr/textAppearanceMedium"/>

       <TextView
            android:id="@+id/textView2"
            android:text="More text"
            android:textAppearance="?android:attr/textAppearanceMedium"/>

   </LinearLayout>

     <!-- Second include file -->
   <Button
        android:id="@+id/button1"
        android:text="Button" />

</LinearLayout>

Pero espera los dos niveles de LinearLayout sean redundantes !

De hecho, los dos anidados LinearLayoutno sirven a ningún propósito que los dos TextViewpodrían incluirse bajo layout1para exactamente la misma representación .

Entonces, ¿qué podemos hacer?

Ingrese la etiqueta de fusión

los <merge> etiqueta es solo una etiqueta ficticia que proporciona un elemento de nivel superior para tratar este tipo de problemas de redundancia.

Ahora include1.xml se convierte en:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:id="@+id/textView1"
        android:text="Second include"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

    <TextView
        android:id="@+id/textView2"
        android:text="More text"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

</merge>

y ahora top_level_activity.xml se representa como:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout1" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- First include file --> 
    <TextView
        android:id="@+id/textView1"
        android:text="Second include"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

    <TextView
        android:id="@+id/textView2"
        android:text="More text"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

    <!-- Second include file -->
    <Button
        android:id="@+id/button1"
        android:text="Button" />

</LinearLayout>

Has guardado un nivel de jerarquía, evita una vista inútil: Romain Guy ya duerme mejor.

¿No estás más feliz ahora?

Mi nombre es carl
fuente
23
Excelente descripcion.
RichieHH
44
explica muy claramente, debe ser elegido como la respuesta
lalitm
2
Excelente, sin duda esta debería ser la respuesta aceptada.
gaurav jain
1
no entendí algo ... ¿qué pasa si el LinearLayout externo es vertical, por ejemplo, pero se suponía que las 2 vistas de texto en el include1.xml eran horizontales? la fusión en ese caso no guarda el diseño que quería. ¿Qué se puede hacer al respecto?
Yonatan Nir
La fusión de @YonatanNir no es lo que necesita en su caso, claramente. si realmente necesita aplanar la jerarquía de vistas, entonces tal vez pueda usar RelativeLayouto dibujar las vistas manualmente
Abhijit
19

Blazeroni ya lo dejó bastante claro, solo quiero agregar algunos puntos.

  • <merge> se utiliza para optimizar diseños, se utiliza para reducir la anidación innecesaria.
  • cuando un diseño que contiene una <merge>etiqueta se agrega a otro diseño, el <merge>nodo se elimina y su vista secundaria se agrega directamente al nuevo padre.
Anshul
fuente
10

Para tener un conocimiento más profundo de lo que está sucediendo, creé el siguiente ejemplo. Eche un vistazo a activity_main.xml y content_profile.xml archivos .

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/content_profile" />

</LinearLayout>

content_profile.xml

<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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Howdy" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hi there" />

</LinearLayout>

Aquí, todo el archivo de diseño cuando se infla se ve así.

<LinearLayout>
    <LinearLayout>
        <TextView />
        <TextView />
    </LinearLayout>
</LinearLayout>

Vea que hay un LinearLayout dentro del LinearLayout padre que no sirve para nada y es redundante. Una mirada al diseño a través de la herramienta Inspector de diseño explica claramente esto.

ingrese la descripción de la imagen aquí

content_profile.xml después de actualizar el código para usar merge en lugar de un ViewGroup como LinearLayout.

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Howdy" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hi there" />

</merge>

Ahora nuestro diseño se ve así

<LinearLayout>
    <TextView />
    <TextView />
</LinearLayout>

Aquí vemos que se elimina LinearLayout ViewGroup redundante. Ahora la herramienta Inspector de diseño ofrece la siguiente jerarquía de diseño.

ingrese la descripción de la imagen aquí

Por lo tanto, siempre intente utilizar la combinación cuando el diseño principal puede posicionar los diseños secundarios, o más precisamente, utilice la combinación cuando comprenda que habrá un grupo de vista redundante en la jerarquía.

capt.swag
fuente
5

Otra razón para usar la combinación es cuando se usan grupos de vista personalizados en ListViews o GridViews. En lugar de usar el patrón viewHolder en un adaptador de lista, puede usar una vista personalizada. La vista personalizada inflará un xml cuya raíz es una etiqueta de fusión. Código para adaptador:

public class GridViewAdapter extends BaseAdapter {
     // ... typical Adapter class methods
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
        WallpaperView wallpaperView;
        if (convertView == null)
           wallpaperView = new WallpaperView(activity);
        else
            wallpaperView = (WallpaperView) convertView;

        wallpaperView.loadWallpaper(wallpapers.get(position), imageWidth);
        return wallpaperView;
    }
}

Aquí está el grupo de vista personalizado:

public class WallpaperView extends RelativeLayout {

    public WallpaperView(Context context) {
        super(context);
        init(context);
    }
    // ... typical constructors

    private void init(Context context) {
        View.inflate(context, R.layout.wallpaper_item, this);
        imageLoader = AppController.getInstance().getImageLoader();
        imagePlaceHolder = (ImageView) findViewById(R.id.imgLoader2);
        thumbnail = (NetworkImageView) findViewById(R.id.thumbnail2);
        thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP);
    }

    public void loadWallpaper(Wallpaper wallpaper, int imageWidth) {
        // ...some logic that sets the views
    }
}

y aquí está el XML:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView
        android:id="@+id/imgLoader"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerInParent="true"
        android:src="@drawable/ico_loader" />

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</merge>
mmienko
fuente
¿Está insinuando que si usara un RelativeLayout en su archivo XML y su ViewGroup personalizado heredara de RelativeLayout que habría dos RelativeLayouts, uno anidado en el otro?
Scott Biggs