ConstraintLayout核心用法详解(四):ConstraintSet、MotionLayout与Flow
整理旧电脑上的一些笔记
前言
- ConstraintLayout 核心用法详解(一):相对定位、边距、偏移与环形定位
- ConstraintLayout核心用法详解(二):尺寸约束与链约束
- ConstraintLayout核心用法详解(三):引导线、屏障、组和占位
今天继续整理 ConstraintLayout。
ConstraintLayout 的特点:
- 扁平布局,无嵌套,一个层级就可以绘制复杂布局;
- 渲染性能高;
- 集合了线性布局、相对布局、百分比布局的特点和大多数功能;
- 支持可视化环境下拖拽绘制约束布局;
- 轻松地为应用中使用的 UI 组件添加动画效果。
1.ConstraintSet
在之前对 ConstraintLayout 的介绍中,我们都是在XML中使用 ConstraintLayout,但某些时候我们需要动态的创建布局,LinearLayout 或 RelativeLayout 等布局中我们会采用 LayoutParams 的方式来动态创建。但在 ConstraintLayout 布局中这种时候就需要使用ConstraintSet。
ConstraintSet 为 ConstraintLayout 定义了一系列的约束。我们可以使用 ConstraintSet 来创建并保存约束条件,然后将它应用到已经存在的 ConstraintLayout 上。
ConstraintSet 支持3种方式创建:
- 手动创建:c = new ConstraintSet(); c.connect(...);
- 从 R.layout.xxx 对象创建:c.clone(context,R.layout.xxx);
- 从一个 ConstraintLayout 创建:c.clone(mConstraintLayout)。
ConstraintSet 关键方法介绍:
- clone(layout):复制已有的 ConstraintLayout 约束信息;
- connect(startId,startSide,endId,endSide):在两个控件间建立约束(包括 PARENT);
- constrainWidth(viewId,width):设置View的宽度(支持 dimension、MATCH_CONSTRAINT、WRAP_CONTENT)
- constrainHeight(viewId,height):设置View的高度(支持 dimension、MATCH_CONSTRAINT、WRAP_CONTENT)
- applyTo(constraintLayout):将约束应用到 ConstraintLayout。
代码示例:
val layout = ConstraintLayout(this)
val tv1 = TextView(this)
tv1.text = "Hello"
tv1.id = View.generateViewId()
val tv2 = TextView(this)
tv2.text = "ConstraintSet"
tv2.id = View.generateViewId()
val constraintSet = ConstraintSet()
layout.addView(tv1)
layout.addView(tv2)
constraintSet.clone(layout)
//创建约束
constraintSet.connect(tv1.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
constraintSet.connect(tv2.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
constraintSet.connect(tv1.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
constraintSet.connect(tv2.id, ConstraintSet.START, tv1.id, ConstraintSet.END,20)
//tv1设置宽度和高度
constraintSet.constrainWidth(tv1.id, ConstraintSet.WRAP_CONTENT)
constraintSet.constrainHeight(tv1.id, ConstraintSet.WRAP_CONTENT)
//tv2设置宽度和高度
constraintSet.constrainWidth(tv2.id, ConstraintSet.WRAP_CONTENT)
constraintSet.constrainHeight(tv2.id, ConstraintSet.WRAP_CONTENT)
constraintSet.applyTo(layout)
setContentView(layout)
效果截图:

2.MotionLayout
2018年的 Google I/O 大会上 ConstraintLayout 2.0首次公开亮相,2.0官方提供了许多新特性,MotionLayout 就是其中的核心之一。
既然已经有了强大的 ConstraintLayout,Android为何又要引入 MotionLayout 布局?MotionLayout 被用来解决什么问题?MotionLayout 不是为了取代任何布局,它是为了补充 Android 开发过程中一个长期被诟病的问题:创建复杂且流畅的动画和视图移动。
在过去的 Android 复杂动画实现方式中主要是 ValueAnimator 或 ObjectAnimator 这类属性动画,想要实现复杂动画需要写很多代码,而且动画代码逻辑分散,不利于维护。想要实现动画手势交互更是需要手动计算位移和速度,十分繁琐易出错。而现在移动端一个明显的趋势就是更加注重“用户体验”,在功能趋同的时代,良好用户体验成为了关键的差异化因素。高性能的硬件支撑、用户追求的直觉式和沉浸式、iOS的珠玉在前.....一切的一切都迫使 Android 必须提供能简单方便写出复杂动画的控件给开发者使用。MotionLayout 也就由此而来了。
MotionLayout继承自ConstraintLayout,它提供管理运动和控件动画的特性,可以用于实现复杂、可交互的动画。
MotionLayout 的知识点很多,下面我们先简单来入门使用下 MotionLayout:
-
添加依赖
//groovy
implementation "androidx.constraintlayout:constraintlayout:2.2.1"
//Kotlin
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
-
将MotionLayout作为XML布局的根目录
MotionLayout 作为XML(如 activity_main.xml)的根布局,并定义宽高属性、ID和layoutDescription。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_01">
</androidx.constraintlayout.motion.widget.MotionLayout>
布局写好后,我们来了解 app:layoutDescription 属性干嘛的。
app:layoutDescription 属性引用了 MotionScene(运动场景文件),MotionScene会包含一个或多个 MotionLayout 都会单独引用一个 MotionScene。
-
创建scene_01.xml
可以直接在
activity_main.xml的根布局MotionLayout里面直接写 app:layoutDescription="@xml/scene_01",然后借助AS编辑器的代码提示来直接创建 XML 资源文件。也可以点击 app->res(右键)->New->Android Resource Directory 在弹出的对话框中 Resource type下拉选择 xml。

scene_01位置在app/res/xml目录下,下面是官网示例中的 scene_01.xml内容:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
</MotionScene>
上面的 MotionScene 示例描述了一个水平运动。
包含运动的基本定义 - motion:constraintSetStart:运动的开始端,值为下面
中定义的 - motion:constraintSetEnd:运动的结束端,值为下面
中定义的 - motion:duration:完成动作所需要的时间(毫秒)
- motion:constraintSetStart:运动的开始端,值为下面
为动作创建触控控件 - motion:touchAnchorId:定义用户可以滑动和拖动的视图
- motion:touchAnchorSide:表示视图正在从右侧拖动
- motion:dragDirection:表示拖动的进度方向
定义运动的各种限制条件的位置。如上面示例中开始和结束都使用了motion:layout_constraintBottom_toBottomOf="parent"和motion:layout_constraintTop_toTopOf="parent" 实现垂直居中。
-
运行效果

由于篇幅限制,本篇不会再介绍更多 MotionLayout 的内容,有兴趣的可以去官网示例中先学习,后续也会继续发布一些个人对MotionLayout 使用内容。
3.Flow
同样是 ConstraintLayout 2.0,官方提供了另一个重要新特性:Flow(Virtual Layout)。
Flow 同 Guideline、Group 一样都是作为虚拟布局助手来使用的,它可以高效、灵活的实现链式排列(如 LinearLayout、GridLayout),能够自动换行和根据约束条件动态调整 child view 位置。
先来介绍一下 Flow 相关属性:
- app:flow_wrapMode="none|chain|aligned":none-不换行(默认),chain-链式换行,aligned-对齐换行
- app:flow_horizontalGap="dimension":设置视图之间的水平间距
- app:flow_verticalGap="dimension":设置视图之间的垂直间距
- app:flow_horizontalStyle="spread|spread_inside|packed":定义链的样式
- app:flow_maxElementsWrap="":强制指定每行最多放置多少个元素
- app:constraint_referenced_ids="id1,id2,id3...":Flow管理和排列的viewId
代码示例:
<?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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.constraintlayout.FlowActivity">
<TextView
android:id="@+id/tv_sport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="体育"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_cookie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="美食"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="摄影"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_news"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="新闻"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_comic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="漫画"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_shows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="综艺"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_wallpaper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="壁纸"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_digital"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="数码"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_game"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="游戏"
app:drawableStartCompat="@drawable/ic_tags" />
<TextView
android:id="@+id/tv_pet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="宠物"
app:drawableStartCompat="@drawable/ic_tags" />
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_sport,tv_cookie,tv_camera,tv_news,tv_comic,tv_shows,tv_wallpaper,tv_digital,tv_game,tv_pet"
app:flow_horizontalGap="10dp"
app:flow_horizontalStyle="spread"
app:flow_verticalGap="20dp"
app:flow_wrapMode="chain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果图:

4. 参考资料
- Developer-ConstraintSet:https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ConstraintSet
- Developer-ConstraintLayout:https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ConstraintLayout
- ConstraintLayout历史版本:https://developer.android.google.cn/jetpack/androidx/releases/constraintlayout?hl=zh_cn
- MotionLayout:https://developer.android.google.cn/develop/ui/views/animations/motionlayout
- MotionLayot Example:https://developer.android.google.cn/develop/ui/views/animations/motionlayout/examples
- Flow:https://developer.android.google.cn/reference/androidx/constraintlayout/core/widgets/Flow?hl=en
浙公网安备 33010602011771号