使用 ConstraintLayout 构建自适应界面   Android Jetpack 的一部分。

试试 Compose 方式
Jetpack Compose 是推荐在 Android 设备上使用的界面工具包。了解如何在 Compose 中使用布局。

借助 ConstraintLayout,您可以使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与 RelativeLayout 相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于 RelativeLayout,并且更易于与 Android Studio 的布局编辑器配合使用。

布局编辑器的可视化工具中直接提供了 ConstraintLayout 的所有功能,因为布局 API 和布局编辑器是专为彼此构建的。您完全可以使用 ConstraintLayout 通过拖动(而非修改 XML)来构建布局。

本页介绍了如何在 Android Studio 3.0 或更高版本中使用 ConstraintLayout 构建布局。如需详细了解布局编辑器,请参阅使用布局编辑器构建界面

如需了解您可以使用 ConstraintLayout 创建的各种布局,请参阅 GitHub 上的约束布局示例项目

约束条件概览

如需在 ConstraintLayout 中定义某个视图的位置,您需要为该视图添加至少一个水平约束条件和一个垂直约束条件。每个约束条件均表示与其他视图、父布局或隐形引导线之间连接或对齐方式。每个约束条件都定义了视图沿纵轴或横轴的位置。每个视图在每个轴上都必须至少有一个约束条件,但通常情况下会需要更多约束条件。

将视图拖放到布局编辑器中后,即使没有约束条件,该视图也会保留在您放置的位置。这只是为了方便编辑。当您在设备上运行布局时,如果视图没有任何约束条件,则会在位置 [0,0](左上角)处进行绘制。

在图 1 中,布局在编辑器中看起来很完美,但视图 C 上却没有垂直约束条件。在设备上绘制此布局时,虽然视图 C 与视图 A 的左右边缘水平对齐,但由于没有垂直约束条件,它会显示在屏幕顶部。

图 1. 编辑器将视图 C 显示在视图 A 下方,但它并没有垂直约束条件。

图 2. 视图 C 现垂直约束在视图 A 下方。

虽然缺少约束条件不会导致编译错误,但布局编辑器会在工具栏中将缺少约束条件显示为错误。如需查看错误和其他警告,请点击 Show Warnings and Errors 。 为帮助您避免出现缺少约束条件这一问题,布局编辑器会使用 Autoconnect 和 Infer Constraints 功能自动为您添加约束条件。

将 ConstraintLayout 添加到项目中

如需在您的项目中使用 ConstraintLayout,请按以下步骤操作:

  1. 确保您的 maven.google.com 代码库已在 settings.gradle 文件中声明:

    Groovy

        dependencyResolutionManagement {
          ...
          repositories {
              google()
          }
        )
        

    Kotlin

        dependencyResolutionManagement {
          ...
          repositories {
              google()
          }
        }
        
  2. 将该库作为依赖项添加到模块级 build.gradle 文件中,如以下示例所示。最新版本可能与示例中显示的不同。

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0-beta01"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01"
    }
    

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-beta01")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01")
    }
    
  3. 在工具栏或同步通知中,点击 Sync Project with Gradle Files

现在,您可以使用 ConstraintLayout 构建布局。

转换布局

图 3. 用于将布局转换为 ConstraintLayout 的菜单。

如需将现有布局转换为约束布局,请按以下步骤操作:

  1. 在 Android Studio 中打开您的布局,然后点击编辑器窗口底部的 Design 标签页。
  2. Component Tree 窗口中,右键点击该布局,然后点击 Convert LinearLayout to ConstraintLayout

创建新布局

如需开始新的约束布局文件,请按以下步骤操作:

  1. Project 窗口中,点击模块文件夹,然后依次选择 File > New > XML > Layout XML
  2. 为布局文件输入名称,并为 Root Tag 输入“androidx.constraintlayout.widget.ConstraintLayout”。
  3. 点击 Finish

添加或移除约束条件

如需添加约束条件,请执行以下操作:

视频 1. 视图的左侧被约束在父布局的左侧。

  1. 将视图从 Palette 窗口拖到编辑器中。

    当您在 ConstraintLayout 中添加视图时,该视图会显示在一个边界框中,每个角都有用于调整大小的方形手柄,每条边上都有圆形的约束手柄。

  2. 点击视图将其选中。
  3. 执行以下某项操作:
    • 点击约束手柄并将其拖动到可用定位点。 此点可以是另一个视图的边缘、布局的边缘或引导线。请注意,当您拖动约束手柄时,布局编辑器会显示可行的连接定位点和蓝色叠加层。
    • 点击 Attributes 窗口的 Layout 部分中的 Create a connection 按钮 ,具体如图 4 所示。

      图 4. 您可以通过 Attributes 窗口中的 Layout 部分创建连接。

创建约束条件后,编辑器会为其提供默认外边距来分隔两个视图。

创建约束条件时,请注意以下规则:

  • 每个视图都必须至少有两个约束条件:一个水平约束条件,一个垂直约束条件。
  • 您只能在共用同一平面的约束手柄和定位点之间创建约束条件。视图的垂直平面(左侧和右侧)只能约束在另一个垂直平面上;而基准线则只能约束到其他基准线上。
  • 每个约束手柄只能用于一个约束条件,但您可以在同一定位点上创建多个约束条件(从不同的视图)。

您可以通过执行以下任一操作来删除约束条件:

  • 点击某个约束条件将其选中,然后点击删除
  • 按住 Ctrl 键的同时点击(在 macOS 上,则按住 Command 键并点击)某个约束定位点。该约束条件变为红色即表示您可以点击将其删除,如图 5 所示。

    图 5. 红色约束条件表示您可以点击将其删除。

  • Attributes 窗口的 Layout 部分中,点击某个约束定位点,如图 6 所示。

    图 6. 点击约束定位点将其删除。

视频 2. 添加与现有约束条件相反的约束条件。

如果您在视图上添加相反的约束条件,约束线会像弹簧一样盘转,以指示相反的力,如视频 2 所示。当视图尺寸设置为“fixed”或“wrap content”时,效果最明显,在这种情况下,视图在两个约束条件之间居中。如果您希望视图扩展其尺寸以满足约束条件,请将尺寸切换为“match constraints”。如果您希望保持当前尺寸,但想要移动视图使其不要居中,则可以调整约束偏差

您可以使用约束条件来实现不同类型的布局行为,如以下部分所述。

父级位置

将视图的一侧约束到布局的相应边缘。

在图 7 中,视图的左侧连接到父布局的左边缘。您可以使用外边距来定义与边缘的距离。

图 7. 对父级的水平约束。

顺序位置

定义两个视图的显示顺序(垂直或水平方向)。

在图 8 中,B 被约束为始终位于 A 的右侧,而 C 被约束在 A 的下方。不过,这些约束条件并不意味着对齐,因此 B 仍然可以上下移动。

图 8. 水平和垂直约束方式。

对齐方式

将一个视图的边缘与另一视图的同一边对齐。

在图 9 中,B 的左侧与 A 的左侧对齐。如果要对齐视图中心,请在两侧创建约束条件。

您可以通过从约束布局向内拖动视图来偏移对齐量。例如,图 10 显示 B 的偏移对齐为 24dp。偏移量由受约束视图的外边距定义。

您还可以选择要对齐的所有视图,然后点击工具栏中的 Align 图标 以选择对齐类型。

图 9. 水平对齐约束。

图 10. 偏移水平对齐约束。

基线对齐

将一个视图的文本基线与另一视图的文本基线对齐。

在图 11 中,B 的第一行与 A 中的文本对齐。

如需创建基线约束条件,请右键点击要约束的文本视图,然后点击 Show Baseline。然后点击文本基线并将其拖到另一基线上。

图 11. 基准对齐约束条件。

引导线约束

您可以添加垂直或水平的引导线来约束视图,并且应用用户看不到该引导线。您可以根据相对于布局边缘的 dp 单位或百分比在布局中定位引导线。

如需创建引导线,请点击工具栏中的 Guidelines 图标 ,然后点击 Add Vertical GuidelineAdd Horizontal Guideline

拖动虚线将其重新定位,然后点击引导线边缘的圆圈以切换测量模式。

图 12. 受引导线约束的视图。

屏障约束

与引导线类似,屏障是一条隐藏的线,您可以用它来约束视图,但屏障不会定义自己的位置。相反,屏障的位置会随着其中所含视图的位置而移动。当您要将一个视图约束为一组视图而不是一个特定视图时,这非常有用。

例如,在图 13 中,视图 C 被约束在屏障的右侧。该屏障设置为视图 A 和视图 B 的“end”侧(或从左至右布局中的右侧)。屏障根据视图 A 或视图 B 的右侧是最右侧来移动。

如需创建屏障,请按以下步骤操作:

  1. 点击工具栏中的 Guidelines 图标 ,然后点击 Add Vertical BarrierAdd Horizontal Barrier
  2. Component Tree 窗口中,选择要放入屏障内的视图,然后将其拖动到屏障组件中。
  3. Component Tree 中选择屏障,打开 Attributes 窗口,然后设置 请稍后再试。

现在,您可以从另一个视图创建屏障约束。

您还可以将屏障的视图约束到屏障。这样,您就可以使屏障中的所有视图相互对齐,即使您并不知道哪个视图最长或最高。

您还可以在屏障内添加引导线,以确保屏障的“最小”位置。

图 13. 视图 C 约束在屏障内,该屏障会根据视图 A 和视图 B 的位置和尺寸而移动。

调整约束偏差

对视图两侧添加约束条件,并且同一维度的视图大小为“固定”或“封装内容”时,视图将在两个约束条件之间居中,且默认偏差为 50%。您可以通过拖动 Attributes 窗口中的偏差滑块或拖动视图来调整偏差,如视频 3 中所示。

如果您希望视图扩展其尺寸以满足约束条件,请将尺寸切换为“match constraints”。

视频 3. 调整约束偏差。

调整视图尺寸

图 14. 选择视图时,Attributes 窗口包含用于以下控件的控件:1 尺寸比、2 删除约束条件、3 高度或宽度模式、4 外边距和 5 约束偏差。您还可以通过点击 6 约束列表中的各个约束条件来突出显示布局编辑器中的各个约束条件。

您可以使用角手柄来调整视图的尺寸,但这会对尺寸进行硬编码,从而使视图不会针对不同的内容或屏幕尺寸进行调整。如需选择其他尺寸模式,请点击视图,然后打开编辑器右侧的 Attributes 窗口。

Attributes 窗口顶部附近的视图检查器中包括若干布局属性的控件,如图 14 所示。这仅适用于约束布局中的视图。

您可以通过点击图 14 中标注 3 所指示的符号来更改高度和宽度的计算方式。这些符号表示尺码模式,如下所示。点击符号即可切换这些设置:

  • Fixed:您可以在下方的文本框中指定具体维度,也可以在编辑器中调整视图尺寸。
  • Wrap Content:视图仅在需要时扩展以适应其内容。
    • layout_constrainedWidth
    • 将其设置为 true 可让水平尺寸发生更改,以遵循约束条件。默认情况下,设置为 WRAP_CONTENT 的 widget 不受约束条件的限制。

  • Match Constraints:视图会尽可能扩展,以满足每侧的约束条件(在考虑视图的外边距之后)。不过,您可以使用以下属性和值修改该行为。只有将视图宽度设置为“match constraints”时,以下属性才会生效:
    • layout_constraintWidth_min

      该视图的最小宽度采用 dp 维度。

    • layout_constraintWidth_max

      该视图的最大宽度采用 dp 维度。

    不过,如果给定维度只有一个约束条件,则视图会扩展以适应其内容。在高度或宽度上使用此模式也可让您设置尺寸比

将尺寸设置为比率

图 15. 该视图的宽高比设置为 16:9,其宽度基于与高度的比率。

如果至少有一个视图尺寸设置为“match constraints”(0dp),您可以将视图尺寸设置为某个比例(例如 16:9)。如需启用该比例,请点击 Toggle Aspect Ratio Constraint(图 14 中的标注 1),然后在显示的输入中输入 width:height 比例。

如果宽度和高度都设置为“match constraints”,您可以点击 Toggle Aspect Ratio Constraint,选择哪个维度基于与另一个维度的比率。视图检查器通过用实线连接相应的边缘来指明哪个尺寸被设为比率。

例如,如果您将两侧都设置为“match constraints”,请点击两次 Toggle Aspect Ratio Constraint,将宽度设置为高度的比率。整个尺寸由视图的高度决定(可以以任意方式定义),如图 15 所示。

调整视图外边距

如需使视图间隔均匀,请点击工具栏中的 Margin ,为您添加到布局的每个视图选择默认外边距。您对默认外边距所做的任何更改仅应用于您在更改后添加的视图。

您可以通过点击代表每个约束条件所在行的数字来控制 Attributes 窗口中每个视图的外边距。在图 14 中,标注 4 表明下外边距设置为 16dp。

图 16. 工具栏的 Margin 按钮。

工具提供的所有外边距均为 8dp 的倍数,以确保您的视图与 Material Design 提供的 8dp 方形网格建议保持一致。

使用链控制线性组

图 17. 具有两个视图的水平链。

链是一组视图,这些视图通过双向位置约束条件相互链接到一起。链中的视图可以垂直或水平分布。

图 18. 每种链样式的示例。

链可以采用以下几种样式之一:

  1. Spread:视图在考虑外边距后均匀分布。这是默认值。
  2. Spread inside:第一个和最后一个视图固定在链两端的约束边界上,其余视图均匀分布。
  3. 加权:将链设置为 spreadspread inside 后,您可以通过将一个或多个视图设置为“match constraints”(0dp)来填充剩余空间。默认情况下,空间会均匀分配给设置为“match constraints”的每个视图,但您可以使用 layout_constraintHorizontal_weightlayout_constraintVertical_weight 属性为每个视图分配重要性权重。 这与线性布局中的 layout_weight 的运作方式相同:权重值最高的视图获得的空间最大,相同权重的视图获得同样大小的空间。
  4. Packed:视图打包在一起(在考虑外边距之后)。您可以通过更改链的“头”视图偏差调整整条链的偏差(左/右或上/下)。

链的“头”视图(水平链中最左边的视图(在从左到右的布局中)和垂直链中最顶部的视图)以 XML 格式定义链的样式。不过,您可以通过选择链中的任意视图,然后点击出现在该视图下方的链按钮 ,在 spreadspread insidepacked 之间进行切换。

如需创建链,请按以下步骤操作(如视频 4 所示):

  1. 选择要添加到链中的所有视图。
  2. 右键点击其中一个视图。
  3. 选择
  4. 选择水平居中垂直居中

视频 4. 创建水平链。

在使用链时,您需要注意以下几点:

  • 视图可以同时属于水平链和垂直链,因此您可以构建灵活的网格布局。
  • 只有当链的每一端都被约束到同一轴上的另一个对象时,链才能正常工作,如图 14 所示。
  • 尽管链的方向是垂直或水平的,但使用其中一个方向不会沿该方向对齐视图。如需使链中的每个视图都能正确定位,请添加其他约束条件,例如对齐约束

自动创建约束条件

您可以在布局编辑器中将每个视图移动到所需位置,然后点击 Infer Constraints 自动创建约束条件,而不是在将视图放置到布局中时为其添加约束条件。

Infer Constraints 会扫描布局,以便为所有视图确定最有效的约束集。它会将视图约束在当前位置,同时提高灵活性。您可能需要进行调整,以便布局能够按照您的预期针对不同的屏幕尺寸和方向进行响应。

Autoconnect to Parent 是您可以启用的独立功能。启用此功能后,当您将子视图添加到父视图时,此功能会自动为每个视图创建两个或多个约束条件,但仅在可以将视图约束到父布局的情况。Autoconnect 不会为布局中的其他视图创建约束条件。

Autoconnect 在默认情况下处于停用状态。点击布局编辑器工具栏中的 Enable Autoconnection to Parent ,即可启用该功能。

关键帧动画

ConstraintLayout 中,您可以使用 ConstraintSetTransitionManager 为尺寸和位置元素的变化添加动画效果。

ConstraintSet 是一个轻量型对象,表示 ConstraintLayout 内所有子元素的约束条件、外边距和内边距。当您将 ConstraintSet 应用于显示的 ConstraintLayout 时,布局会更新其所有子级的约束条件。

如需使用 ConstraintSet 制作动画,请指定两个布局文件作为动画的开始和结束关键帧。然后,您可以从第二个关键帧文件加载 ConstraintSet 并将其应用于显示的 ConstraintLayout 中。

以下代码示例展示了如何为一个按钮移动到屏幕底部添加动画效果。

// MainActivity.kt

fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.keyframe_one)
    constraintLayout = findViewById(R.id.constraint_layout) // member variable
}

fun animateToKeyframeTwo() {
    val constraintSet = ConstraintSet()
    constraintSet.load(this, R.layout.keyframe_two)
    TransitionManager.beginDelayedTransition()
    constraintSet.applyTo(constraintLayout)
}
// layout/keyframe1.xml
// Keyframe 1 contains the starting position for all elements in the animation
// as well as final colors and text sizes.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// layout/keyframe2.xml
// Keyframe 2 contains another ConstraintLayout with the final positions.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

其他资源

Sunflower 演示版应用中使用了 ConstraintLayout