MotionLayout

MotionLayout的介绍:

  1. 是什么?

    MotionLayout是一种布局类型,能够帮我们实现复杂的动画效果,还能与人进行交互;

    简单来说,我们可以在布局中定义多个状态,Motionlayout帮助我们在这些状态中平滑的过渡,从而实现复杂的动画效果;

2. MotionLayoutConstraintLayout的子类,所以具有ConstraintLayout类同样强大的功能,所以我们只需要添加ConstraintLayout 的依赖,就可以使用 MotionLayout 了;

  1. 我们可以通过XML文件中声明性的设置,就可以完成动画效果了,当然我们也可以通过Java代码设置,拿到Motionlayout的实例,使用一些方法进行设置,后文会提到;

  2. MotionLayout 只能为它直接子级设置动画;

实现

添加依赖

implementation "androidx.constraintlayout:constraintlayout:2.2.0"

也可以去官网查看最新版

使用Motionlayout

方式一:

xml布局中component Tree > 点击第一个子项 > 点击Convert to MotionLayout;

这种方法系统会自动把ConstraintLayout替换成MotionLayout,并且生成MotionScene;MotionScene可以在你的res/xml目录下找到类似于nihao_scene.xml为名的文件;
在这里插入图片描述

方式二:

手动替换为MotionLayout,以及创建xml目录下MotionScene为根布局的文件;

MotionLayout文件如下:

<?xml version="1.0" encoding="utf-8"?>
  <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  app:layoutDescription="@xml/motionsence"
  android:background="@drawable/second">
  <ImageView
  android:id="@+id/page"
  android:layout_width="100dp"
  android:layout_height="100dp"
  android:background="@drawable/first">
  </ImageView>
    </androidx.constraintlayout.motion.widget.MotionLayout>

布局很简单,MotionLayout布局里面写了一个view;

MotionScene(运动性描述文件)

  1. 是什么?

    相应布局的所有运动描述;也就是说我们定义什么样的动画,就在这个文件里设置;

  2. 每个 MotionLayout 都引用一个单独的 MotionScene;那么怎么将二者联系起来呢?

app:layoutDescription="@xml/motionsence" :通过MotionLayout的 app:layoutDescription联系起来;

  1. MotionScene 中的定义的运动描述优先于 MotionLayout 中的任何相同定义;比如定义宽高,最终显示出来的效果是MotionScene 中定义的宽高;

    MotionLayout其他属性:

    • showPaths:布尔类型,表示在运动进行时是否显示运动路径。默认值为 false;
    • currentState:可指定具体的 ConstraintSet,告诉 MotionLayout 从哪个状态开始滑动;

其实重要的还是怎么使用动画达到我们想要的效果,所以我们来详细看看MotionScene

一个运动性描述文件框架上主要有Transition,ConstraintSet,Constraint,OnClick,OnSwipe;

简单分析完成一段动画需要什么,我们得确定动画的刚开始的状态,结束时的状态,怎么触发这个动画,一个布局里面有很多个view,如何正确的把刚开始的状态以及结束状态应用到理想的view上;那么我们围绕以上内容进行学习;

ConstraintSet
  1. 介绍: <ConstraintSet> 元素是用来指定视图在动画序列中某一点上的位置和属性(可同时指定多个视图),也叫约束条件集合;

通俗来讲:这就是决定动画某一时刻(比如起始)的状态,我们可以在这里设置很多元素的起始状态,所以这个就要做约束条件集合;

  1. 代码模式:
<ConstraintSet android:id = "@+id/end">
  <Constraint
  ...
  </Constraint>
    <Constraint
    ...
    </Constraint>
      </ConstraintSet>
  1. 属性:

    1. android:id = "@+id/end" 指定这个约束集的名字;
    2. deriveConstraintsFrom:值为另一个 ConstraintSet 的 ID;如果指定这个属性,ID 对应约束集合内的所有约束条件都将应用于此集合,除非此集明确替换它们;
  2. 里面必须包含一个或者多个Constraint元素;

Constraint
  1. 介绍:<Constraint> 元素用来声明运动序列其中一个视图的位置和属性,也就是视图的约束;

通俗来讲:规定具体的view的位置以及属性,为它设置状态;

  1. 代码:
<Constraint
android:id = "@+id/TODO"
android:layout_height="64dp"
android:layout_width="64dp"
motion:layout_constraintRight_toRightOf="parent"
motion:layout_constraintTop_toTopOf="parent"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp">
<CustomAttribute
motion:attributeName="background"
motion:customColorValue="#CCA4E3">
</CustomAttribute>
  </Constraint>
  1. 属性:

    1. android:layout_height="64dp"这里设置宽高的优先级高于view自身;

    这里有一个疑问?为什么在主布局中定义了view的宽高,为什么这里还要再定义一遍?

    答案:主布局设置的是初始值,而这里设置的是在这个动画中这个状态下的view的大小;

    1. android:id = "@+id/TODO"这里的ID不是为了起名字,而是为了指定作用于哪个view;
CustomAttribute
  1. 介绍:对view变化过程中进行插值,是运行时属性,比如背景颜色,文字颜色,圆角半径等;

  2. 代码:

    <CustomAttribute
    motion:attributeName="backgroundColor"
    motion:customColorValue="#D8C7D5">
    </CustomAttribute>
  3. 属性:

    1. motion:attributeName你要变化的属性名
    2. motion:customColorValue变化的数值

    常见的属性表如下:

    属性类型对应的字段名示例说明
    颜色customColorValue#FF4081可对颜色进行渐变(自动补间 RGB)
    浮点数customFloatValue0.5常用于透明度、圆角半径、进度等
    整数customIntegerValue100如宽高、边距、进度值等
    布尔customBooleanValuetrue通常配合状态切换使用
    字符串customStringValue“Hello”很少用于动画,但可以保存状态
    维度customDimension16dp常用于阴影半径、圆角、边距等
  4. 如果你要定义,那么这个view的全程都应该写CustomAttribute

Transition
  1. 介绍: 该元素是用来声明运动过程中的开始和结束状态,包括所有预期的过度状态、用户的触发的交互等。

    通俗来讲:我们可能会定义很多个状态,那么它就是将开始的状态和结束的状态连接到一起作为一个动画,就是Transition 的作用

  2. 代码:

<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/middle"
>
<OnClick
motion:targetId="@+id/TODO"
motion:clickAction="transitionToEnd">
</OnClick>
  </Transition>
    <Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@+id/middle"
    motion:duration="500">
    <OnClick
    motion:targetId="@+id/TODO"
    motion:clickAction="transitionToEnd">
    </OnClick>
      </Transition>
  1. 属性:

    1. motion:constraintSetStart="@+id/start" 确实运动过程中的起始状态;
    2. motion:constraintSetEnd="@+id/middle" 同理结束状态;
    3. motion:duration="500" 动画持续的时间,如果没写,默认父元素中设置的时间,所以我们父元素也就是MotionScene中可以设置,属性是motion:defaultDuration="1000"
  2. Transition 可以包含的元素:OnClick,OnSwipe,KeyFrameSet

OnClick
  1. 介绍: 该元素用于指定当用户点按特定视图时要执行的操作,指定当用户点击视图时触发动画序列;

通俗来讲:就是点击触发动画;

  1. 代码
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/middle"
motion:duration="500">
<OnClick
motion:targetId="@+id/TODO"
motion:clickAction="transitionToEnd">
</OnClick>
  </Transition>
  1. 属性:

    1. motion:targetId="@+id/TODO" 你要滑动触发的view的对象;
    2. motion:clickAction="transitionToEnd" 代表点击触发的操作;

    clickAction属性值附表

    属性值含义行为描述典型使用场景
    transitionToEnd切换到 end 状态从当前状态(通常是 start)平滑过渡到 end点击按钮触发开启动画,例如菜单展开、视图滑出
    transitionToStart切换到 start 状态从当前状态(通常是 end)回到start点击关闭按钮,恢复初始位置
    toggle自动在 start / end 之间切换如果当前在start 就去 end,反之返回 start单个按钮控制展开 / 收起
    jumpToEnd立即跳转到 end 状态不进行动画过渡,直接到结束布局测试时或某些需要立即切换的场景
    jumpToStart立即跳转到 start 状态不进行动画过渡,直接回到起始布局重置布局
    none无操作点击无任何动画或状态变化占位、禁用点击行为

​ 其实jumpToStart和transitionToStart是区别就是有无动画,下面是一个手动start和end之间切换的实例:

<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/middle"
motion:duration="500">
<OnClick
motion:targetId="@+id/TODO"
motion:clickAction="transitionToEnd">
</OnClick>
  </Transition>
    <Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@+id/middle"
    motion:duration="500">
    <OnClick
    motion:targetId="@+id/TODO"
    motion:clickAction="transitionToStart">
    </OnClick>
      </Transition>

我们可以看到两个transition定义的起始和结束状态都是一样的,那么设立了两个点击触发,就可以达到点击到结束状态,再点击回到初始状态;

  1. 为什么不写在一个Transition里面?只能写一个,写多了编译器报错;

实现的效果如下:

OnSwipe
  1. 介绍: 该元素用于指定当用户在不居中滑动时需要执行的操作;

通俗来讲:就是滑动触发动画;

  1. 代码
<OnSwipe
motion:touchAnchorId="@id/page"
motion:touchRegionId="@id/page"
motion:dragDirection="dragLeft"
motion:onTouchUp="autoComplete"
>
</OnSwipe>
  1. 属性:

    1. motion:touchAnchorId="@id/page" 你的目标view的ID;
    2. motion:touchRegionId="@id/page" 你的锚点区域;
    3. motion:dragDirection="dragLeft" 你朝哪个方向滑动触发
    4. motion:onTouchUp="autoComplete" 触摸结束之后的动画

实现的效果如下:

  1. 其他属性:

    motion:dragScale:控制目标视图的滑动距离和用户手指滑动距离的相对比例,默认值是1。通俗来讲,取值小于1时view比手滑动的慢,取值大于1时view比手快;

motion:maxVelocity:目标view的最大速度;

motion:maxAcceleration:目标view的最大加速度;

  1. motion:onTouchUp属性其他可选值:

stop(停止动画)、autoComplete(自动完成动画)、autoCompleteToEnd(自动完成到结束状态)、autoCompleteToStart(自动完成到开始状态)、decelerate(减速停止动画)、decelerateAndComplete(减速并完成动画);

KeyFrameSet
  1. 介绍: <KeyFrameSet> 就是提供声明更加复杂动画的元素,他声明了动画运动轨迹的关键点集合,平滑的连接这些关键点,可以得到动画;

通俗来讲:它是一个定义关键帧的容器,通过关键帧,可以精确定义动画的轨迹和行为;

  1. 代码
<KeyFrameSet>
  <KeyPosition>
    </KeyPosition>
      <KeyAttribute>
        </KeyAttribute>
          </KeyFrameSet>

它有很多关键帧类型,每种类型有不同的属性,触发不同的事件,这个关键帧通过动画的时间轴(frameposition)来定位;

关键帧`
KeyPosition
  1. 介绍:是一个关键帧类型,用于定义视图在动画路径上的位置和大小变化;

  2. 代码:

    <KeyPosition
    motion:framePosition="40"
    motion:keyPositionType="parentRelative"
    motion:motionTarget="@+id/TODO"
    motion:percentX="0.45"
    motion:percentY="0.75"
    >
    </KeyPosition>
  3. 属性:

    1. motion:framePosition="40" 定义了动画帧的位置,值为0-100,代表在动画过程中的百分比位置;
    2. motion:motionTarget="@+id/TODO" 目标view
    3. motion:percentX="0.45"取值在-1 - 1之间,坐标轴取决于:keyPositionType
    4. motion:keyPositionType="parentRelative" 计算关键帧的位置,有三种计算方式:
    三种计算方式:

    第一种parentRelative:相当于父容器来计算关键帧,左上角为(0,0);
    ``
    在这里插入图片描述

    第二种:deltaRelative: 基于相对于起始点和结束点的相对偏移量来计算路径,起始坐标和终点坐标分别为(0,0)和(1,1)

    在这里插入图片描述

    第三种:以路径为x轴,以垂直方向为Y轴

在这里插入图片描述

KeyAttribute
  1. 介绍: <KeyAttribute> 元素是用来在运动序列的特定时刻,设置视图的任何标准属性;

    通俗来讲:KeyPosition是规定view的位置和大小,KeyAttribute是规定view的UI元素属性的关键帧类型;可以在动画的某些时刻对元素的多个属性进行变换,例如透明度、旋转、缩放、平移等,从而实现复杂的动画效果;

  2. 代码:

<KeyAttribute
motion:motionTarget="@+id/TODO"
motion:framePosition="50"
android:scaleX="1.5"
android:scaleY="1.5"
android:rotation="359"
>
</KeyAttribute>
  1. 属性:

    1. motion:motionTarget="@+id/TODO" 目标view
    2. motion:framePosition="50" 关键帧在动画进度中的位置
    3. android:scaleX="1.5" 表示将view的weight扩大到1.5倍
    4. android:scaleY="1.5" 同理
    5. android:rotation="359" 将view旋转359度
  2. 其他属性:

    1. android:alpha 设置透明度

    2. android:translationXandroid:translationY:控制视图的 X、Y 方向偏移;

    3. android:transformPivotXandroid:transformPivotY:控制旋转和缩放的中心点;

    4. transformPivotTarget: 指定其他视图作为旋转和缩放的中心点;

    5. motion:transitionEasing="easeInOut" 设置速度

      取值:加速(ease-in)减速(ease-out)先快后慢(easeInOut

有这样的效果:

在这里插入图片描述

代码如下:

<?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"
  motion:duration="2000">
  <Transition
  motion:constraintSetStart="@+id/start"
  motion:constraintSetEnd="@+id/middle"
  >
  <OnClick
  motion:targetId="@+id/TODO"
  motion:clickAction="transitionToEnd">
  </OnClick>
    <KeyFrameSet>
      <KeyPosition
      motion:framePosition="40"
      motion:keyPositionType="parentRelative"
      motion:motionTarget="@+id/TODO"
      motion:percentX="0.45"
      motion:percentY="0.75"
      >
      </KeyPosition>
        <KeyPosition
        motion:framePosition="50"
        motion:keyPositionType="parentRelative"
        motion:motionTarget="@+id/TODO"
        motion:percentX="0.5"
        motion:percentY="0.5"
        >
        </KeyPosition>
          <KeyPosition
          motion:framePosition="60"
          motion:keyPositionType="parentRelative"
          motion:motionTarget="@+id/TODO"
          motion:percentX="0.65"
          motion:percentY="0.45"
          >
          </KeyPosition>
            <KeyAttribute
            motion:motionTarget="@+id/TODO"
            motion:framePosition="50"
            android:scaleX="1.5"
            android:scaleY="1.5"
            android:rotation="359"
            >
            </KeyAttribute>
              <KeyAttribute
              motion:motionTarget="@+id/TODO"
              motion:framePosition="100"
              android:scaleX="1"
              android:scaleY="1"
              android:rotation="360"
              >
              </KeyAttribute>
                </KeyFrameSet>
                  </Transition>
                    <ConstraintSet android:id = "@+id/start">
                      <Constraint
                      android:id = "@+id/TODO"
                      android:layout_height="100dp"
                      android:layout_width="100dp"
                      motion:layout_constraintLeft_toLeftOf="parent"
                      motion:layout_constraintBottom_toBottomOf="parent"
                      android:layout_marginLeft="10dp"
                      android:layout_marginBottom="10dp">
                      </Constraint>
                        </ConstraintSet>
                          <ConstraintSet android:id = "@+id/middle">
                            <Constraint
                            android:id = "@+id/TODO"
                            android:layout_height="100dp"
                            android:layout_width="100dp"
                            motion:layout_constraintRight_toRightOf="parent"
                            motion:layout_constraintBottom_toBottomOf="parent"
                            android:layout_marginRight="10dp"
                            android:layout_marginBottom="10dp">
                            </Constraint>
                              </ConstraintSet>
                                </MotionScene

动态设置

我们也可以在代码中动态设置

方法功能
transitionToEnd()触发当前 Transition 到 end 状态
transitionToStart()触发当前 Transition 到 start 状态
setProgress(float progress)直接设置动画进度,0~1
getProgress()获取当前 progress
setTransition(int startId, int endId)动态切换当前 Transition

示例:

MotionLayout motionLayout = findViewById(R.id.motionLayout);
// 手动设置动画进度到一半
motionLayout.setProgress(0.5f);
// 动态切换 Transition
motionLayout.setTransition(R.id.start, R.id.end);
// 播放动画到结束
motionLayout.transitionToEnd();

好啦,本次介绍到此结束,谢谢大家;VV