本文是 ConstraintLayout 小课堂系列第 2 讲,课程目录:
平均间隔
先看一个需求:存在多个 TextView,他们的宽度是可变的,但它们之间的间隔是相等的,并且要平均分配整个屏幕的宽度。
使用原生的 LinearLayout 也能做到这点,简单看一下如何实现:
添加若干不可见的 View 来填充 TextView 之间的间隙,通过设置相同的 layout_weight 使得这些 View 的尺寸相同,这样 TextView 的间隔就是相同的了。
按照套路应该说这样做的缺点了:
- 可能产生 ViewGroup 嵌套(没错,用 ConstraintLayout 就是为了不嵌套)
- 多余的 View 在绘制过程中浪费 CPU(没错,不嵌套的终极目的就是提高绘制速度)
- 想动态操作这些 TextView 以及间隔,需要给这些间隔 View 添加引用,手动设置 visibility,使用起来很不方便。
那么用 ConstraintLayout 怎么实现这个功能呢?
使用 ConstraintLayout 中的 Chain(链)可以更简单地实现这个需求,不用添加任何辅助对象,只需要在这些 TextView 之间形成链,默认就是平均分配间隔的样子。
Chain(链)
什么是 Chain?
两个 View 在一个方向上互相约束对方就形成了链,多个 View 两两成链,就形成了更长的链,见下图(图来自官网):
这里吐个槽,本文写作时最新 Android Studio 3.3.2 版本无法通过拖拽创建链,需要手写代码。
代码这么写,举个栗子:
<Button
android:id="@+id/buttonOK"
app:layout_constraintEnd_toStartOf="@+id/buttonCancel" 👉 左边的右手牵左手
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/buttonCancel"
app:layout_constraintStart_toEndOf="@+id/buttonOK" 👉 右边的左手牵右手
app:layout_constraintEnd_toEndOf="parent" />
形成链的一系列 View 会表现出一些整体特征,可以设置一些特殊的布局效果,见下图(图来自官网):
- Spread Chain:伸展链,默认设置,形成链的 View 分散排列,间隔相等。
- Spread Inside Chain:内部伸展链,也是平均分配间隔,但 View 和 parent 直接没有间隔。
- Weighted Chain:权重链,与 LinearLayout 的 weight 相似,按比例分配空间大小。
- Packed Chain:打包链,将所有 View 打包在一起,当做整体,居中。
- Packed Chain with Bias:带偏斜的打包链。
如何设置不同的布局效果
数据结构里的链表都有个头节点,它的引用就是整个链表的引用。在 ConstraintLayout 中,左侧第一个或者上方第一个成链的 View 就是头节点。给这个头节点设置链属性,就相当于给整个链设置了链属性。实现以上各种效果需要设置哪些链属性呢,我们一个一个看。
Spread Chain
这个就是开篇提到的平均间隔布局了,只要结成链,默认就是这个效果。
设置方法:
- 链头节点设置 chainStyle
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintVertical_chainStyle="spread"
- 所有 View 设置 wrap_content
android:layout_width="wrap_content"
android:layout_height="wrap_content"
与 LinearLayout 的实现相比:
- 没有多余的 View,不用维护多个间隔 View 的引用
- 如果要隐藏某个 TextView,直接设置为 gone 就行了,剩余的 TextView 仍然是平均分隔的。
通过设置 margin 可以微调一下间隔,margin 相当于扩大了 TextView 的占用空间,剩余的空间再平分给间隔,有 margin 的 View 看起来间隔会大一些。注意 ConstraintLayout 中使用的 margin 只能是非负数,设置成负数无效。
Spread Inside Chain
设置方法:
- 链头节点设置 chainStyle
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintVertical_chainStyle="spread_inside"
- 所有 View 设置 wrap_content
android:layout_width="wrap_content"
android:layout_height="wrap_content"
这个设置成两边贴着 parent,如果要求左侧贴着 parent,右侧不贴呢?
如果是 LinearLayout,去掉左侧贴边的辅助 View 即可,比较方便。用 ConstraintLayout 的 Chain 怎么做呢?
直接去掉左侧第一个 TextView 对 第二个 TextView 的约束,也就是第一个的右手放开第二个的左手。这样整个链就少了一个 View,第一个 View 没有了右侧约束,直接靠在了左侧 parent 上,剩下的 View 仍然是一个链,分配剩余的空间。
Weighted Chain
设置方法:
- chainStyle:设置成任何值都没区别,不用设置
- 至少有一个 View 设置成
match_constraint
android:layout_width="0dp"
android:layout_height="0dp"
- 可选的 weight
app:layout_constraintHorizontal_weight="1"
app:layout_constraintVertical_weight="1"
只要在链上有一个 View 设置为了 match_constraint
,这些 View 之间的剩余空间都会被占用,因此 chainStyle 属性设置为任何一个都没有区别。
如果不设置 weight 属性,就有点复杂了:
- 所有
match_constraint
的 View 都不设置。大家平分,与都设置了weight="1"
的效果相同。 - 一些 View 设置了 weight 属性,一些 View 没设置。没设置的 View 完全消失了,所占空间被分配给剩余的 View 了。
- 设置了
weight="0"
的 View 完全消失,剩余空间被其他 View 瓜分。 - 有趣的是,如果只有不设置的和设置为
weight="0"
的,则相当于都不设置,大家平分。
Packed Chain & Packed Chain with Bias
设置方法:
- 链头节点设置 chainStyle
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintVertical_chainStyle="packed"
- 所有 View 设置为 wrap_content
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- 链头节点设置 bias
app:layout_constraintHorizontal_bias="0.1"
打包链将整个链中的所有 View 打包成一个整体,可以与其他 View 关联约束。可能的应用场景是多个可变长的 TextView 紧挨在一起,并且它们的整体要在某个范围内居中。如果不用 ConstraintLayout,就只能将它们嵌套在一个单独的 View 中来做到了,你看,这就是 ConstraintLayout 能减少嵌套的原因👏👍。
Bias 属性是用来设置一个 View 被居中时,偏向某一侧的程度。通常取值范围是 [0, 1],表示左侧或上侧的空白区域占所有空白区域的范围。有趣的是,这个值也可以取 [0, 1] 之外的值,这个 View 就超出了约束的范围,小于 0 向左移,大于 0 向右移。
回到 Packed Chain with Bias,由于打包效果,整个链上的 View 对于其他 View 来说相当于一个 View,设置的 bias 属性也与一个 View 的 bias 相同。
总结
经过上面的讲解可以看到 ConstraintLayout 的链布局非常强大,可以实现的布局效果非常丰富,如果有什么难度较高的布局普通方法实现不了,可以考虑用链式布局来思考。
(ole)