À quoi sert la balise <merge> d'Android dans les mises en page XML?

325

J'ai lu le post de Romain Guy sur le <merge />tag, mais je ne comprends toujours pas à quel point c'est utile. S'agit-il d'une sorte de remplacement de la <Frame />balise, ou est-il utilisé comme ceci:

<merge xmlns:android="....">
<LinearLayout ...>
    .
    .
    .
</LinearLayout>
</merge>

alors <include />le code dans un autre fichier?

cesar
la source

Réponses:

586

<merge/> est utile car il peut supprimer les ViewGroups inutiles, c'est-à-dire les dispositions qui sont simplement utilisées pour encapsuler d'autres vues et ne servent à rien elles-mêmes.

Par exemple, si vous étiez dans <include/>une mise en page à partir d'un autre fichier sans utiliser la fusion, les deux fichiers pourraient ressembler à ceci:

layout1.xml:

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

layout2.xml:

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

qui est fonctionnellement équivalent à cette disposition unique:

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

Ce FrameLayout dans layout2.xml peut ne pas être utile. <merge/>aide à s'en débarrasser. Voici à quoi cela ressemble en utilisant la fusion (layout1.xml ne change pas):

layout2.xml:

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

Ceci est fonctionnellement équivalent à cette disposition:

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

mais puisque vous utilisez, <include/>vous pouvez réutiliser la mise en page ailleurs. Il ne doit pas être utilisé pour remplacer uniquement FrameLayouts - vous pouvez l'utiliser pour remplacer toute mise en page qui n'ajoute pas quelque chose d'utile à l'apparence / au comportement de votre vue.

blazeroni
la source
17
Dans cet exemple, vous pouvez simplement faire en sorte que layout2.xml ne contienne <TextView />rien d'autre.
Karu
21
Certes, un simple TextView pourrait être utilisé à la place dans layout2, mais ce serait alors une chose complètement différente et non utile comme exemple dans la réponse à cette question.
Dave
En conjonction avec la balise <include>, il est toujours utile d'utiliser la balise <merge>.
Anshul
38
@Karu: vous avez raison, la balise de fusion n'est pas nécessaire dans cet exemple mais c'est uniquement parce qu'il y a un élément dans layout2. Si layout2 avait plusieurs éléments, alors il DOIT avoir un nœud racine pour être XML valide et c'est à ce moment que la balise de fusion est utile.
gMale
3
Alors, comment spécifieriez-vous si <merge> a une orientation verticale ou horizontale? Et comment donnez-vous un layout_weight?
IgorGanapolsky
304

La balise include

La <include>balise vous permet de diviser votre mise en page en plusieurs fichiers: elle permet de gérer une interface utilisateur complexe ou trop longue.

Supposons que vous divisiez votre mise en page complexe à l'aide de deux fichiers d'inclusion comme suit:

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>

Ensuite, vous devez écrire include1.xmlet include2.xml.

Gardez à l'esprit que le xml des fichiers inclus est simplement vidé dans votre top_level_activitymise en page au moment du rendu (un peu comme la #INCLUDEmacro pour C).

Les fichiers d'inclusion sont des fichiers XML de mise en page plain jane.

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"/>

... et 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" />

Voir? Rien d'extraordinaire. Notez que vous devez toujours déclarer l'espace de noms Android avec xmlns:android="http://schemas.android.com/apk/res/android.

La version rendue de top_level_activity.xml est donc:

<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>

Dans votre code java, tout cela est transparent: findViewById(R.id.textView1)dans votre classe d'activité retourne le widget correct (même si ce widget a été déclaré dans un fichier xml différent de la mise en page de l'activité).

Et la cerise sur le gâteau: l' éditeur visuel gère la chose avec nage. La disposition de niveau supérieur est rendue avec le xml inclus.

L'intrigue se corse

Comme un fichier inclus est un fichier xml de mise en page classique, cela signifie qu'il doit avoir un élément supérieur. Donc, si votre fichier doit inclure plus d'un widget, vous devrez utiliser une mise en page.

Disons qu'il en include1.xmla maintenant deux TextView: une mise en page doit être déclarée. Choisissons un 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>

Le top_level_activity.xml sera rendu comme:

<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>

Mais attendez que les deux niveaux de LinearLayoutsoient redondants !

En effet, les deux imbriqués LinearLayoutne servent à rien car les deux TextViewpourraient être inclus sous layout1pour exactement le même rendu .

Alors, que pouvons-nous faire?

Entrez la balise de fusion

La <merge>balise est juste une balise factice qui fournit un élément de niveau supérieur pour faire face à ce type de problèmes de redondance.

Maintenant, include1.xml devient:

<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>

et maintenant top_level_activity.xml est rendu comme:

<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>

Vous avez enregistré un niveau de hiérarchie, évitez une vue inutile: Romain Guy dort déjà mieux.

N'êtes-vous pas plus heureux maintenant?

Le nom est carl
la source
23
Excellente description.
RichieHH
4
explique très clairement, devrait être choisi comme réponse
lalitm
2
Excellent, cela devrait sans aucun doute être la réponse acceptée.
gaurav jain
1
n'a pas compris quelque chose .. que se passe-t-il si le LinearLayout externe est vertical par exemple, mais les 2 vues de texte dans le include1.xml étaient censées être horizontales? la fusion dans ce cas ne sauvegarde pas leur mise en page que je voulais. Que peut-on y faire?
Yonatan Nir du
La fusion @YonatanNir n'est pas ce dont vous avez besoin dans votre cas, clairement. si vous avez vraiment besoin d'aplatir la hiérarchie des vues, alors vous pouvez peut-être utiliser RelativeLayoutou dessiner les vues manuellement
Abhijit
19

blazeroni l'a déjà dit assez clairement, je veux juste ajouter quelques points.

  • <merge> est utilisé pour optimiser les mises en page. Il est utilisé pour réduire l'imbrication inutile.
  • lorsqu'une présentation contenant une <merge>balise est ajoutée dans une autre présentation, le <merge>nœud est supprimé et sa vue enfant est ajoutée directement au nouveau parent.
Anshul
la source
10

Pour avoir une connaissance plus approfondie de ce qui se passe, j'ai créé l'exemple suivant. Jetez un œil aux fichiers activity_main.xml et content_profile.xml .

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>

Ici, le fichier de mise en page entier une fois gonflé ressemble à ceci.

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

Vérifiez qu'il existe un LinearLayout à l'intérieur du LinearLayout parent qui ne sert à rien et est redondant. Un coup d'œil à la mise en page via l'outil Inspecteur de mise en page l'explique clairement.

entrez la description de l'image ici

content_profile.xml après la mise à jour du code pour utiliser la fusion au lieu d'un ViewGroup comme 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>

Maintenant, notre disposition ressemble à ceci

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

Ici, nous voyons que le LinearLayout ViewGroup redondant est supprimé. L'outil Inspecteur de présentation donne maintenant la hiérarchie de présentation suivante.

entrez la description de l'image ici

Essayez donc toujours d'utiliser la fusion lorsque votre disposition parent peut positionner vos dispositions enfant, ou plus précisément d'utiliser la fusion lorsque vous comprenez qu'il va y avoir un groupe de vues redondant dans la hiérarchie.

capt.swag
la source
5

Une autre raison d'utiliser la fusion est lors de l'utilisation de groupes de vues personnalisés dans ListViews ou GridViews. Au lieu d'utiliser le modèle viewHolder dans un adaptateur de liste, vous pouvez utiliser une vue personnalisée. La vue personnalisée gonflerait un fichier XML dont la racine est une balise de fusion. Code pour l'adaptateur:

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;
    }
}

voici le groupe de vues personnalisé:

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
    }
}

et voici le 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
la source
Voulez-vous dire que si vous utilisiez un RelativeLayout dans votre fichier XML et votre ViewGroup personnalisé hérité de RelativeLayout, il y aurait alors deux RelativeLayouts, l'un imbriqué dans l'autre?
Scott Biggs