Использование <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
<?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
также будет вариант, когда высота элементов меняется.
в принципе, всегда легче работать со структурой, чем против нее.
Некоторые проблемы с вашим вопросом:
- Согласно ссылке на документацию для Android
Вы также можете переопределить все параметры макета (любые атрибуты android:layout_*) корневого представления включенного макета, указав их в теге
. Таким образом, все ограничения, введенные вами в тег include, будут удалены.
любой
android:id
Включение НЕ будет переопределено, если в вашем включенном макете используется тег слияния.Связывание и добавление ограничений работает для представлений с разными идентификаторами. Так что для включения одного и того же вида несколько раз с одинаковым весом не будет работать через тег включения.
При этом, вы можете скопировать и вставить весь
Таким образом, вы не можете использовать включить таким образом.
Вам осталось 3 варианта:
- Используйте другой вид ViewGroup (например, LinearLayout, а затем макет ограничения)
- Скопируйте и вставьте содержимое
include
макет с разными идентификаторами просмотров - изменять
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>
Надеюсь, это поможет вам.