在ConstraintLayout中使用<merge>与<include>

49

我在使用 ConstraintLayout 时遇到了使用 <include><merge> 标签的困难。

我想创建一个扁平的视图层次结构(因此使用 Constraints),但仍然希望元素可重用。因此,我在我的布局中使用了 <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>

并且这个视图电影注释:

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

我期望得到这个:

I am expecting this

但实际上我得到了这个:

Instead I get this

很明显,我在<include>标签中设置的约束被包含布局中的约束覆盖了。

这是否是预期行为?如果是,我们应该如何使用<include>和ConstraintLayout保持平面布局呢?


这是预期的行为。所有包含的视图都将放置在父视图中,没有单独的父视图组。您有一个宽度为match_constraints的卡片视图,并且左右约束与父视图相关,因此将占用整个宽度。 - nomag
请注意,我们发现在将0dp设置为内容大小时会出现问题。但是,在设置wrap content(例如)的情况下,包含的约束不适用于具有其设置的合并视图。那么如何解决此问题,而无需在运行时重置所有约束。 - GensaGames
4
我已经在您的问题上添加了赏金,并在这里创建了一个问题 https://issuetracker.google.com/issues/115695764,提出了如何处理约束布局合并的建议,请随意给它点赞。 - Daniele Segato
2
感谢@DanieleSegato,这样确实引起了更多的关注。不幸的是,我正在度假,你的悬赏已经过期了...答案只回答了问题中“为什么这不起作用”的部分,但错过了真正的重点,即:“我们如何拥有可重用的组件和平面布局?”我想我们还没有完美的解决方案,如果我必须选择,我会选择可重用的组件而不是完美的平面布局。我已经标记了你的问题。 - JDenais
6个回答

51

简短回答

最好的做法是用(嵌套)ConstraintLayout替换<merge>块,而不是使用冗余布局结构。




ConstraintLayout很棒,但它与组合和每个部分的职责分离不兼容。

这是错误的。ConstraintLayout与重用布局非常兼容。任何布局只要所有子视图都根据兄弟视图和父布局之间的关系进行布局,就会表现得像这样。甚至对于RelativeLayout也是如此。


那么问题在哪里?

让我们仔细看一下<merge>是什么。

文档说:

<merge/>标签可以在一个布局中包含另一个布局时,帮助消除视图层次结构中的冗余视图组。

它将产生与用<include>元素替换<merge>块的效果相同。换句话说,<merge/>块中的视图直接放置在父布局中,而不需要中间视图组。因此,<include>元素的约束完全被忽略。

在这个特定的例子中,包含布局中的视图被添加了两次到父布局中,第二个视图叠加在另一个视图上方。


结论

布局资源文件旨在独立使用。为了符合可重用的条件,它不应该依赖于其父元素(将来将添加到其中的视图组)。 如果您只需要包含布局一次,那么看起来应该还好。但是,即使在这种情况下,</merge>也不是一个好主意,因为您不能将其放置在不同位置的任何其他布局中。

显然,扁平化的布局层次结构具有更好的性能。然而,有时我们可能必须做出牺牲。


2
好的答案应该与结论/讨论紧密相连,解释了权衡取舍,这使读者在应用帖子中的提示时更加自信。 - Ludvig W

7

Android文档指出:

<merge /> 标签可帮助您在一个布局中包含另一个布局时消除视图层次结构中的冗余视图组。

并提供了一个示例:

如果您的主布局是垂直 LinearLayout,其中两个连续的视图可以在多个布局中重复使用,则放置这两个视图的可重用布局需要其自己的根视图。但是,使用另一个 LinearLayout 作为可重用布局的根将导致一个垂直 LinearLayout 嵌套在另一个垂直 LinearLayout 中。嵌套的 LinearLayout 没有实际用途,只会降低 UI 性能。

还可以查看这个答案,可以更好地理解 merge 标签。

布局中的问题

对于子布局:

你在<merge标签中放置了对子元素的约束条件。这是不可取的,因为当两个子布局在父布局中合并时,这些约束条件在运行时将被破坏。(如果没有使用<include标记,你能做到这一点吗?你的约束条件会起作用吗?)
对于父布局,<include标记同样如此。你给<include标记设置了约束/自定义属性,但这些将会丢失,因为<merge标签与根视图合并,所以你不能使用<merge标签对<include标记应用自定义属性。这就是为什么Bahman的答案可行的原因。
当你在子布局中有根元素而没有<merge标签时,<include标记上的属性才能起作用。
结论
作为明显的事实,你没有使用<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>

output

我希望我能让你理解得很好。


4

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>

@MartinZeitler 不,使用 include 包含的标记有一些限制属性,在 cardView 中是无效的。 - ygngy
这就是为什么我写了“除非” - 虽然整个问题都是关于“如何创造一个过于复杂的解决方案来解决一个简单的问题”的(这就是为什么我的答案只涉及替代方法)。 - Martin Zeitler
@MartinZeitler 我同意它有更简单的答案。应该避免嵌套布局。 - ygngy
你可以直接在include标签上执行相同的操作。将容器移动到那里是无用的。 - Daniele Segato
@DanieleSegato 如果你用 Constraint Layout 替换 merge,然后把属性放在两个 include 里而不是两个 Constraint Layout 里,那么应该是可能的。你测试过这个方法并且有效吗? - ygngy
显示剩余7条评论

2
作为一种解决方案,父布局enter image description here
<?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>

查看电影注释 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>

2

0

关于您的问题有一些问题:

  1. 根据Android文档link

您还可以通过在<include/>标记中指定它们来覆盖所包含布局的根视图的所有布局参数(任何android:layout_*属性)因此,您放入include标记的任何约束都将被删除。

  1. 如果在您的包含布局中使用了合并标记,则不会覆盖包含中的任何android:id

  2. 链接和添加约束适用于具有不同ID的视图。 因此,通过包含标记多次包含相同视图并具有相等权重将无法工作。

话虽如此,您可以复制整个

因此,您不能以这种方式使用include。

您有3个选项:

  1. 使用其他的ViewGroup(例如LinearLayout和ConstraintLayout)
  2. 复制并粘贴include布局的内容,并使用不同的视图ID
  3. 修改ConstraintLayout代码以支持spread-chains,这样整个包含的布局就可以在水平方向上复制。

我的看法是,如果你只有少量这些布局,则第一种选项最好;如果你只有一个布局(问题中提到),则第二种选项最好;如果你有大量的布局,则第三种选项最好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接