抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

MotionLayout使用小结

简单描述/说明

谈MotionLayout之前得先了解一下过渡动画,工作中我其实很少去使用过渡动画,工作中目前用的最多的应该就是属性动画了,但是都知道属性动画的变化受其ViewGroup的影响,简言之:View的变化不会使ViewGroup及其子元素跟着变化,没有一种联动的效果。

过渡动画的使用很简单,就是在view的属性改动前,加上一句:

TransitionManager.beginDelayedTransition(viewGroup);
//改变view的layoutParams
...

注意这里是传入ViewGroup,TransitionManager会先记录当前VG的状态,这样在View属性改变时才会有一种过渡的感觉,即:View的变化会使ViewGroup及其子元素跟着变化,这样在你改变一个子view的时候,ViewGroup和其中的子view都会重新进行测量。

当然过渡动画也可以对一个viewGroup进行整体的过渡替换,使用的方法是:

ViewGroup viewGroup=findViewById(R.id.root);
Scene sceneStart = Scene.getSceneForLayout(viewGroup, R.layout.rootStart, this);
Scene sceneEnd = Scene.getSceneForLayout(viewGroup, R.layout.rootEnd, this);
//---- 切换到初始布局
TransitionManager.go(sceneStart);
OR
//---- 切换到结束布局
TransitionManager.go(sceneEnd);
...

但是有个点需要注意:数据、点击事件等都需要重新绑定。原因可以在go方法源码中找到,在go方法的方法体内有个changeScene函数,在该函数调用的scene.enter()中可以看到以下代码:

scene.enter()

然后看注释也就明白了。总结下过渡动画的大致流程:

  1. 两个场景,一个【开始场景】一个【结束场景】,记录场景上的空控件的各种参数
  2. 有了参数,创建出动画对象
  3. 播放动画

上面说到两种过渡动画的使用,都有缺点,第一种是只能给单个view进行设置,属性的更改需要在代码中进行。第二种是对ViewGroup设置后,因为控件被重新初始化从而需要重新绑定数据。那么有没有一个可以将这两个优点结合,缺点解决的办法呢?答案是有的,就是ConstraintSet()。

PS:关于过渡动画细节,网上很多地方都有写到,比如:https://juejin.im/post/5ae057e46fb9a07aa83e682

ConstraintSet,一听就知道是约束布局的辅助,所以这里还不需要用到MotionLayout,仅仅只需要ConstraintLayout即可,用法如下:

ConstraintLayout root = findViewById(R.id.root);
TransitionManager.beginDelayedTransition(root);
ConstraintSet constraintSet=new ConstraintSet();
//---- clone初始布局属性
constraintSet.clone(this,R.layout.constraint_rootStart);
OR
//---- clone结束布局属性
constraintSet.clone(this,R.layout.constraint_rootEnd);
//应用
constraintSet.applyTo(root);

这里是clone方法,意思是,将对应布局对应id的控件属性值进行重新赋值,你可以先在xml中写好界面,执行上面的代码就可以随意切换了,这里需要注意的是:与TransitionManager.go一样,xml布局中每个元素都需要有id,且两个布局中控件id 类型需要对应。区别是,TransitionManager.go会移除控件再添加,这个只是做布局参数属性赋值。

那,这样就完美了吗?答案肯定不是的,缺点如下:

  1. 不能控制进度,过程中无法暂停。
  2. 不支持触摸反馈,没办法设置高级的触摸反馈,只有点击什么的,因为改了布局属性,其他的对view的设置都会失效无法保存。
  3. 布局中存在重复部分,它们id一样,属性也有相同的地方。
  4. 增加了新的layout文件,增加了文件查找的复杂度,而这些文件实际只是布局片段。

MotionLayout

MotionLayout,一句话,很简单:MotionLayout 类继承自 ConstraintLayout 类,允许你为各种状态之间的布局设置过渡动画,意味着,你可以在xml中进行过渡动画的设置。

因为比较懒,就不弄图文+动效了,直接上配置文件,把关键点记录下。

用法:

  1. ConstraintLayout 改为 MotionLayout。
  2. 创建 MotionScene 文件并使⽤ app:layoutDescription进行关联,MotionScene文件放xml文件夹即可。

MotionLayout使用MotionScene文件进行相关动画配置,配置文件主要分三大部分,如下

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--用于过渡动画的详细配置-->
    <Transition
        app:constraintSetEnd="@id/end"
        app:constraintSetStart="@id/start"
        app:duration="1000">
        //...
    </Transition>

    <!--用于过渡动画的起始点状态参数配置-->
    <ConstraintSet android:id="@+id/start">
        //...
    </ConstraintSet>
    
    <!--用于过渡动画的结束点状态参数配置-->
    <ConstraintSet android:id="@+id/end">
      //...
    </ConstraintSet>
</MotionScene>

没看错,就这么点东西,本来用代码写的,可以全配置在这个MotionScene文件中,然后介绍下相关节点

Constraint节点

ConstraintSet中可以包含多个Constraint,每个Constraint对应一个view,可控制view的UI显示,约束,宽高等属性状态,自定义view也可支持,结构如下:

<Constraint android:id="@id/viewId">
    <!-- 运动模型: 弧线路路径,时间模型等 -->
    <Motion/>
    <!--
    布局相关
    注意: width 、 height 和 margin 的命名空间是 android: (beta1 开始)
         而约束相关的命名空间是 app (或 motion )
    -->
    <Layout/>
    <!-- 动画变换:做旋转,位移,缩放,海海拔等属性 -->
    <Transform/>
    <!--
    自定义属性
        attributeName 会加上 set/get 反射找到真正的函数名,
        ⽐如 backgroundColor 就会调用 setBackgroundColor() 函数
        custom(xxx)Value 对应属性的数据类型
    -->
    <CustomAttribute/>
    <!--
    特定的属性
        visibility 、alpha 等属性
    -->
    <PropertySet/>
</Constraint>

关于时间模型(加速、减速插值器…)

每个控件可以单独设置时间模型
在 <Motion> 节点中使⽤ app:transitionEasing

整个 Transition 也可以设置插值器
在<Transition>中使⽤app:motionInterpolator

Transition 节点

过渡动画的详细配置,可进行动画时长,触摸反馈,关键帧,动画的相关配置。大概结构如下:

<Transition
    app:constraintSetStart="@+id/start"
    app:constraintSetEnd="@+id/end"
    app:duration="1000">

    <OnSwipe
        app:touchAnchorId="@id/viewId"
        app:dragDirection="dragDown"/>

    <OnClick
        app:clickAction="toggle"
        app:targetId="@id/viewId"/>
    <KeyFrameSet >
        <KeyAttribute>
            <CustomAttribute/>
        </KeyAttribute>
        <KeyPostion/>
        <KeyCycle/>
        <KeyTimeCycle/>
    </KeyFrameSet>
</Transition>

onSwipe

滑动触摸事件

  • touchRegionId 指的是哪一个控件响应触摸事件。
  • touchAnchorId ⼀般不用
  • autoComplete 默认的是 true ,会根据动画完成的百分⽐⾃动到最近的一个状态
  • dragDirection 拖拽的方向

OnClick

点击触摸事件

  • targetId 指定控件
  • clickAction 注意这里是指点击后动画状态的改变 而不是执行某个自定义函数
    • toggle 反转状态
    • transitionToEnd/Start 通过动画到结束/起始状态
    • jumpToEnd/Start 没有动画直接到结束/起始状态

KeyFrameSet

可以设置位移,旋转,缩放等属性,同时还可以通过 CustomAttribute 添加自定义属性

  • app:motionTarget 目标对象 ID
  • app:framePosition 百分⽐(0 – 100) 需要改变的帧位置

其子节点有:

KeyPosition 位置关键帧

KeyPosition 可以帮助视图改变运动的路径形状

  • percentX/Y 在关键帧时,对应路路径的对应百分⽐
  • percentWidth/Height 在关键帧时,控件⼤⼩改变的百分⽐
  • curveFit 运动路径的样式(直线,曲线等)
  • keyPositionType 坐标系
    • parentRelative
      • (0,0)为父容器左上⻆
      • (1,1)为⽗容器右下角
    • deltaRelative
      • (0,0)为起始控件中心
      • (1,1)为结束控件中⼼
    • pathRelative
      • (0,0)为起始控件中⼼
      • (1,0)为结束控件中⼼

KeyCycle KeyTimeCycle

通过 3 个 KeyCycle 定义一个准确的循环关键帧,主要用于处理类似波形的动画

<KeyFrameSet>
<KeyTimeCycle
android:rotation="0"
app:framePosition="0"
app:motionTarget="@id/viewId"
app:wavePeriod="0"
app:waveShape="sin" />

<KeyTimeCycle
android:rotation="45"
app:framePosition="50"
app:motionTarget="@id/viewId"
app:wavePeriod="10"
app:waveShape="sin" />

<KeyTimeCycle
android:rotation="0"
app:framePosition="100"
app:motionTarget="@id/viewId"
app:wavePeriod="0"
app:waveShape="sin" />
</KeyFrameSet>

相关属性:

  • wavePeriod 表示循环次数 (在KeyTimeCycle中表示每秒循环次数)
  • waveShape 数学模型

分享一篇文章,用于加深部分属性的理解:https://juejin.im/post/5d2afcfef265da1b8a4f4d0e

实例有同僚写了:https://juejin.im/post/5c358e05f265da615b71a536

以上,仅作笔记记录,部分总结来自讲义。


2条评论

  • 头像

    小王

    您好,我是一名刚毕业的学生,现在正在求职中,主要求职方向为Android开发。在求职的过程中逐渐认识到了自身的不足,想提高下自身水平,无意间看到了朱老师的【HenCoder Plus】系列课程,但由于囊中羞涩承担不起正版课…想问下能否麻烦楼主分享下本课的相关资源呢?不胜感激!

    • Mosaic-C

      Mosaic-C

      不好意思的说,不太方便。

      第一是因为 腾讯课堂并不支持离线后分享,如果使用技术手段实现,章节之多耗费精力也不小,并可能给我带来法律隐患。
      第二是因为 Hencoder Plus 本身不适合新手去听,两年前我买的,现在在重新刷,因为很多之前未能理解。(扔老师的原话也是需要刷3~5遍)。
      第三是因为 这个课是重基础与原理,完成耗时巨大,不适合用于短时间内进行面试突破,同时本身需要结合很多背景知识才能理解通透。

      其实我那时候刚毕业也是用了花呗分期才买下来的,所以你现在最适合的是在面试过程中,总结经验,缺什么去补什么,虔诚一点,会找到公司的,然后在周末,多做练习。
      刚出来不要怕失败,多吃点苦懂得也多点,加油!

发表评论