如何使用ConstraintLayout将多个视图居中对齐?

87

背景

Google宣布了一个名为 "ConstraintLayout" 的新布局,这是终极布局,可以替换所有布局而保持平面(没有嵌套布局)并具有更好的性能。

问题

问题是,除了在 Google IO 上介绍的视频之外,我几乎找不到任何有助于我解决问题的教程。

我的目标是将垂直居中的LinearLayout转换为单个ConstraintLayout。

毕竟,这是这种新布局的目的...

我想处理的布局如下所示:

enter image description here

请注意,中间的视图仅在垂直方向上居中,而且两个TextView位于ImageView右侧,该ImageView也在垂直方向上居中。

这在RelativeLayout中可以很好地工作,其中包含2个TextView的LinearLayout,但我想知道如何将它们转换为单个ConstraintLayout。

这是我展示的示例XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/listPreferredItemHeightSmall">

    <ImageView
        android:id="@+id/appIconImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:layout_marginEnd="4dp"
        android:layout_marginLeft="2dp"
        android:layout_marginRight="4dp"
        android:layout_marginStart="2dp"
        android:adjustViewBounds="true"
        android:src="@android:drawable/sym_def_app_icon"
        tools:ignore="ContentDescription"/>

    <LinearLayout
        android:id="@+id/appDetailsContainer"
        android:layout_width="0px"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/appIconImageView"
        android:layout_toLeftOf="@+id/overflowView"
        android:layout_toRightOf="@+id/appIconImageView"
        android:layout_toStartOf="@+id/overflowView"
        android:orientation="vertical">

        <TextView
            android:id="@+id/appLabelTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:text="label"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textDirection="locale"
            tools:ignore="HardcodedText,UnusedAttribute"/>

        <TextView
            android:id="@+id/appDescriptionTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:minLines="3"
            android:text="description"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textDirection="locale"
            tools:ignore="HardcodedText,UnusedAttribute"/>
    </LinearLayout>

    <ImageView
        android:id="@+id/overflowView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:adjustViewBounds="true"
        android:background="?attr/selectableItemBackground"
        android:clickable="true"
        android:padding="10dp"
        app:srcCompat="@drawable/ic_more_vert_black_24dp"

        tools:src="@drawable/ic_more_vert_black_24dp"
        tools:ignore="ContentDescription"/>

    <ImageView
        android:id="@+id/isSystemAppImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignEnd="@+id/overflowView"
        android:layout_alignLeft="@+id/overflowView"
        android:layout_alignParentBottom="true"
        android:layout_alignRight="@+id/overflowView"
        android:layout_alignStart="@+id/overflowView"
        android:adjustViewBounds="true"
        android:scaleType="centerInside"
        app:srcCompat="@drawable/ic_warning_black_24dp"
        tools:ignore="ContentDescription"
        tools:src="@drawable/ic_warning_black_24dp"/>

</RelativeLayout>

我尝试过的

我尝试阅读了一些Google的文章和观看了一些视频:

但这些都没有帮助,所以我尝试使用它,希望我能自己找出如何使用它。 但我无法找出如何做到。我尝试使用转换布局的功能,但这会使视图混乱,并添加我不想要的额外边距。

问题

如何将这两个布局转换为单个ConstraintLayout?


请在下面的文本框中输入您的代码。 - pskink
@pskink 有点类似,但是居中的视图有两个TextView,一个在另一个下面。不是只有一个。目前它们位于一个居中的LinearLayout中。此外,该LinearLayout位于右侧和左侧视图之间。 - android developer
你尝试过在设计面板上进行自动转换吗?“将<your_layout>转换为ConstraintLayout”? - Devrim
@DevrimTuncer 是的。结果并没有完全展开它。你可以在这里看到我的尝试:https://code.google.com/p/android/issues/detail?id=212116 - android developer
5个回答

120

请查看我在这里的答案。

ConstraintLayout包含一种名为Chains的特性,可以实现您所要求的功能:

链条提供了单轴(水平或垂直)的类似于组的行为。

如果一组小部件通过双向连接链接在一起,则被视为链条。

创建链之后,有两种可能性:

  • 将元素分散在可用空间中
  • 链条还可以“打包”,在这种情况下,元素会分组在一起

对于您的情况,您需要打包您的labeldescription TextView,并使它们在父视图中垂直居中:

(请确保您使用支持链条的ConstraintLayout版本)

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


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toRightOf="@+id/imageView2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHorizontal_chainStyle="packed"/>

    <TextView
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Button\nMkay"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/imageView2"
        app:layout_constraintTop_toBottomOf="@+id/textView"/>

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="16dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher"/>

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher"/>

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:srcCompat="@mipmap/ic_launcher"/>
</android.support.constraint.ConstraintLayout>

2019年6月25日更新 (@Saeid Z):

现在在约束布局 1.1.3 版本中,我们必须使用 app:layout_constraintHorizontal_chainStyle="packed" 替代 app:layout_constraintVertical_chainPacked="true"


2
它如何将视图分组在一起? - android developer
1
链的定义是:“如果一组小部件通过双向连接相互链接,则被视为链” - 例如中的两个视图相互依赖(textView在按钮上方,按钮在textView下方),因此它们被称为链并分组在一起。请跟随链接并查找更多信息。 - Yury Fedorov
2
我不知道具体的实现细节。我所知道的是,在它们的约束中相互引用的视图会形成一个链,其行为由第一个成员定义。 - Yury Fedorov
@mbonnin 这个问题确切地说是如何将视图居中在父视图中,而不是相对地对齐。我认为,对一个回答进行负评,因为它没有回答你的问题,但回答了提问者的问题,是不合适的。 - Yury Fedorov
2
@Yury Fedorov 谢谢。现在在约束布局1.1.3中,我们必须使用app:layout_constraintHorizontal_chainStyle="packed"代替app:layout_constraintVertical_chainPacked="true"。 - Saeid Z
显示剩余7条评论

9

app:layout_constraintVertical_bias="0.5" 设置给需要在垂直方向上居中的视图,只有在指定边界的约束条件(例如在垂直偏差时指定顶部和底部约束,水平偏差时指定左侧和右侧约束)后,偏差属性才会生效。

示例:

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/constraintLayout">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="@id/constraintLayout"
        app:layout_constraintBottom_toBottomOf="@id/constraintLayout"
        app:layout_constraintVertical_bias="0.5" />

</android.support.constraint.ConstraintLayout>

我在我的布局中把它搞定了:https://github.com/hidroh/tldroid/blob/master/app/src/main/res/layout/activity_main.xml,虽然布局相似,但我将物品放置在屏幕高度的1/3处。 enter image description here

1
我不理解。我有两个TextViews,可能会有更多。我希望它们在垂直方向上一起居中对齐。我应该怎么做? - android developer
我的布局有两个TextView。我只给出了如何在ConstraintLayout中垂直居中一个视图的示例。如果您想要一堆这样的视图,请为每个视图设置偏置属性。在我的情况下,我只设置了一个TextView,并将其用作另一个TextView的锚点。 - hidro
请问您能否演示一下如何实现我所提出的情况?即将两个TextView垂直排列,同时让它们一起居中显示。 - android developer
你看了我回答中的Github文件吗?它几乎完全相同,只需将我的布局中的偏置值更改为0.5而不是0.3。 - hidro

5

编辑:此答案是在"chains"出现之前编写的。现在请使用"chains",请参见上面的答案:https://dev59.com/F1oU5IYBdhLWcg3wYWWx#40102296


目前我认为您唯一的选择是将这两个文本视图包含在另一个布局中——可能线性布局最适合这种情况。

但是,约束布局团队表示他们想引入"虚拟容器",以满足这个用例:您可以将两个或多个视图分组,并在容器上设置约束(如垂直居中)。这样做的想法是它不是完整的嵌套布局,而只是约束布局用于定位其子项的东西——因此它应该比嵌套提供更好的性能。

他们在I/O talk(链接到特定时间)中提到了这一点。所以我猜要继续关注。


但是RelativeLayout已经包含了一个LinearLayout。难道整个重点不就是将其扁平化以获得单一的布局吗?我的情况有这么特殊吗? - android developer
1
是的,重点是要展平。你的情况并不特殊,只是还没有处理。它只是在版本 alpha2 上。虚拟组将会出现来处理你的情况。 - Marcin Koziński
1
虚拟组目前还不存在。这是ConstraintLayout团队想要引入的一个概念。他们决定提前发布,没有所有功能,并且没有处理所有常见情况。 - Marcin Koziński
你是怎么知道这些的?我不记得在 Google IO 的讲座中听到过,也没有在任何文章中看到过。 - android developer
1
实际上我在I/O讨论中找到了 :) 但是他们称其为“虚拟容器”,所以我会编辑我的答案并发布链接。 - Marcin Koziński
显示剩余2条评论

4

为了将某个元素在垂直或水平方向上居中,可以在布局中设置一个相反的约束

垂直居中

    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"

水平居中

    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"

1
请先查看此链接 如何回答问题这个问题之前已经有人回答过了,显然你可以在此处添加你的答案。但是在回答之前需要理解一些要点。首先,不要添加与先前添加的相同代码或建议的答案。其次,如果用户非常明确地询问了问题及其解决方法,请不要添加过于复杂的答案。第三,如果您想就答案或问题提出任何建议,可以添加评论。 - always-a-learner
24
这只是中心单一视图。 - Rasel

2

请看我的例子 (如何在ConstraintLayout中居中组件)

目前您可以将"image"居中(将其左侧、顶部和底部约束),将"label"顶部与"image"顶部约束,将"description"顶部与"label"底部约束。 在下面的示例中,我将按钮高度与右侧的两个文本视图匹配(这也是您的情况)。其他文本视图按照上述链接中的方式约束。

enter image description here

更新:回答您下面的评论:

我已经安装了您的应用程序,这是我目前能想到的唯一解决方法。由于它是ViewHolder布局,因此可以将layout_height设置为wrap_content,或者您可以将其固定。如果您希望,我可以发送xml给您,但我不想淹没答案。

上方的TextView左侧约束被约束到ImageView的右侧约束。另外,上方的TextView -> 顶部约束被约束到容器的顶部,右侧也是如此。下方的TextView被约束到容器的底部。中间的TextView被约束为与顶部的TextView匹配宽度,与ImageView没有关联。

enter image description here


我问的是如何将两个视图一起居中,而不是单独一个。 - android developer
@androiddeveloper 是的,我知道这个问题,我提供了一个解决方法。视觉上看起来是一样的。当然,问题仍然存在,因为我们都想要相互约束多个组件居中。 - bajicdusko
其实在我的情况下,这可能是一个不错的解决方法。我使用的布局是用于RecyclerView中的项目(在此应用程序中:https://play.google.com/store/apps/details?id=com.lb.app_manager)。也许我可以将顶部TextView设置为占据顶部(但重力在底部),将底部TextView设置为占据底部(在顶部TextView下方)。你认为它看起来会一样吗? - android developer
@androiddeveloper 我已经更新了我的答案,并针对你的应用程序提供了特定于布局的解决方案。希望能对你有所帮助。 - bajicdusko
我有时间时会尝试。我注意到最近他们修复了一个bug,可以在RecyclerView中使用新布局。 - android developer

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