Использование <include> с <merge> в ConstraintLayout

У меня проблемы с использованием тегов <include> а также <merge> внутри ConstraintLayout.

Я хочу создать плоскую иерархию представлений (отсюда и ограничения), но при этом иметь элементы, которые можно использовать повторно. Поэтому я использую <include> в моем макете и <merge> во включенных макетах, чтобы избежать вложенных макетов (особенно избегая вложенных ConstraintLayouts)

Итак, я написал это: родительский макет

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/review_2"/>

    <include
        layout="@layout/view_movie_note"
        android:id="@+id/review_2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginLeft="7dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/review_1"
        app:layout_constraintRight_toRightOf="parent"
        />

</android.support.constraint.ConstraintLayout>

и это view_movie_note:

<merge>

    <TextView
        android:id="@+id/note_origin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:layout_marginStart="5dp"
        app:layout_constraintStart_toStartOf="@+id/cardView2"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="5dp" />


    <android.support.v7.widget.CardView
        android:id="@+id/five_star_view_container"
        android:layout_width="0dp"
        android:layout_height="52dp"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="10dp"
        android:elevation="3dp"
        app:cardUseCompatPadding="true"
        app:contentPaddingTop="22dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_min="52dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/note_origin">

        <FiveStarsView
            android:id="@+id/five_star_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />

    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
        android:id="@+id/cardView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        app:cardBackgroundColor="@color/colorPrimary"
        app:contentPaddingLeft="15dp"
        app:contentPaddingRight="15dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/note_origin">

        <TextView
            android:id="@+id/grade"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp" />

    </android.support.v7.widget.CardView>


</merge>

Я ожидаю этого

Я ожидаю этого

Вместо этого я получил это

Вместо этого я получаю это

Ясно, что ограничения, которые я положил в <include> тег переопределяется ограничениями во включенном макете.

Это ожидаемое поведение? Если да, как мы должны сохранять плоскую разметку, используя <include> а ConstraintLayout?

8 ответов

Короткий ответ

Лучшим ходом будет замена <merge> блок с (вложенным) ConstraintLayout вместо использования избыточной структуры макета.




По словам Daniele Segato (тот, кто положил награду на должность),

ConstraintLayout великолепен, но он плохо работает с составом и разделением обязанностей каждого элемента

Это не правильно. ConstraintLayout хорошо работает с повторным использованием макетов. Любой макет, в котором все дочерние представления размечены в соответствии с отношениями между родственными представлениями и родительским макетом, ведет себя именно так. Это верно даже для RelativeLayout,


Тогда в чем проблема?

Давайте внимательнее посмотрим на то, что <merge> является.

Док говорит

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

Это будет иметь тот же эффект, что и замена <include> элемент с содержанием <merge> блок. Другими словами, мнения в <merge/> Блок непосредственно помещается в родительский макет без промежуточной группы представлений. Следовательно, ограничения <include> Элемент полностью игнорируется.

В этом конкретном примере представления во включающем макете добавляются к родительскому объекту два раза как второй поверх другого.


Заключение

Файлы ресурсов макета предназначены для самостоятельного использования. Чтобы квалифицировать термин "повторно используемый", он не должен зависеть от его родителя (группа представления, в которую он будет добавлен в будущем). Было бы хорошо, если бы вы включили макет только один раз. Но </merge> не будет хорошей идеей и в этом случае, потому что вы не можете поместить его в другой макет в другое положение.

Очевидно, что плоские иерархии имеют лучшую производительность. Однако иногда нам, возможно, придется пожертвовать этим.

Документация Android гласит

<merge /> тег помогает исключить избыточные группы представлений в иерархии представлений при включении одного макета в другой

и есть пример тоже

Если ваш основной макет вертикальный LinearLayout в котором два последовательных вида могут быть повторно использованы в нескольких макетах, тогда макет многократного использования, в котором вы размещаете два вида, требует своего собственного корневого представления. Однако, используя другой LinearLayout как корень для многоразового макета приведет к вертикальной LinearLayout внутри вертикали LinearLayout, Вложенные LinearLayout не служит никакой реальной цели, кроме как для снижения производительности вашего интерфейса.

Также посмотрите этот ответ, который поможет вам лучше понять тег слияния.

Проблема в вашем макете

Для детской раскладки

Вы устанавливаете ограничения на дочерние элементы внутри <merge тег. Это не хорошо. Потому что эти ограничения уничтожаются во время выполнения, когда оба дочерних макета объединяются с вашим родительским макетом. (Вы скажете мне, если вы можете сделать это без тега включения, сработают ли ваши ограничения?)

Для родительского макета

То же самое для <include тег, вы даете ограничения / пользовательские атрибуты для <include тег, который будет потерян, потому что <merge тег присоединяется к корневому представлению, поэтому вы не можете применять пользовательские атрибуты к <include с <merge тег. Вот почему Бахман ответ будет работать.

Атрибуты на <include тег работает, когда у вас есть корневой элемент внутри дочернего макета и нет <merge тег.

Заключение

Как это понятно, вы не используете <merge а также <include, так, как это должно быть. Вы поняли, что <include а также <merge тег сделать. Так что используйте их соответствующим образом.

Если вы спросите решение

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

Родительский макет

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        />

    <include
        android:id="@+id/review_2"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="7dp"
        android:layout_weight="1"
        />

</LinearLayout>

view_movie_note.xml

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
     .../>

    <android.support.v7.widget.CardView
    ...
    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
    ...
    </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>

выход

Я надеюсь, что смогу хорошо тебя понять.

Заворачивать include теги с ConstraintLayout теги затем перемещают атрибуты include теги к этим новым ConstraintLayout теги:

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <android.support.constraint.ConstraintLayout
            android:id="@+id/review_1"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/review_2">

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

   </android.support.constraint.ConstraintLayout>

    <android.support.constraint.ConstraintLayout
            android:id="@+id/review_2"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginLeft="7dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toRightOf="@+id/review_1"
            app:layout_constraintRight_toRightOf="parent">

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

    </android.support.constraint.ConstraintLayout>

  </android.support.constraint.ConstraintLayout>

Слияние - это тег, а не ViewGroup, поэтому все параметры, передаваемые для включения, будут игнорироваться... Вы можете использовать эту ViewGroup только с дублированным макетом, если вам нужно управлять этим, вы можете создать группу... XML атрибуты от макета слияния до RelativeLayout через inflate

Как решение

Родительский макет введите описание изображения здесь

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_width="match_parent"
 android:layout_height="match_parent">


<android.support.v7.widget.LinearLayoutCompat
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:weightSum="2">

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/review_2"
        app:layout_constraintTop_toTopOf="parent" />

    <include
        android:id="@+id/review_2"
        layout="@layout/view_movie_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="7dp"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/review_1"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

 </android.support.v7.widget.LinearLayoutCompat>


</android.support.constraint.ConstraintLayout>

view_movie_note 2

<?xml version="1.0" encoding="utf-8"?>


<android.support.constraint.ConstraintLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

<TextView
    android:id="@+id/note_origin"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="15dp"
    android:layout_marginLeft="5dp"
    android:layout_marginStart="5dp"
    app:layout_constraintStart_toStartOf="@+id/cardView2"
    app:layout_constraintTop_toTopOf="parent" />


<android.support.v7.widget.CardView
    android:id="@+id/five_star_view_container"
    android:layout_width="wrap_content"
    android:layout_height="52dp"
    android:layout_marginBottom="8dp"
    android:layout_marginTop="10dp"
    android:elevation="3dp"
    app:cardUseCompatPadding="true"
    app:contentPaddingTop="22dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHeight_min="52dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/note_origin">

    <!--<FiveStarsView-->
    <!--android:id="@+id/five_star_view"-->
    <!--android:layout_width="wrap_content"-->
    <!--android:layout_height="wrap_content"-->
    <!--android:layout_gravity="center_horizontal" />-->

    <RatingBar
        android:id="@+id/ratingBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</android.support.v7.widget.CardView>

<android.support.v7.widget.CardView
    android:id="@+id/cardView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    app:cardBackgroundColor="@color/colorPrimary"
    app:contentPaddingLeft="15dp"
    app:contentPaddingRight="15dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@+id/note_origin">

    <TextView
        android:id="@+id/grade"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp" />

  </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>

Файл view_movie_note.xml должен иметь один узел XML в своем корне - иначе не будет двух узлов макета, которые могли бы быть выровнены с ограничениями (<merge> тег может быть бесполезным). имея две одинаковые раскладки с одинаковыми resId добавляет к проблеме - потому что ограничения всегда определяются относительно resId, что не является уникальным здесь.

альтернативные подходы были бы: а) использовать LinearLayout для распределения двух элементов по горизонтали - или б) в случае, если должно быть еще несколько дочерних узлов (что я бы предположил), лучше использовать CardViewв GridLayout с двумя столбцами, внутри GridView / RecyclerView.

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

в соответствии с ожидаемым результатом, ConstraintLayout не требуется выравнивать узлы так же, как - с GridLayoutManager Можно даже настроить количество столбцов в соответствии с размером дисплея. StaggeredGridLayoutManager также будет вариант, когда высота элементов меняется.

в принципе, всегда легче работать со структурой, чем против нее.

Некоторые проблемы с вашим вопросом:

  1. Согласно ссылке на документацию для Android

Вы также можете переопределить все параметры макета (любые атрибуты android:layout_*) корневого представления включенного макета, указав их в теге . Таким образом, все ограничения, введенные вами в тег include, будут удалены.

  1. любой android:id Включение НЕ будет переопределено, если в вашем включенном макете используется тег слияния.

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

При этом, вы можете скопировать и вставить весь

Таким образом, вы не можете использовать включить таким образом.

Вам осталось 3 варианта:

  1. Используйте другой вид ViewGroup (например, LinearLayout, а затем макет ограничения)
  2. Скопируйте и вставьте содержимое include макет с разными идентификаторами просмотров
  3. изменять ConstraintLayout код для поддержки цепочек распространения, так что весь включенный макет копируется горизонтально.

IMO, 1-й вариант лучше, если у вас есть небольшое количество этих макетов, 2-й вариант лучше, если у вас есть только один макет (заданный вопрос), и 3-й вариант лучше, если у вас есть большое количество макетов.

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

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

<FrameLayout
    android:id="@+id/review_1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/review_2"
    app:layout_constraintTop_toTopOf="parent">

    <include
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="0dp" />
</FrameLayout>

<FrameLayout
    android:id="@+id/review_2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toRightOf="@+id/review_1"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <include
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginLeft="7dp" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>

Надеюсь, это поможет вам.

Другие вопросы по тегам