MotionLayout动画

本文详细介绍如何使用ConstraintLayout 2.0中的MotionLayout实现复杂的动画效果,包括定义ImageView的位置变化、关键帧设置及路径动画,最终实现一个类似Google I/O演示的动画案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在今年的Google I/O 上 Google 开发者展示了这么一段视频 What’s New in ConstraintLayout (Google I/O’19) (视频在youtube上,需要翻墙),截了其中一段做了个gif图在这里插入图片描述
图中动画使用ConstraintLayout 2.0里的MotionLayout完成。
ConstraintLayout 2.0现在还是beta版,不过已经可以实行图中的动画了。
今天可以做一个简易版的动画

本文后面的内容需要MotionLayout的相关知识
在此推荐

本项目环境配置:
Android Studio 3.4.1
ConstraintLayout 2.0.0-beta1

新建项目,引入ConstraintLayout 库

implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta1'

布局根节点使用MotionLayout替换ConstraintLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout 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/activity_motion"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="20dp"
    android:layout_marginBottom="20dp"
    app:layoutDescription="@xml/motion_scene"
    app:showPaths="true"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/main_image1"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@color/color1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="ContentDescription" />

</android.support.constraint.motion.MotionLayout>

xml里引入motion_scene文件

<?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:constraintSetEnd="@id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="4000"
        motion:motionInterpolator="easeInOut">
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/main_image1" />
            
			......
			......
     
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_image1"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:drawPath="path"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_image1"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent" />

    </ConstraintSet>

</MotionScene>

我们先从最简单的入手,定义一个ImageView,两个ConstraintSet 分别定义开始位置和结束位置,然后在Transition处引入
motion:duration 定义动画时长
motion:motionInterpolator定义缓动动画 (这里推荐一个网站 缓动函数速查表

<OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/main_image1" />

这段代码 表示监听main_image1的点击事件,并在开始位置和结束位置之间切换状态

在这里插入图片描述
为了使图片旋转一周,我们需要定义5个关键点
在这里插入图片描述
先添加第一个关键点

<KeyFrameSet>

            <KeyPosition
                motion:framePosition="20"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="flip"
                motion:percentX="0.25"
                motion:percentY="0.65" />
           
        </KeyFrameSet>

motion:motionTarget 表示关键点应用的控件
motion:framePosition 表示这个点处于动画进行到20%时
motion:keyPositionType 表示使用的坐标系,有三种选择

  • parentRelative
  • deltaRelative
  • pathRelative

motion:percentX motion:percentX 关键点的(x,y)坐标

三种坐标系的区别
parentRelative
这个我使用后发现与引用的博客里不一样,这个是与Android坐标系一致,以父容器左上角为坐标原点,向右为x轴正方向,向下为y轴正方向。
deltaRelative
以控件开始位置和结束位置定义坐标系,开始位置为坐标原点,水平方向为X轴,垂直方向为Y轴。
在这里插入图片描述
pathRelative
以起始位置为坐标原点,起始位置到结束位置的path为X轴,垂直方向为Y轴。
在这里插入图片描述

motion:pathMotionArc 表示两点之间的path形状,默认是none(线性) ,flip表示翻转当前弧形方向

  • startHorizontal
    startHorizontal

  • startVertical
    在这里插入图片描述

第一个关键点定义完成后运行如图
在这里插入图片描述
如此这样,定义完其他的关键点

<KeyFrameSet>

            <KeyPosition
                motion:framePosition="20"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="flip"
                motion:percentX="0.25"
                motion:percentY="0.65" />
            <KeyPosition
                motion:framePosition="36"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="flip"
                motion:percentX="0"
                motion:percentY="0.5" />
            <KeyPosition
                motion:framePosition="52"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="startHorizontal"
                motion:percentX="0.5"
                motion:percentY="0.25" />
            <KeyPosition
                motion:framePosition="68"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="startVertical"
                motion:percentX="1"
                motion:percentY="0.5" />
            <KeyPosition
                motion:framePosition="84"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="none"
                motion:percentX="0.5"
                motion:percentY="0.75" />

        </KeyFrameSet>

如此这样,定义完其他的关键点

<KeyFrameSet>

            <KeyPosition
                motion:framePosition="20"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="flip"
                motion:percentX="0.25"
                motion:percentY="0.65" />
            <KeyPosition
                motion:framePosition="36"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="flip"
                motion:percentX="0"
                motion:percentY="0.5" />
            <KeyPosition
                motion:framePosition="52"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="startHorizontal"
                motion:percentX="0.5"
                motion:percentY="0.25" />
            <KeyPosition
                motion:framePosition="68"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="startVertical"
                motion:percentX="1"
                motion:percentY="0.5" />
            <KeyPosition
                motion:framePosition="84"
                motion:keyPositionType="parentRelative"
                motion:motionTarget="@id/main_image1"
                motion:pathMotionArc="none"
                motion:percentX="0.5"
                motion:percentY="0.75" />

        </KeyFrameSet>

运行效果如下:
在这里插入图片描述
添加其他的ImageView,更改最后的显示位置,将ImageView1的关键点复制3份,分别设成其他的ImageView的关键点

<ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/main_image1"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:drawPath="path"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />

        <Constraint
            android:id="@+id/main_image2"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:drawPath="path"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />
        <Constraint
            android:id="@+id/main_image3"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:drawPath="path"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />
        <Constraint
            android:id="@+id/main_image4"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:drawPath="path"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/main_image1"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toLeftOf="@id/main_image2"
            motion:layout_constraintVertical_chainStyle="spread_inside" />
        <Constraint
            android:id="@+id/main_image2"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toRightOf="@id/main_image1"
            motion:layout_constraintRight_toLeftOf="@id/main_image3" />
        <Constraint
            android:id="@+id/main_image3"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toRightOf="@id/main_image2"
            motion:layout_constraintRight_toLeftOf="@id/main_image4" />
        <Constraint
            android:id="@+id/main_image4"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toRightOf="@id/main_image3"
            motion:layout_constraintRight_toRightOf="parent" />

    </ConstraintSet>

效果如下:
在这里插入图片描述
可以看到效果和演示视频差不多了,接下来使4个ImageView依次运动

更改每个ImageView的每个motion:framePosition,使各个关键点产生距离,最终效果如下

在这里插入图片描述
代码已上传github github地址

参考文章:

### 如何在 Android 中使用 MotionLayout 创建动画 #### 定义布局文件中的 `MotionLayout` `MotionLayout` 是一种特殊的布局管理器,继承自 `ConstraintLayout`。这意味着可以在定义视图位置和大小的同时指定这些属性随时间变化的方式。 ```xml <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="wrap_content" app:layoutDescription="@xml/scene_file"> <!-- 子View --> </androidx.constraintlayout.motion.widget.MotionLayout> ``` 上述代码展示了如何声明一个 `MotionLayout` 控件并关联到场景描述文件[^1]。 #### 场景描述文件 (`@xml/scene_file`) 的结构 为了使 `MotionLayout` 能够工作,需要创建 XML 文件来描述不同状态下的约束条件以及两者之间的转换方式: ```xml <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"/> <ConstraintSet android:id="@+id/start"> <!-- 初始状态下各组件的位置和其他属性设置 --> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <!-- 结束状态下各组件的新位置和其他属性设置 --> </ConstraintSet> </MotionScene> ``` 此配置指定了两个不同的约束集——起始(`start`) 和结束(`end`) ,并通过 `<Transition>` 标签连接起来形成完整的运动路径[^2]。 #### 动态控制动画播放 除了静态设定外,还可以编程地触发或调整动画过程。例如,在 Java/Kotlin 代码里调用相应的方法启动特定的过渡动作或者改变当前进度值: ```java // 获取 MotionLayout 实例 MotionLayout motionLayout = findViewById(R.id.motion_layout); // 开始向目标状态转变 motionLayout.transitionToEnd(); // 或者手动设置中间某个百分比的状态 float progressValue = 0.5f; // 表示一半完成度 motionLayout.setProgress(progressValue); ``` 以上方法允许开发者灵活操控由 `MotionLayout` 所驱动的各种交互行为[^4]。 #### 添加关键帧 (Optional) 如果希望更精细地定制动画流程,则可以通过添加额外的关键帧节点进一步增强表现力。这使得某些时刻内的视觉特性得以精确调控而不必依赖线性的插值算法。 ```xml <KeyFrameSet> <KeyAttribute app:framePosition="50" app:motionTarget="@id/my_view"> <CustomAttribute app:attributeName="alpha" app:value="0.5"/> </KeyAttribute> </KeyFrameSet> ``` 这里展示了一个简单的例子,其中当达到整个动画周期的一半时会将某对象透明度设为半透明白色[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值