Какова цель тега Android <merge> в макетах XML?

325

Я прочитал пост Романа Гая о <merge />теге, но до сих пор не понимаю, насколько он полезен. Является ли это заменой <Frame />тега или используется так:

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

тогда <include />код в другом файле?

Сезар
источник

Ответы:

586

<merge/> полезен, потому что он может избавиться от ненужных ViewGroups, то есть макетов, которые просто используются, чтобы обернуть другие представления и сами не служат цели.

Например, если вы <include/>использовали макет из другого файла без слияния, эти два файла могут выглядеть примерно так:

layout1.xml:

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

layout2.xml:

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

который функционально эквивалентен этой единственной компоновке:

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

Этот FrameLayout в layout2.xml может быть бесполезен. <merge/>помогает избавиться от этого. Вот как это выглядит при использовании слияния (layout1.xml не меняется):

layout2.xml:

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

Это функционально эквивалентно этому макету:

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

но так как вы используете, <include/>вы можете использовать макет в другом месте. Его не нужно использовать для замены только FrameLayouts - вы можете использовать его для замены любого макета, который не добавляет чего-либо полезного к тому, как ваш вид выглядит / ведет себя.

blazeroni
источник
17
В этом примере вы можете просто сделать layout2.xml просто <TextView />ничем другим.
Кару
21
Да, вместо этого в layout2 можно использовать простой TextView, однако тогда это будет совсем другое дело и не будет полезным в качестве примера в ответе на этот вопрос.
Дэйв
В сочетании с тегом <include> всегда полезно использовать тег <merge>.
Anshul
38
@Karu: вы правы, тег слияния в этом примере не нужен, но это только потому, что в layout2 есть один элемент. Если layout2 имеет несколько элементов, тогда он ДОЛЖЕН иметь корневой узел, чтобы быть допустимым XML, и именно тогда тег merge пригодится.
gMale
3
Итак, как бы вы указали, если <merge> имеет вертикальную или горизонтальную ориентацию? А как вы даете layout_weight?
Игорь Ганапольский
304

Тег включения

<include>Тег позволяет разделить ваш макет на несколько файлов: это помогает дело с комплексным или слишком длинным пользовательским интерфейсом.

Предположим, вы разбили сложный макет, используя два включаемых файла следующим образом:

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>

Тогда вам нужно написать include1.xmlи include2.xml.

Имейте в виду, что xml из включаемых файлов просто выгружается в ваш top_level_activityмакет во время рендеринга (почти как #INCLUDEмакрос для C).

Включаемые файлы представляют собой простой jane layout xml.

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

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

Видеть? Ничего особенного. Обратите внимание, что вы все равно должны объявить пространство имен Android с помощью xmlns:android="http://schemas.android.com/apk/res/android.

Итак, отрендеренная версия 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 -->
    <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>

В вашем java-коде все это прозрачно: findViewById(R.id.textView1)в вашем классе активности возвращается правильный виджет (даже если этот виджет был объявлен в xml-файле, отличном от макета активности).

И вишня сверху: визуальный редактор справляется с этим плавно. Макет верхнего уровня отображается с включенным xml.

Сюжет утолщается

Поскольку включаемый файл является классическим XML-файлом макета, это означает, что он должен иметь один верхний элемент. Таким образом, если ваш файл должен содержать более одного виджета, вам придется использовать макет.

Допустим, include1.xmlсейчас их два TextView: макет должен быть объявлен. Давайте выберем 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>

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

Но подождите, два уровня LinearLayoutизбыточны !

Действительно, два не вложенными LinearLayoutслужат никакой цели , как два TextViewмогут быть включены в layout1течение точно так же рендеринга .

Так что мы можем сделать?

Введите тег слияния

<merge>Тег просто фиктивный тег , который обеспечивает верхний элемент для решения такого рода проблемы избыточности.

Теперь include1.xml становится:

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

и теперь 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 --> 
    <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>

Вы сохранили один уровень иерархии, избегая одного бесполезного представления: Romain Guy уже спит лучше.

Разве ты не счастливее сейчас?

Зовут карл
источник
23
Отличное описание.
RichieHH
4
объясняет очень четко, должен быть выбран в качестве ответа
lalitm
2
Отлично, без сомнения, это должен быть принятый ответ.
Гаурав Джейн
1
что-то не понял .. что, если внешний LinearLayout, например, вертикальный, но 2 текстовых представления в include1.xml должны были быть горизонтальными? объединение в этом случае не сохраняет их расположение, которое я хотел. Что с этим можно сделать?
Йонатан Нир
@ YonatanNir слияние не то, что вам нужно в вашем случае, ясно. если вам действительно нужно выровнять иерархию RelativeLayoutвидов , то, возможно, вы можете использовать или нарисовать виды вручную
Abhijit
19

Blazeroni уже дал понять, я просто хочу добавить несколько моментов.

  • <merge> используется для оптимизации макетов. Используется для уменьшения ненужных вложений.
  • когда макет, содержащий <merge>тег, добавляется в другой макет, <merge>узел удаляется, а его дочернее представление добавляется непосредственно к новому родителю.
Anshul
источник
10

Чтобы иметь более глубокие знания о том, что происходит, я создал следующий пример. Посмотрите на файлы activity_main.xml и 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>

Здесь весь файл макета при накачке выглядит следующим образом.

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

Обратите внимание, что внутри родительского LinearLayout есть LinearLayout, который не служит какой-либо цели и является избыточным. Взгляд на макет с помощью инструмента «Инспектор макетов» ясно объясняет это.

введите описание изображения здесь

content_profile.xml после обновления кода для использования слияния вместо ViewGroup, например 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>

Теперь наш макет выглядит так

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

Здесь мы видим, что лишняя LinearLayout ViewGroup удалена. Теперь инструмент Layout Inspector предоставляет следующую иерархию макетов.

введите описание изображения здесь

Поэтому всегда пытайтесь использовать слияние, когда ваш родительский макет может позиционировать ваши дочерние макеты, или, точнее, использовать слияние, когда вы понимаете, что в иерархии будет избыточная группа представлений.

capt.swag
источник
5

Другой причиной использования слияния является использование пользовательских групп представлений в ListViews или GridViews. Вместо использования шаблона viewHolder в адаптере списка вы можете использовать пользовательское представление. Пользовательское представление будет раздувать XML, корнем которого является тег слияния. Код для адаптера:

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

Вот пользовательская группа:

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

и вот 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
источник
Вы подразумеваете, что если бы вы использовали RelativeLayout в своем XML-файле и свою пользовательскую ViewGroup, унаследованную от RelativeLayout, то тогда было бы два RelativeLayout, один вложенный в другой?
Скотт Биггс