这是我参与更文挑战的第14天,活动详情查看: 更文挑战
特性详解
Chain(链)
链在单个轴(水平或垂直)中提供类似行的行为。另一个轴可以独立约束。
如果一组控件通过双向连接链接在一起,则它们被视为链(如下图,是最小单位的Chain,只具有两个控件)。
Chain头部
横向上,Chain头部是Chain最左边的控件;纵向上,Chain头部是Chain最顶部的控件。
Chain外边距
如果连接时定义了外边距,Chain就会发生变化。在SPREAD CHAIN
中,外边距会从已经分配好的空间中去掉。
Chain Style(样式)
当对Chain的第一个元素设置layout_constraintHorizontal_chainStyle
或layout_constraintVertical_chainStyle
属性,Chain就会根据特定的样式(默认样式为CHAIN_SPREAD
)进行相应变化,样式类型如下:
CHAIN_SPREAD
元素被分散开(默认样式)- 在
CHAIN_SPREAD
模式下,如果一些控件被设置为MATCH_CONSTRAINT
,那么控件将会把所有剩余的空间均分后“吃掉” CHAIN_SPREAD_INSIDE
Chain两边的元素贴着父容器,其他元素在剩余的空间中采用CHAIN_SPREAD
模式CHAIN_PACKED
Chain中的所有控件合并在一起后在剩余的空间中居中
Weighted chains (带权重的Chain)
默认的Chain是在可用空间中平均分布元素。如果其中有一个或多个元素使用了MATCH_CONSTRAINT
属性,那么他们会将剩余的空间平均填满。属性layout_constraintHorizontal_weight
和layout_constraintVertical_weight
控制使用MATCH_CONSTRAINT
的元素如何均分空间。
例如,一个Chain中包含两个使用MATCH_CONSTRAINT
的元素,第一个元素使用的权重为2,第二个元素使用的权重为1,那么被第一个元素占用的空间是第二个元素的2倍。
When using margins on elements in a chain, the margins are additive.
For example, on a horizontal chain, if one element defines a right margin of 10dp and the next element defines a left margin of 5dp, the resulting margin between those two elements is 15dp.
**Margins and chains **
此属性在1.1版本添加
在链中的元素上使用边距时,边距是相加的。
例如,在水平链上,如果一个元素定义了10dp的右边距,而下一个元素定义了5dp的左边距,则这两个元素之间产生的边距为15dp。 在计算链用于定位项目的剩余空间时,会同时考虑项目及其边距。剩余空间不包含边距。
Virtual Helper objects(辅助工具)
除了前面详述的内在功能之外,您还可以使用ConstraintLayout中的辅助工具来帮助您进行布局。目前,Guideline
对象允许您创建相对于ConstraintLayout容器定位的水平和垂直指南。然后可以通过将小部件限制为这样的指导来定位小部件。在1.1中,也增加了Barrier
和Group
。
Guideline
Guideline 不会显示在设备上(它们标记为View.GONE),仅用于布局目的。它们仅在ConstraintLayout中工作。用处是帮助我们的控件增加约束。就如同Photoshop中参考线的概念一样。如下图,创建一个垂直方向的参考线,可以切换百分比,或者是实际的距离。具体用哪种还是得根据实际情况来。
Barrier
很多时候我们都会遇到控件的大小随着其包含的数据的多少而改变的情况,而此时如果有多个控件之间是相互约束的话,就比较难来设定各个控件间的约束关系了
而 Barrier
(屏障)就是用于这种情况,Barrier
和 GuideLine
一样是一个虚拟的 View,对界面是不可见的,只是用于辅助布局,而 Barrier 和 GuideLine 的区别在于它可以由多个 View 来决定其属性
Barrier 可以使用的属性有:
-
rrierDirection:用于设置 Barrier 的位置,属性值有:bottom、top、start、end、left、right
-
constraint_referenced_ids:用于设置 Barrier 所引用的控件的 ID,可同时设置多个
-
barrierAllowsGoneWidgets:默认为 true,当 Barrier 所引用的控件为 Gone 时,则 Barrier 的创建行为是在已 Gone 的控件已解析的位置上进行创建。如果设置为 false,则不会将 Gone 的控件考虑在内
下面是一个非常简单的例子:
我们有三个TextViews: 左边 textView1 和 textView2 ,右边 textView3。textView3 约束在 textView1 的右边,效果也符合我们的预期。
但是当需要支持多语言的时候事情就变得复杂了。如果我们添加德语就出现了问题,因为在英语里面textView1的文字是长于textView2的,但是在德语中却是textView2的文字比textView1长:
这里的问题在于textView3仍然是相对于textView1的,所以textView2直接插入了textView3中。在设计视图里看起来更明显(白色背景的那个)。
View只能设置一个View作为锚点,设置了一个就顾不了另一个,所以传统方法是使用TableLayout,或者把 textView1 & textView2 包裹在一个垂直的LinearLayout中,然后让textView3约束在这个LinearLayout的后面。
所以就诞生了Barrier,他可以设置N个View作为锚点。
Barrier 是一个虚拟视图,类似于 Guideline,用来约束对象。Barrier 和 Guideline 的区别在于它是由多个 view 的大小决定的。在这个例子中,我们不知道 textView1 和 textView2 哪个长些,因此我们可以 基于这两个 view 的宽度创建一个Barrier。我们可以让 textView3 约束在 Barrier 后面。
在编辑器中创建Barriers
首先选择上下文菜单的create a vertical barrier,创建一个垂直的barrier:
注:Android Studio2.3貌似没有这个菜单,Android Studio3.0有,但是在help菜单组的下级菜单,跟下面的演示图有些区别。
你可以在组建树(component tree)中看到Barrier(左边靠近底部的面板)。
我们可以通过拖动改变它的位置(可选项):
接下来我们需要设置Barrier 方向(direction)。这里我们是想让Barrier根据textView1 和 textView2 的大小确定是在谁的后面,因此我们需要把 direction 设置为 end:
最后一步是告诉Barrier它是相对于哪些view。我不用约束来形容是因为约束一般指一对一的,而这里是多个view。我们需要为 Barrier 指定引用的view的 ID , 可以通过在 component tree 中把view拖动到 Barrier 来完成:
一旦定义好之后,这些引用将被列为 Barrier 的 children。而且你还会在蓝色面板中看到Barrier跳到了新的位置(垂直的虚线)。
现在 Barrier 就已经定义好了,只剩下把textView3的约束从相对于 textView1 改为 相对于 Barrier 了:
完了之后 textView3 就到了 textView2 的后面了。
为了看到整体的效果,可以切换语言,此时你会看到 Barrier 会自动位于较宽的那个 textView 后面,也就间接让 textView3 也位于了正确的位置:
在XML中创建Barriers
XML代码其实也非常简单:
<?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">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/warehouse"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@string/hospital"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="textView2,textView1" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/lorem_ipsum"
app:layout_constraintStart_toEndOf="@+id/barrier7"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码
barrierAllowsGoneWidgets
目前button3和button1 button2的top对齐,此时如果button1 Gone了呢?
如上图效果,button1 gone之后,会变为一个点,所以button3顶齐父布局也没问题。但有的时候这不符合我们的需求,我们希望Barrier不要关注Gone的View了,所以谷歌提供了属性barrierAllowsGoneWidgets
,设为false后,就不在关注Gone的View了,效果如上图,button1 gone之后,button3 不再和父布局顶齐,而是和button2顶齐。
Barrier特别的地方就在于Barrier元素自身。app:barrierDirection 属性决定 Barrier 的方向 - 这里把它放在被引用view的后面。被引用的view 是布局中的view的id列表,用逗号隔开。
借用一张图 来自medium.com/androiddeve…
Group
使用组,您可以将某些视图分组在一起。不要把这与Android中的普通ViewGroups混淆。ConstraintLayout中的一个组仅包含对视图ID的引用,而不将组合中的视图嵌套。这样一来,您可以设置组中控件的可见性仅通过设置组的可见性就行了,而无需设置每个视图的可见性。这对于诸如错误屏幕或加载屏幕的事情是有用的,其中一些元素需要一次更改其可见性
其可使用到的属性为:
- constraint_referenced_ids:指定所引用控件的 id。
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="title, desc" />
复制代码
如果有多个 Group,是可以同时指定相同的控件的,最终是以 XML 中最后声明的 Group 为准。
Placeholder
Placeholder顾名思义,就是用来一个占位的东西,它可以通过 setContentId() 方法将占位符变为有效的视图。如果视图已经存在于屏幕上,那么视图将会从原有位置消失。
除此之外,还可以通过 setEmptyVisibility() 方法设置当视图不存在时占位符的可见性。
<?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:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.Placeholder
android:id="@+id/placeholder"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/mail" />
<ImageButton
android:id="@+id/favorite"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="#00000000"
android:scaleType="centerInside"
android:src="@drawable/favorite"
android:tint="#E64A19"
app:layout_constraintEnd_toStartOf="@id/mail"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/mail"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="#00000000"
android:scaleType="centerInside"
android:src="@drawable/checked"
android:tint="#512DA8"
app:layout_constraintEnd_toStartOf="@id/save"
app:layout_constraintStart_toEndOf="@id/favorite"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/save"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="#00000000"
android:scaleType="centerInside"
android:src="@drawable/star"
android:tint="#D32F2F"
app:layout_constraintEnd_toStartOf="@id/play"
app:layout_constraintStart_toEndOf="@id/mail"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/play"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="#00000000"
android:scaleType="centerInside"
android:src="@drawable/delete"
android:tint="#FFA000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码
class DemoFragment : Fragment(), View.OnClickListener {
override fun onClick(v: View) {
//call TransitionManager so and pass ConstraintLayout to perform smooth animation
TransitionManager.beginDelayedTransition(mConstraintLayout);
//finally set clicked view at placeholder
mPlaceholder.setContentId(v.id)
}
private lateinit var dashboardViewModel: DashboardViewModel
private lateinit var mConstraintLayout: ConstraintLayout
private lateinit var mPlaceholder: Placeholder
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
dashboardViewModel =
ViewModelProviders.of(this).get(DashboardViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_dashboard, container, false)
val favorite: ImageButton = root.findViewById(R.id.favorite)
favorite.setOnClickListener(this)
val star : ImageButton = root.findViewById(R.id.star)
star.setOnClickListener(this)
val checked : ImageButton = root.findViewById(R.id.checked)
checked.setOnClickListener(this)
val delete : ImageButton = root.findViewById(R.id.delete)
delete.setOnClickListener(this)
mConstraintLayout = root.findViewById(R.id.constraintLayout)
mPlaceholder = root.findViewById(R.id.placeholder)
return root
}
}
复制代码
Optimizer (优化器)
需要知道的是,当我们使用 MATCH_CONSTRAINT 时,ConstraintLayout 将不得不对控件进行 2 次测量,而测量的操作是昂贵的。
而优化器(Optimizer)的作用就是对 ConstraintLayout 进行优化,对应设置给 ConstraintLauyout 的属性是:
- layout_optimizationLevel。
可设置的值有:
- none:不应用优化。
- standard:仅优化直接约束和屏障约束(默认的)。
- direct:优化直接约束。
- barrier:优化屏障约束。
- chain:优化链约束(实验)。
- dimensions:优化尺寸测量(实验)。
在设置值时,可以设置多个,如:
app:layout_optimizationLevel="direct|barrier|dimensions"
复制代码
可视化编辑器
这部分可以参考以下文章
[译文]使用ConstraintLayout构建一个响应式的UI
Android新特性介绍,ConstraintLayout完全解析
demo
动画
高级
developer.android.com/reference/a…
developer.android.com/reference/a…
Barriers clone() in ConstraintSet 1.1.3
约束集与动画
您可以将 ConstraintLayout
随同 ConstraintSet
(约束集)一起使用来一次实现多个元素的动画效果。
一个 ConstraintSet
仅持有一个 ConstraintLayout
的约束。你可以在代码中创建一个ConstraintSet
,或者从一个布局文件中加载它。然后,您可以将 ConstraintSet
应用于 ConstraintLayout
,更新所有约束以匹配 ConstraintSet
中的约束。
要使其具有动画效果,请使用 support library 中的 TransitionManager.beginDelayedTransition()
方法。此功能将使您的 ConstraintSet
中的所有布局的更新都通过动画来呈现。
这是一个更深入地涵盖了这个话题的视频:
-
YouTube 视频链接:youtu.be/OHcfs6rStRo
motionlaytout
性能分析
android.jlelse.eu/constraint-…
参考
细细品读!深入浅出,官方文档看ConstraintLayout
Android 约束布局(ConstraintLayout)1.1.0 版详解
Android 约束布局(ConstraintLayout)详解
ConstraintLayout 之 Guideline、Barrier、Chains和Groups
约束布局(ConstraintLayout)1.1.2 版本的新特性