居中
让一个控件居中于⽗容器
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
居中于控件中⼼
⽔平⽅向居中
app:layout_constraintStart_toStartOf="@id/view"
app:layout_constraintEnd_toEndOf="@id/view
垂直⽅向居中
app:layout_constraintTop_toTopOf="@id/view"
app:layout_constraintBottom_toBottomOf="@id/view"
居中于控件的边
例如这样的效果,猫头在上边图片下边的中间:
app:layout_constraintTop_toBottomOf="@id/view"
app:layout_constraintBottom_toBottomOf="@id/view"
填充
⽔平⽅向填充⽗容器(通过 match_constraint )
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
上边的代码就很适合下边的逻辑,比如我们需要将下边图片中的按钮变成和 ImageView 的高度一样大的话,就可以使用match_constraint或者是使用android:layout_height="0dp"他们的意思都表示铺满约束的意思。
改完之后就变成了这样:
如果使用 match_parent,这个是铺满父布局,不一定会有这样的效果。
权重
为⽔平⽅向的控件设置权重,⼤⼩为 2:1:1
<!-- (view-1) -->
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="2"
<!-- (view-2) -->
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
<!-- (view-3) -->
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="1"
⽂字基准线对⻬
例如这样的效果,即便两个字体的大小99和%不一致,也可以实现效果:
app:layout_constraintBaseline_toBaselineOf
直接使用底部对齐是无法实现的:
圆形(⻆度)定位
通过「圆⼼」「⻆度」「半径」设置圆形定位
app:layout_constraintCircle="@id/view"
app:layout_constraintCircleAngle="90"
app:layout_constraintCircleRadius="180dp"
可以实现类似于这样的效果:
约束限制
限制控件⼤⼩不会超过约束范围。
限制宽度
app:layout_constrainedWidth="true"
限制高度
app:layout_constrainedHeight="true
例如这个图中,我们可以通过限制文本的宽度来实现其显示长度不超过图片的宽度,完整源码:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="HardcodedText">
<ImageView
android:id="@+id/avatar"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="40dp"
android:src="@mipmap/ic_launcher_round"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@color/colorPrimary"
android:text="长文本长文本长文本长文本长文本文本长文本长文本"
android:textColor="@android:color/white"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="@+id/avatar"
app:layout_constraintHorizontal_bias="0.508"
app:layout_constraintStart_toStartOf="@+id/avatar"
app:layout_constraintTop_toBottomOf="@+id/avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>
偏向 (bias)
控制控件在垂直⽅向的0.0~1.0的位置的位置,让一个控件出现在指定方向的百分比位置上。这个 bias(偏向)属性,是“在它约束的范围内”的百分比位置——不是整个屏幕,而是“由你的 top/bottom 约束确定的那一段区间”。水平方向当然就是“start/end”的区间了。
例如:控制控件在垂直⽅向的 30%的位置:
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.3
该值默认值是0.5,同时除了Vertical外还有layout_constraintHorizontal_bias类型的。
垂直⽅向居顶部
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constrainedHeight="true"
app:layout_constraintVertical_bias="0.0"
layout_goneMarginStart
当使用这个属性的控件所依赖的控件GONE掉之后,这个属性可以增加一个默认的边距
使用示例:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="HardcodedText">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="长文本长文本"
android:textColor="@android:color/white"
android:textSize="28sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
app:layout_goneMarginStart="16dp"
android:id="@+id/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher_round"
app:layout_constraintStart_toEndOf="@id/textview"
app:layout_constraintTop_toTopOf="@id/textview" />
</androidx.constraintlayout.widget.ConstraintLayout>
约束链
在约束链上的第⼀个控件上加上 chainStyle ,⽤来改变⼀组控件的布局⽅式。只有使用在第一个水平或者垂直的控件上边才会有效果。
三种 chainStyle 含义
- spread(扩散):
控件均匀分布在链的两端,各自间隔平均。
- spread_inside(内部扩散):
首尾两个控件贴边,中间控件均匀分布在链中间。
- packed(打包):
全部控件紧紧贴在一起,整体可以左右/上下偏移(bias) 。
垂直⽅向 packed:
app:layout_constraintVertical_chainStyle="packed
典型宽高比写法
假设你要让一个 ImageView 保持 16:9 宽高比,且要自动撑满屏幕宽度,高度自适应:
<ImageView
android:id="@+id/iv"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="16:9"
/>
解释:
layout_width="0dp":宽度由约束决定(就是 match constraints),代表让宽度自动拉满两侧。layout_constraintDimensionRatio="16:9":宽高比 16:9。layout_height="wrap_content":系统会根据宽度和比例自动算出高度。
反过来,如果你想让高度撑满父容器,宽度自适应:
<ImageView
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="16:9"
/>
layout_height="0dp":高度被约束拉满,宽度系统帮你算出来。
一些容易踩坑的点
- 只有 match_constraints(0dp)那一边才会根据 ratio 参与计算。
你随便写wrap_content或match_parent,系统就直接测量内容或父布局,不用 ratio。 - 配合其它约束一起用(比如左右/上下都贴 parent),ratio 才会生效。
- 宽高都 0dp 也可以,但要有其它辅助约束来给最终确定形状。
正确原理:
- wrap_content 或 固定值:
这一边的尺寸是“已知的”,就是系统能直接测量(内容多大就多大,或者就是写死的数值)。 - 0dp(match constraints) :
这一边是“未知的”,会根据比例(ratio)+ 已知那一边的尺寸,算出来。
Ratio 的进阶写法
app:layout_constraintDimensionRatio="H,2:1"
让“高度 = 2 × 宽度”,并且用高度来反推宽度(H = height based)。W,3:4"
用宽度为主导,宽:高 = 3:4。
其实大部分时候写 "16:9" 或 "1:1" 就够用。
为什么要设置 0dp?
- 在 ConstraintLayout 中,使用
wrap_content或者固定值(如 100dp)就意味着这个尺寸是已经“定死”或由内容大小决定的,没有可伸缩的余地。 - 而
match_constraints(也就是设置为0dp)表示这个边可以被“约束规则”所支配,它本身不主动决定大小。因此,如果想通过某个规则(例如长宽比 ratio)去计算出宽/高,就必须让它处于可计算、可被约束的状态,这时候设置为0dp就是关键。
<ImageView
android:layout_width="0dp" <!-- match_constraints也可以 -->
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
- 这里
android:layout_width="0dp"表示宽度由 ConstraintLayout 的约束来决定(即 match constraints)。 app:layout_constraintDimensionRatio="16:9"表示宽高比为 16:9。- 当运行时,会先根据高度计算出宽度,或者根据宽度计算出高度,具体计算方式和约束条件有关,一般会取决于其他约束以及哪边是
0dp(match constraints)。
注意:如果我们想让“宽度”基于“高度”来计算,需要把“高度”设置为 match constraints 并且“宽度”可以是 wrap_content 或 match constraints,反之亦然。
在 ConstraintLayout 的 1.1.0+ 版本中,可以用前缀 W, 或 H, 来显式指定谁是参考边。
-
W 代表 width
"W,16:9"表示「宽:高 = 16:9」
-
H 代表 height
"H,16:9"表示「高:宽 = 16:9」
比如:
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="W,16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
这里等同于前面用法中 "16:9",因为默认其实就是宽:高。
注意事项
-
至少有一个维度(宽或者高)要设置成
0dp(match constraints)
只有这样,ConstraintLayout 才能根据另一个维度以及比例去计算当前维度。 -
避免与固定值/
wrap_content错配-
如果你想让一个组件“宽高都随父布局变化”,那就需要同时把 width / height 设成
0dp并有至少一个方向能被计算出来(通常还需要配合其他约束如 start_toStartOf, end_toEndOf, top_toTopOf, bottom_toBottomOf 等)。 -
如果你写成类似
android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintDimensionRatio="16:9"这时系统会尝试先测量内容,再按照宽高比去调节,但可能会导致比例被忽略,或宽高约束无法生效。
-
-
与其他约束配合使用
比如你想让 View 填充宽度,那么需要给它app:layout_constraintStart_toStartOf="parent"和app:layout_constraintEnd_toEndOf="parent",然后宽度设为0dp,再通过layout_constraintDimensionRatio算出高度。
百分⽐布局
- 需要对应⽅向上的值为 match_constraint
- 百分⽐是 parent 的百分⽐,⽽不是约束区域的百分⽐
例如:宽度是⽗容器的 30%
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.3"
辅助控件
GuideLine(引导线)
概述
GuideLine 是一种虚拟的辅助线,可以水平或垂直地放置在布局中,用于对齐和定位其他视图。它本身不会显示在屏幕上,但可以在设计布局时进行参考。
使用场景
- 对齐多个视图:通过在相同的位置使用 GuideLine,可以确保多个视图在相同的位置对齐。
- 响应式设计:在不同屏幕尺寸下,通过百分比位置的 GuideLine 保持布局的一致性。
示例
<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">
<!-- 垂直 GuideLine,位置为父宽度的 50% -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<!-- 水平 GuideLine,位置为父高度的 30% -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.3" />
<!-- 使用 GuideLine 对齐的视图 -->
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintStart_toStartOf="@id/guideline_vertical"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical"
app:layout_constraintTop_toTopOf="@id/guideline_horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>
注意事项
- 百分比定位:
layout_constraintGuide_percent可以使用百分比(0.0 到 1.0)定位 GuideLine。 - 固定偏移:也可以使用
layout_constraintGuide_begin或layout_constraintGuide_end来设置固定的偏移量。
Group(组)
概述
Group 用于将多个视图组织在一起,便于同时控制它们的可见性(visibility)和启用状态(enabled)。它不会影响布局,只是一个逻辑上的分组工具。
使用场景
- 批量控制视图属性:例如,当需要同时隐藏或显示一组视图时,可以使用 Group 来简化操作。
- 提高代码可读性:将相关视图分组,便于管理和维护。
示例
<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/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2"
app:layout_constraintTop_toBottomOf="@id/button1"
app:layout_constraintStart_toStartOf="parent" />
<!-- Group 定义 -->
<androidx.constraintlayout.widget.Group
android:id="@+id/group_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="button1,button2" />
</androidx.constraintlayout.widget.ConstraintLayout>
val group = findViewById<Group>(R.id.group_buttons)
group.visibility = View.GONE // 隐藏 group 内所有视图
注意事项
- 属性限制:Group 只能控制
visibility和enabled属性,不能控制位置或尺寸。 - 引用视图:通过
app:constraint_referenced_ids属性引用需要分组的视图,多个 ID 用逗号分隔。
Layer(层)
概述
Layer 是 ConstraintLayout 的一个功能,允许将多个视图组合在一起,并为这些组合视图设置统一的属性或进行动画操作。它类似于图层的概念,可以帮助更好地管理复杂的视图层级。
使用场景
- 组合动画:对一组视图同时应用动画效果。
- 统一属性设置:例如,统一调整一组视图的透明度、旋转等属性。
示例
假设有三个按钮,想要将它们组合在一起,并对整个组合应用旋转和缩放效果。
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 按钮 1 -->
<Button
android:id="@+id/button1"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="按钮1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 按钮 2 -->
<Button
android:id="@+id/button2"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="按钮2"
app:layout_constraintStart_toEndOf="@id/button1"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="16dp" />
<!-- 按钮 3 -->
<Button
android:id="@+id/button3"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="按钮3"
app:layout_constraintStart_toEndOf="@id/button2"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="16dp" />
<!-- 定义 Layer -->
<androidx.constraintlayout.helper.widget.Layer
android:id="@+id/layer_buttons"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="button1,button2,button3"
app:rotation="15"
app:scaleX="1.2"
app:scaleY="1.2"
app:translationX="10dp"
app:translationY="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.helper.widget.Layer
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var layer: Layer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
layer = findViewById(R.id.layer_buttons)
// 示例:点击某个按钮时,改变 Layer 的透明度
val button1 = findViewById<View>(R.id.button1)
button1.setOnClickListener {
layer.alpha = 0.5f // 设置 Layer 透明度为 50%
}
// 示例:点击另一个按钮时,恢复 Layer 的透明度
val button2 = findViewById<View>(R.id.button2)
button2.setOnClickListener {
layer.alpha = 1.0f // 设置 Layer 透明度为 100%
}
}
}
Barrier(障碍)
概述
Barrier 用于在布局中根据一组视图的实际位置动态创建一条参考线(或参考边)。这条参考线能够帮助其他视图进行对齐或排列。相比 GuideLine(在布局初始化时位置就固定),Barrier 会根据被引用视图尺寸的变化而动态移动,从而满足更灵活的布局需求。
Barrier 可以理解为一条“动态的约束线”。它具有以下特征:
- 自身不可见:和
Guideline一样,Barrier 并不会在界面上显示。 - 动态定位:Barrier 的位置由它所引用的一组视图共同决定。例如,当你设置
barrierDirection="end"并引用若干文本视图时,Barrier 的位置会始终位于那几个文本视图最右边界的最大值之后(再加上一点点偏移),无论这些文本视图的内容或长度如何变化,Barrier 都会动态更新位置。 - 可作为对齐基准:在其他视图的约束中,可以使用
Barrier的start,end,top,bottom来对齐或排布,达到自适应布局的效果。
使用场景
-
动态宽度对齐:如果有多个视图的宽度不确定(例如有些文本会根据内容长度变化),需要在这些不确定宽度的视图后面再放置一个按钮或图片,让该按钮/图片始终贴在最宽的视图之后,就可以使用
Barrier。 -
动态高度对齐:同理,如果有多个视图高度可能随着内容变化而改变,需要在这些视图下面对齐另一个视图,就可以使用
Barrier。 -
响应式布局:在多语言、多屏幕适配时,内容长度经常会发生变化。使用
Barrier可以在布局层面自动处理这些变化,无需繁琐地在代码中计算位置或尺寸。
Barrier 的关键属性
-
app:barrierDirection
- 表示
Barrier的方向,可能的取值有start,end,top,bottom。 - 例如,
barrierDirection="end"表示此Barrier会对齐一组视图的右边界中“最靠右”的那个点。
- 表示
-
app:constraint_referenced_ids
- 用于声明这个
Barrier所引用的一组视图的 ID。可以引用一个或多个视图,多个视图 ID 之间使用逗号分隔。 - 例如:
app:constraint_referenced_ids="text1,text2,text3"。
- 用于声明这个
-
app:barrierAllowsGoneWidgets
- 当被引用的视图是 GONE 状态时,该视图是否会影响
Barrier的计算。 true表示即使视图是GONE,也会将其位置视为 0 宽或 0 高来参与计算;false表示视图处于 GONE 时,直接忽略它的大小。
- 当被引用的视图是 GONE 状态时,该视图是否会影响
示例
有三个 TextView,它们的文本长度不同,宽度也不一样。我们想在它们最右边放置一个按钮,让这个按钮始终贴在“最右的文本右边界(也可以理解为宽度最长的文本)”之后。
<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">
<!-- 第一个文本,文本内容较短 -->
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="短文本"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" />
<!-- 第二个文本,文本内容较长 -->
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个比较长的文本内容,用于演示 Barrier 的位置变化"
app:layout_constraintTop_toBottomOf="@id/text1"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" />
<!-- 第三个文本,中等长度 -->
<TextView
android:id="@+id/text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中等长度文本"
app:layout_constraintTop_toBottomOf="@id/text2"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" />
<!-- Barrier 定义,方向为 end -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="text1,text2,text3"
app:barrierAllowsGoneWidgets="true" />
<!-- 需要贴在最右文本之后的按钮 -->
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作"
app:layout_constraintTop_toTopOf="@+id/text1"
app:layout_constraintStart_toEndOf="@id/barrier_right"
android:layout_marginStart="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
示例说明:
-
TextView 的位置
- 三个
TextView的宽度各不相同(取决于文本内容),我们并不清楚它们在实际渲染后的宽度。
- 三个
-
Barrier 的方向
- 代码中设置了
app:barrierDirection="end",表示这个Barrier会自动放在三个文本视图中最靠右的那个右边缘之后。 barrierAllowsGoneWidgets="true"表示如果有视图被设置为View.GONE,Barrier依然会考虑它的尺寸(此时 GONE 视图尺寸为 0),以免造成布局意外塌陷。根据实际需要可设置为false。
- 代码中设置了
-
按钮的约束
- 按钮的
Start约束连接到Barrier的End:app:layout_constraintStart_toEndOf="@id/barrier_right"。 - 这意味着按钮的左边缘始终贴着 Barrier 的右边缘。由此保证无论哪一个文本的宽度最大,按钮都会放在它的右边。
- 按钮的
注意事项
- 方向设置:通过
app:barrierDirection属性设置 Barrier 的方向,可以是start、end、top、bottom。 - 引用视图:通过
app:constraint_referenced_ids属性引用需要计算 Barrier 位置的视图,多个 ID 用逗号分隔。 - 动态调整:Barrier 会根据引用视图的当前位置和尺寸动态调整自身位置,适用于内容动态变化的场景。
Placeholder(占位符)
概述
本身在界面上并不可见,但能在布局中“预留”出一块空间,然后在运行时将其他视图“放”到这个 Placeholder 所在的位置。
-
本质
- Placeholder 是一个继承自
ConstraintHelper的“虚拟视图”。它本身并不会渲染可见的内容,而是用来放置其他视图。
- Placeholder 是一个继承自
-
核心功能
- 可以在预先定义的布局位置(Placeholder 所在的区域),在运行时将某个视图移动或替换到这个位置上,以实现动态布局切换或过渡动画。
-
布局特性
- 可以像普通视图一样,为 Placeholder 设置约束和大小(通常为
match_constraints或固定大小),以此确定它在布局中的位置和区域。 - 当使用
placeholder.setContentId(R.id.some_view)时,ConstraintLayout 会把some_view移动到 Placeholder 的约束位置,并继承 Placeholder 的约束和尺寸。
- 可以像普通视图一样,为 Placeholder 设置约束和大小(通常为
Placeholder 的常见使用场景
-
视图内容切换
- 比如有时要在同一个容器区域里显示一个图片,切换到下一步后又显示一个文字说明,或者表单等;此时可以通过 Placeholder 动态地切换要显示的视图 ID,实现快速的内容替换。
-
动画过渡
- 使用 MotionLayout(ConstraintLayout 的扩展)或其他动画时,可以把 Placeholder 作为目标位置,让一个视图在动画中从自己的原位置移动到 Placeholder 所在位置,使得视觉过渡更加流畅且代码更简洁。
-
占位示例 / 骨架屏
- 部分应用会先显示一个“加载中”或“骨架屏”样式,在数据加载完成后再替换成真正的内容视图。Placeholder 可以很好地完成这个占位到真实内容的切换。
示例
<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">
<!-- Placeholder 定义 -->
<androidx.constraintlayout.widget.Placeholder
android:id="@+id/placeholder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- 要替换的视图 -->
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/sample_image"
android:visibility="gone" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="动态文本"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
// 在 Kotlin 中动态替换 Placeholder 的内容
val placeholder = findViewById<Placeholder>(R.id.placeholder)
val imageView = findViewById<ImageView>(R.id.imageView)
val textView = findViewById<TextView>(R.id.textView)
// 替换 Placeholder 为 ImageView
placeholder.setContentId(R.id.imageView)
// 或者替换为 TextView
placeholder.setContentId(R.id.textView)
与动画或 MotionLayout 搭配
Placeholder 在日常使用时,也常常结合 MotionLayout 来做过渡动画。大致流程是:
- 在 MotionScene 中定义不同的 ConstraintSet,让同一个 Placeholder 在不同状态中使用不同视图或不同的属性。
- 在动画过渡(Transition)中切换
setContentId或者切换视图的可见性,从而实现平滑的视图内容变换。
这比传统的在代码中手动管理坐标或切换布局更加直观、简洁。
注意事项
-
占位尺寸
- Placeholder 的尺寸/约束决定了被替换视图的最终大小和位置。
- 如果 Placeholder 是
wrap_content,就需要确保你能准确给它一个大小;如果是0dp(match_constraints),则要结合相应的约束(如Start,End,Top,Bottom)或长宽比来确定它所占的空间。
-
视图初始可见性
- 如果不想在“切换”发生前就看到视图的实际位置,可以让被替换视图初始状态为
GONE。 - 当你调用
setContentId时,会自动将视图移动到占位区域,并将其可见性调为VISIBLE(如果之前是GONE)。
- 如果不想在“切换”发生前就看到视图的实际位置,可以让被替换视图初始状态为
-
对约束的影响
- 使用
setContentId会让该视图忽略它原本在 XML 中的约束,转而继承 Placeholder 的约束。 - 如果原本的视图有非常复杂的约束,记得再三确认切换后不会产生冲突。
- 使用
-
动态替换多个视图
- 可以在代码中根据业务逻辑随时调用
placeholder.setContentId(R.id.viewA)、placeholder.setContentId(R.id.viewB)等把不同视图移动到相同的位置上,从而做内容切换或空间共享。
- 可以在代码中根据业务逻辑随时调用
Flow(流布局)
概述
用于在同一个 ConstraintLayout 中实现类似 Linearlayout 或 FlexboxLayout 的“流式”布局。它能够根据子视图(被引用的视图)的数量和大小,自动进行多行或多列的排列,同时支持对齐方式、间距设置、最大元素数限制等多种灵活配置。Flow 是 ConstraintHelper 的一个子类(Flow extends VirtualLayout extends ConstraintHelper),可以在同一个 ConstraintLayout 内对多视图进行自动排列。它在布局时,会根据配置的 布局模式、间距、对齐方式 等,将所有被引用的视图按照一定规则进行“流式”排布:
- 可以按行或按列自动换行/换列;
- 可以指定一行或一列中能容纳的 最大元素数量;
- 可以控制每个元素之间的 水平和垂直间距;
- 可以指定 对齐模式、重心偏移 等。
Flow 的使用场景
-
标签云/流式按钮组
- 当有一组标签或按钮,需要在空间不够的情况下自动换行,并设定标签或按钮之间的间距。
- 例如电商APP中的商品标签、社交应用中的兴趣标签等。
-
自适应网格
- 需要根据屏幕宽度或动态内容,自动将子元素按一定规则“流式”摆放。
- Flow 可以指定每行(列)最大元素数,也可以指定高度或宽度限制,来模拟简单的网格布局。
-
相对复杂的动态布局
- 当应用内容是动态生成或数量不固定时,Flow 能够在不改变布局层级(仍是单层 ConstraintLayout)的情况下,为子视图提供自动排列与换行功能。
Flow 的核心属性
Flow 在 XML 中常用的属性列表如下(大部分以 app:flow_ 开头):
-
app:constraint_referenced_ids- 指定由 Flow 管理的视图 ID,多个视图以英文逗号分隔。
- 示例:
app:constraint_referenced_ids="button1,button2,button3"
-
app:flow_wrapMode-
决定 Flow 如何换行或换列,常见取值:
- none:不换行,所有子视图均在同一行(或同一列)中排布。
- chain:当到达
maxElementsWrap或空间不足时,会自动换行/换列。 - aligned:与
chain类似,但会对行/列进行对齐,使各行/列在可见尺寸上保持对齐。
-
-
app:flow_maxElementsWrap- 指定在自动换行/换列前,单行(或单列)最多可容纳的子元素个数。
- 示例:
app:flow_maxElementsWrap="3"表示每行最多放 3 个元素,之后自动换行。
-
app:flow_horizontalGap/app:flow_verticalGap- 子视图之间的 水平/垂直间距(单位:dp)。
- 示例:
app:flow_horizontalGap="8dp"表示每个子视图之间有 8dp 的水平间距。
-
app:flow_horizontalStyle/app:flow_verticalStyle-
子视图在水平方向 / 垂直方向的分配模式,可选值包括:
- spread(默认,平均分配空白)
- wrap(只占用实际宽度/高度)
- packed(全部堆在一起,可通过 bias 再做偏移)
-
-
app:flow_horizontalBias/app:flow_verticalBias- 当使用了
packed模式或类似情况时,可以使用bias控制子视图在剩余空间里的对齐位置,取值区间[0..1],0 表示靠“start”或“top”,1 表示靠“end”或“bottom”。
- 当使用了
-
app:flow_firstHorizontalStyle,app:flow_lastHorizontalStyle等- 可以对第一行、最后一行使用不同的分配模式,适用于更复杂的场景。
此外,还有一些其他属性,如 flow_firstHorizontalBias, flow_lastHorizontalBias, flow_firstVerticalStyle 等,用于针对首行、尾行做特殊处理。大部分场景下,你只需要掌握最常用的 flow_wrapMode、flow_maxElementsWrap、flow_horizontalGap、flow_verticalGap 等即可。
示例
有 5 个按钮,想要让它们在一行中摆放,当空间不足或视图数量超过某个限制时自动换行,并在水平方向和垂直方向上设置固定的间距。
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Flow 组件 -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="button1,button2,button3,button4,button5"
app:flow_wrapMode="chain"
app:flow_maxElementsWrap="3"
app:flow_horizontalGap="8dp"
app:flow_verticalGap="8dp"
/>
<!-- 子视图们:5 个按钮 -->
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮3" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮4" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮5" />
</androidx.constraintlayout.widget.ConstraintLayout>
示例说明:
Flow自身是一个 ConstraintHelper,本身并不绘制可见内容,也不需要放在特定的层级结构内;它只需要在同一个 ConstraintLayout 中即可。constraint_referenced_ids="button1,button2,button3,button4,button5"表示 Flow 将管理这 5 个按钮的摆放位置。flow_wrapMode="chain"+flow_maxElementsWrap="3"表示在一行最多放 3 个按钮,超过 3 个就自动换到下一行。flow_horizontalGap="8dp"、flow_verticalGap="8dp"表示相邻元素之间的间距。android:layout_width="0dp"+app:layout_constraintStart_toStartOf和app:layout_constraintEnd_toEndOf表示 Flow 这个辅助组件的“占位宽度”会随父容器的宽度进行拉伸,以便在内部进行更灵活的子视图排布。
示例2: 想要在垂直方向上进行流式排列,每列最多放 2 个按钮,并让它们在垂直方向上居中对齐:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_vertical"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:constraint_referenced_ids="buttonA,buttonB,buttonC,buttonD"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:flow_wrapMode="chain"
app:flow_maxElementsWrap="2"
app:flow_horizontalGap="16dp"
app:flow_verticalGap="16dp"
app:flow_orientation="vertical" <!-- 垂直方向流式排列 -->
app:flow_verticalStyle="packed" <!-- 垂直方向子视图堆叠在一起 -->
app:flow_verticalBias="0.5" <!-- 垂直方向偏移量 0.5 表示居中 -->
/>
<!-- 四个按钮 -->
<Button
android:id="@+id/buttonA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A" />
<Button
android:id="@+id/buttonB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="B" />
<Button
android:id="@+id/buttonC"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C" />
<Button
android:id="@+id/buttonD"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="D" />
</androidx.constraintlayout.widget.ConstraintLayout>
示例2说明:
-
app:flow_orientation="vertical":将 Flow 切换到 竖直方向的排布模式。默认值是horizontal。 -
app:flow_maxElementsWrap="2":表示每列最多容纳 2 个子视图。放入第三个子视图时,就会自动换到下一列。 -
app:flow_verticalStyle="packed"+app:flow_verticalBias="0.5":将子视图在垂直方向上“包裹”到一起,并在容器可用空间中居中显示。 -
android:layout_width="wrap_content"+android:layout_height="0dp"+ 垂直的上下约束,意味着 Flow 的高度会“填满”父布局的可用高度,并允许子元素在内部根据 bias 做居中或其他排布。
关键属性
- app:flow_wrapMode:定义换行模式,可选值有
none(不换行)、chain(链式换行)、aligned(对齐换行)。 - app:flow_horizontalGap 和 app:flow_verticalGap:设置子视图之间的水平和垂直间距。
- app:flow_maxElementsWrap:设置每行(或列)的最大元素数目,当达到限制时自动换行。
- app:constraint_referenced_ids:指定 Flow 管理的子视图 ID,多个 ID 用逗号分隔。
注意事项
-
被 Flow 管理的视图依旧需要在同一个 ConstraintLayout 中
Flow只能引用同一ConstraintLayout下的子视图 ID。无法跨布局引用视图。
-
不要给被 Flow 引用的子视图再添加相互矛盾的约束
- 一旦视图被 Flow 管理,它们的
位置就主要由 Flow 决定。你可以给子视图做一些如大小约束等,但尽量避免再指定大量相互冲突的Start_toStartOf或Top_toTopOf等,否则可能导致布局无法正常测量或出现意外。
- 一旦视图被 Flow 管理,它们的
-
动态增删视图
- 如果要在运行时动态增删被引用的视图,你需要在 Java/Kotlin 代码里重新设置 Flow 的引用 IDs(
setReferencedIds()),或通过ConstraintSet动态更新。如果只是隐藏某些视图(View.GONE),Flow 会自动把它们忽略,但需要注意布局刷新。
- 如果要在运行时动态增删被引用的视图,你需要在 Java/Kotlin 代码里重新设置 Flow 的引用 IDs(
-
性能
- Flow 在大多数常见场景下性能都是足够的。只有当你需要频繁、大量地增删视图或进行动画时,才需要特别关注性能问题。一些极端复杂的场景下,可能还需手动优化或使用更专业的布局方案。
总结
ConstraintLayout 提供了多种辅助工具和组件,如 GuideLine、Group、Layer、Barrier、Placeholder 和 Flow,以帮助构建灵活、高效和响应式的布局。了解并合理使用这些组件,可以显著提升布局的可维护性和适应性。
快速参考
| 组件 | 用途描述 | 关键属性 |
|---|---|---|
| GuideLine | 虚拟辅助线,用于对齐和定位其他视图 | layout_constraintGuide_percent、orientation |
| Group | 逻辑分组,用于批量控制视图的 visibility 和 enabled | constraint_referenced_ids |
| Layer | 组合视图,统一设置属性或应用动画 | 具体属性依赖于实现 |
| Barrier | 动态参考线,根据一组视图的位置动态调整 | barrierDirection、constraint_referenced_ids |
| Placeholder | 占位符,用于动态替换或展示其他视图 | setContentId 方法 |
| Flow | 流式布局,自动排列子视图 | flow_wrapMode、flow_horizontalGap、constraint_referenced_ids |
ConstraintHelper
ConstraintHelper 是一个非常核心且抽象的“辅助类”基类,许多常用的辅助组件(如 Barrier、Group、Layer、Flow、Placeholder)都是继承自 ConstraintHelper。它的主要作用是:在不直接参与可见视图的前提下,通过引用一组目标视图,对它们的布局或属性进行批量或动态的控制。
一、ConstraintHelper 是什么?
- 抽象基类:
ConstraintHelper继承自View(或者更准确地说是ViewGroup的子类,但它自身不显示内容),在 ConstraintLayout 体系下用作对“引用视图的批量处理”。 - 引用视图:它有一个非常重要的属性 ——
constraint_referenced_ids,通过它可以指定若干需要被“帮助”或“管理”的视图,多个视图的 ID 用逗号分隔。 - 布局流程:ConstraintHelper 在布局流程(
onMeasure、onLayout等)中会参与 ConstraintLayout 的约束解析,可以在这其中对被引用视图做批量的调整、计算或变换。
简单来说,你可以把 ConstraintHelper 想象成“非可视化的布局组件”,它不展示内容,却能集中管理一组可见视图,或对它们进行统一的逻辑处理。
二、ConstraintHelper 的常见子类
在日常开发中,我们或多或少都用过以下组件——它们都继承自 ConstraintHelper,只是实现了各自特殊的功能:
-
Group
- 主要用于批量控制一组视图的可见性(
visibility)和启用状态(enabled)。 - 不会影响视图的尺寸和位置,只是一个对若干视图的逻辑分组。
- 主要用于批量控制一组视图的可见性(
-
Barrier
- 动态参考线,用于根据被引用视图的位置(最左/最右/最上/最下)创建一条虚拟的对齐边。
- 适用于视图宽高不确定时,让其他视图可以“紧贴”这条动态边界。
-
Layer
- 可将一组视图当成一个整体来处理,对这组视图应用统一的变换(旋转、缩放、透明度、位移等)。
- 常见场景:对一组按钮或视图做整体动画。
-
Flow
- 继承自
VirtualLayout(也是ConstraintHelper的子类),可以在 ConstraintLayout 内部实现流式布局、自动换行、多行多列等功能。 - 常见场景:动态标签云、复杂的网格排列等。
- 继承自
-
Placeholder
- 一个“占位符”视图,可在运行时用来“挂载”其他视图,常用于动态切换视图或做动画过渡。
-
VirtualLayout
- 其实
Flow就是VirtualLayout的一个实现。“虚拟布局”可以在约束层面或绘制层面帮助管理一组视图,避免多重嵌套布局。
- 其实
这些子类通过继承 ConstraintHelper 并实现或重写特定方法(如 updatePreLayout()、updatePostLayout() 等),实现了各自的功能逻辑。
三、ConstraintHelper 的核心属性
-
app:constraint_referenced_ids
- 这是
ConstraintHelper最常用的属性,用于指定需要被管理的视图。 - 多个视图的 ID 用英文逗号分隔,例如
app:constraint_referenced_ids="view1,view2,view3"。 - 这些 ID 对应的视图必须在同一个 ConstraintLayout 中。
- 这是
-
其他自定义属性
- 不同的子类会增加自身需要的自定义属性。
- 例如
Barrier有barrierDirection、barrierAllowsGoneWidgets;Flow有flow_wrapMode、flow_horizontalGap、flow_verticalGap;Layer有rotation、scaleX、scaleY、alpha等。
四、自定义 ConstraintHelper
如果内置的 Barrier、Group、Flow 等已经满足需求,通常不需要自己去继承 ConstraintHelper。
但如果想实现一些特殊的“批量控制”或“动态计算”,可以尝试自定义一个类,例如 MyCustomHelper,继承 ConstraintHelper,然后在其中重写以下方法(关键):
-
init(AttributeSet attrs, int defStyleAttr)
- 用于在构造函数中解析自定义属性(如果有的话),并初始化相关逻辑。
-
updatePreLayout(ConstraintLayout container)
- 布局前的更新。在这里可以拿到被引用视图的列表,通过
getViews(container)获取具体的子视图引用,然后根据需要进行一些前置计算或处理。
- 布局前的更新。在这里可以拿到被引用视图的列表,通过
-
updatePostLayout(ConstraintLayout container)
- 布局后的更新。如果需要在布局完成后根据实际尺寸或位置再次调整什么,可以在这里处理。
-
onDraw(Canvas canvas) (或
dispatchDraw(Canvas canvas))- 如果你的 Helper 需要在屏幕上绘制一些辅助信息(多数情况不会这么做),可以在此处实现自定义绘制逻辑。
- 一般的 ConstraintHelper 子类不会去绘制可见元素,除非像
Flow需要绘制特殊的背景或分隔线。
一个示例的伪代码结构大致如下:
class MyCustomHelper @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
init {
// 解析自定义属性(如果需要)
}
override fun updatePreLayout(container: ConstraintLayout?) {
super.updatePreLayout(container)
// 获取被引用的视图
val views = getViews(container)
// 对 views 做一些批量逻辑处理,例如改变它们的宽高、margin 等
}
override fun updatePostLayout(container: ConstraintLayout?) {
super.updatePostLayout(container)
// 在容器完成布局之后,如果需要再次根据实际位置进行微调,可以做在这里
}
}
然后在布局 XML 中声明并使用这个自定义 Helper:
<com.example.MyCustomHelper
android:id="@+id/my_helper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="view1,view2,view3" />
ConstraintHelper 就是可以在测量/布局阶段对被引用视图做“干预”或“额外计算”的关键所在。、
学后测验
一、单项选择题(每题1分)
1. 下列关于 ConstraintLayout 中的 0dp(match constraints)说法,正确的是:
A. 0dp 等同于 wrap_content
B. 0dp 表示控件宽/高由父容器撑满
C. 0dp 表示该边可由约束和其他规则决定实际大小
D. 0dp 不能和 ratio 属性一起用
【答案与解析】
答案:C
解析:0dp 在 ConstraintLayout 中表示 match_constraints,即该边的大小受限于其它约束和计算,可以搭配比例等使用;不是 wrap_content,也不是简单的 match_parent。
2. 以下哪个属性可以让 TextView 的文字基线与另一个控件对齐?
A. app:layout_constraintBottom_toBottomOf
B. app:layout_constraintBaseline_toBaselineOf
C. app:layout_constraintTop_toTopOf
D. app:layout_constraintStart_toStartOf
【答案与解析】
答案:B
解析:Baseline_toBaselineOf 使两个控件的文本基线对齐,特别适合对齐不同字号的文本控件。
3. 如果你需要让一个视图随另一组视图动态变化边界对齐,应优先用哪种辅助组件?
A. GuideLine
B. Barrier
C. Group
D. Layer
【答案与解析】
答案:B
解析:Barrier 动态参考一组控件的边界,无论这些控件内容怎么变化,Barrier 都能自动移动,是自适应复杂场景的利器。
4. 关于 ConstraintLayout 的 bias(偏向),下列说法正确的是:
A. bias 只能取 0 或 1
B. bias 影响约束范围内控件的位置百分比
C. bias 只能用于水平居中
D. bias 只影响宽度,不影响高度
【答案与解析】
答案:B
解析:bias 取值 [0,1],控制控件在约束范围内的位置,比如 0.3 就是在 30% 的地方。既有水平也有垂直 bias。
二、多项选择题(每题2分)
5. 以下哪些属于 ConstraintHelper 的子类?(多选)
A. Barrier
B. Layer
C. Guideline
D. Flow
E. Group
F. Placeholder
【答案与解析】
答案:A、B、D、E、F
解析:ConstraintHelper 的子类包括 Barrier、Layer、Flow、Group、Placeholder。GuideLine 不是,它有自己独立的实现。
6. 关于 Flow 布局组件,下列说法哪些是正确的?(多选)
A. Flow 支持横向和纵向流式布局
B. Flow 只能在 ConstraintLayout 中引用视图
C. Flow 可以设置元素之间的间距
D. Flow 必须放在被引用控件的父容器外部
E. Flow 会自动管理被引用视图的位置
【答案与解析】
答案:A、B、C、E
解析:Flow 只能引用同一 ConstraintLayout 下的视图,可以设置间距,会自动管理被引用视图的位置,支持横向纵向流式布局。Flow 必须和被管理视图同属于一个 ConstraintLayout。
三、判断题(每题1分)
7. ( ) ConstraintLayout 的 Group 组件可以直接控制分组视图的位置和尺寸。
【答案与解析】
答案:错误
解析:Group 只能控制分组视图的 visibility 和 enabled,不能直接影响布局属性如位置和尺寸。
8. ( ) Placeholder 可以让任意视图在运行时占用 Placeholder 预留的空间和约束。
【答案与解析】
答案:正确
解析:Placeholder 通过 setContentId(),可以让任意被管理视图移动到自身位置,并继承约束和尺寸。
9. ( ) Barrier 只适用于横向布局场景,无法用于垂直对齐。
【答案与解析】
答案:错误
解析:Barrier 可设置方向,支持 start/end/top/bottom,横向、纵向都可以。
四、简答题(每题4分)
10. 简述 ConstraintLayout 的 0dp(match constraints)和 match_parent 的区别?在什么场景应该用 0dp?
【答案与解析】
答案:
0dp 表示 match constraints,只在 ConstraintLayout 下才有意义,表示该方向的尺寸完全受限于其约束和相关规则,可以用来自适应空间(比如流式布局、配合 ratio、百分比宽高等)。
match_parent 表示直接撑满父布局剩余空间,不考虑任何约束,只和父容器尺寸相关。ConstraintLayout 中不建议用 match_parent,容易导致冲突或无效,推荐用 0dp + 约束表达拉伸或自适应需求。
常用场景:
- 需要控件自动撑满两侧(或上下)约束间剩余空间时
- 需要和 ratio、百分比等结合做自适应宽高时
- 用于 Flow/Barrier 这种辅助组件引用的控件宽高设置
11. Barrier 适合哪些场景?请简述实现原理和注意事项。
【答案与解析】
答案:
Barrier 适合一组控件尺寸、内容不确定,且需要让其他控件随其边界自动适配对齐的场景,比如多语言文本、动态列表、响应式按钮布局。
原理:Barrier 动态计算它所引用控件的最大(或最小)边界,形成一条虚拟线,其他控件可以对齐该线,实现自适应。
注意事项:
- 只能引用同一个 ConstraintLayout 下的视图
- 注意 GONE 状态对 Barrier 计算的影响
- 被引用视图尺寸频繁变化时,Barrier 会动态刷新,布局性能可能略受影响
五、编程/布局题(每题5分)
12. [XML] 设计如下布局:屏幕顶部放一个图片,图片正下方有一个横向排列的按钮组,按钮数量不定但需要自动换行,每行最多放3个按钮,并且按钮之间水平和垂直间隔8dp。图片和按钮组之间垂直间距为24dp,所有控件水平方向居中。请写出关键的 ConstraintLayout XML。
【答案与解析】
答案:
<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">
<!-- 顶部图片 -->
<ImageView
android:id="@+id/top_image"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<!-- Flow 负责流式按钮排列 -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_buttons"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:constraint_referenced_ids="btn1,btn2,btn3,btn4,btn5,btn6"
app:flow_wrapMode="chain"
app:flow_maxElementsWrap="3"
app:flow_horizontalGap="8dp"
app:flow_verticalGap="8dp"
app:layout_constraintTop_toBottomOf="@id/top_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_margin="24dp"
/>
<!-- 六个按钮示例,实际数量可增减 -->
<Button android:id="@+id/btn1" ... android:text="按钮1"/>
<Button android:id="@+id/btn2" ... android:text="按钮2"/>
<Button android:id="@+id/btn3" ... android:text="按钮3"/>
<Button android:id="@+id/btn4" ... android:text="按钮4"/>
<Button android:id="@+id/btn5" ... android:text="按钮5"/>
<Button android:id="@+id/btn6" ... android:text="按钮6"/>
</androidx.constraintlayout.widget.ConstraintLayout>
解析:Flow 组件实现流式自动换行,maxElementsWrap 控制每行最大按钮数,按钮引用 id 灵活扩展即可。
13. [Kotlin] 动态实现如下需求:有两个视图 A/B,点击按钮可以让 A 或 B 动态切换到布局中某个占位符 Placeholder 的位置。写出简要实现代码。
【答案与解析】
答案:
val placeholder = findViewById<Placeholder>(R.id.placeholder)
val viewA = findViewById<View>(R.id.viewA)
val viewB = findViewById<View>(R.id.viewB)
val btnSwitch = findViewById<Button>(R.id.btnSwitch)
// 点击按钮时切换显示
btnSwitch.setOnClickListener {
val showingA = placeholder.contentId == R.id.viewA
placeholder.setContentId(if (showingA) R.id.viewB else R.id.viewA)
}
解析:通过 setContentId() 切换 Placeholder 的内容,原视图的约束失效,直接使用占位符的约束。