ConstraintLayout核心用法详解(四):ConstraintSet、MotionLayout与Flow

整理旧电脑上的一些笔记

前言

  • ConstraintLayout 核心用法详解(一):相对定位、边距、偏移与环形定位
  • ConstraintLayout核心用法详解(二):尺寸约束与链约束
  • ConstraintLayout核心用法详解(三):引导线、屏障、组和占位

今天继续整理 ConstraintLayout

ConstraintLayout 的特点:

  • 扁平布局,无嵌套,一个层级就可以绘制复杂布局;
  • 渲染性能高;
  • 集合了线性布局、相对布局、百分比布局的特点和大多数功能;
  • 支持可视化环境下拖拽绘制约束布局;
  • 轻松地为应用中使用的 UI 组件添加动画效果。

1.ConstraintSet

在之前对 ConstraintLayout 的介绍中,我们都是在XML中使用 ConstraintLayout,但某些时候我们需要动态的创建布局,LinearLayoutRelativeLayout 等布局中我们会采用 LayoutParams 的方式来动态创建。但在 ConstraintLayout 布局中这种时候就需要使用ConstraintSet

ConstraintSetConstraintLayout 定义了一系列的约束。我们可以使用 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)

效果截图:

image-20250910211308329

2.MotionLayout

2018年的 Google I/O 大会上 ConstraintLayout 2.0首次公开亮相,2.0官方提供了许多新特性,MotionLayout 就是其中的核心之一。

既然已经有了强大的 ConstraintLayout,Android为何又要引入 MotionLayout 布局?MotionLayout 被用来解决什么问题?MotionLayout 不是为了取代任何布局,它是为了补充 Android 开发过程中一个长期被诟病的问题:创建复杂且流畅的动画和视图移动

在过去的 Android 复杂动画实现方式中主要是 ValueAnimatorObjectAnimator 这类属性动画,想要实现复杂动画需要写很多代码,而且动画代码逻辑分散,不利于维护。想要实现动画手势交互更是需要手动计算位移和速度,十分繁琐易出错。而现在移动端一个明显的趋势就是更加注重“用户体验”,在功能趋同的时代,良好用户体验成为了关键的差异化因素。高性能的硬件支撑、用户追求的直觉式和沉浸式、iOS的珠玉在前.....一切的一切都迫使 Android 必须提供能简单方便写出复杂动画的控件给开发者使用。MotionLayout 也就由此而来了。

MotionLayout继承自ConstraintLayout,它提供管理运动和控件动画的特性,可以用于实现复杂、可交互的动画。

MotionLayout 的知识点很多,下面我们先简单来入门使用下 MotionLayout

  1. 添加依赖

//groovy
implementation "androidx.constraintlayout:constraintlayout:2.2.1"

//Kotlin
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
  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

  1. 创建scene_01.xml

    可以直接在 activity_main.xml 的根布局 MotionLayout 里面直接写 app:layoutDescription="@xml/scene_01",然后借助AS编辑器的代码提示来直接创建 XML 资源文件。

    也可以点击 app->res(右键)->New->Android Resource Directory 在弹出的对话框中 Resource type下拉选择 xml。

屏幕截图 2025-09-11 222537

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:touchAnchorId:定义用户可以滑动和拖动的视图
    • motion:touchAnchorSide:表示视图正在从右侧拖动
    • motion:dragDirection:表示拖动的进度方向
  • 定义运动的各种限制条件的位置。如上面示例中开始和结束都使用了motion:layout_constraintBottom_toBottomOf="parent"和motion:layout_constraintTop_toTopOf="parent" 实现垂直居中。
  1. 运行效果

kalvin_tool_20250911223035

由于篇幅限制,本篇不会再介绍更多 MotionLayout 的内容,有兴趣的可以去官网示例中先学习,后续也会继续发布一些个人对MotionLayout 使用内容。

3.Flow

同样是 ConstraintLayout 2.0,官方提供了另一个重要新特性:Flow(Virtual Layout)

FlowGuidelineGroup 一样都是作为虚拟布局助手来使用的,它可以高效、灵活的实现链式排列(如 LinearLayoutGridLayout),能够自动换行和根据约束条件动态调整 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>

效果图:

1757609691351

4. 参考资料

posted @ 2026-09-05 14:11  换元不配限  阅读(4)  评论(0)    收藏  举报