Android MotionLayout相关

作者: 高丕基 | 来源:发表于2018-09-16 17:41 被阅读281次

    1、概述

    I / O '18提到了MotionLayout,当时还没有正式发布前段时间,在今年的6月26日正式发布了ConstraintLayout的2.0alpha版,也算正式推出了MotionLayout。 MotionLayout是ConstraintLayout的子类,它具有ConstraintLayout的所有属性。MotionLayout用来处理两个ConstraintSet之间的切换,并在根据两个ConstraintSet的CustomAttribute参数来自动生成切换动画,关于ConstraintSet下面会讨论。同时MotionLayout所增加的是可以直接通过触摸屏幕来控制动画的运行进度。也就是说MotionLayout会管理你的触摸事件通过跟踪手指的速度,并将其与系统中的视图速度相匹配。从而可以自然地在两者之间通过触摸滑动平稳过渡。并且在动画里面加入了关键帧的概念,使得其自动生成动画在运行时某一阶段会运行到关键帧的状态。同时MotionLayout支持在XML中完全描述一个复杂的动画,而不需要通过Java代码来实现。

    ConstraintSet之间切换

    2、ConstraintLayout动画

    ConstraintLayout中的动画要借助于ConstraintSet。ConstraintSet是一个轻量级对象,表示ConstraintLayout中所有子元素的constraints,margins和padding 。当将 ConstraintSet应用于显示ConstraintLayout时,布局会使用ConstraintSet中的约束来更新对应ConstraintLayout中的。ConstraintSet仅为视图的大小和位置设置动画,不会为其他属性设置动画(例如颜色)。

    下面的代码示例显示了如何动画将单个按钮移动到屏幕底部:

    
    public class MainActivity extends AppCompatActivity {
    
        ConstraintLayout constraintLayout;
        Button button;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.keyframe_one);
            constraintLayout = findViewById(R.id.constraint_layout);
    
            button = findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    animateToKeyframeTwo();
                }
            });
        }
    
        void animateToKeyframeTwo() {
    
            ConstraintSet constraintSet = new ConstraintSet();
            constraintSet.load(this, R.layout.keyframe_two); //载入要更新的布局到constraintSet中
            TransitionManager.beginDelayedTransition(constraintLayout); // 开启
            constraintSet.applyTo(constraintLayout);
        }
    }
    
    
    
    ConstraintSet动画

    这边要注意一下,只会运行R.layout.keyframe_two中与R.layout.keyframe_one中id对应的动画。不会出现R.layout.keyframe_two中有而R.layout.keyframe_one中没有的视图,也不会对R.layout.keyframe_one有但R.layout.keyframe_two中没有的视图有任何效果。

    3、MotionLayout动画

    前面已经演示了怎么对ConstraintLayout布局设置动画,现在来讨论下MotionLayout布局下的动画。

    3.1 MotionScene

    与通常的布局不同,MotionLayout所做的约束保存在一个单独的XML文件MotionScene中,该文件存储在您的res/xml目录中。

    MotionScene包含内容

    MotionScene文件可以包含指定动画所需的全部内容,例如前面提到的ConstraintSets、ConstraintSets直接的过渡、关键帧、触摸处理等等。

    3.2 创建动画简单流程

    3.2.1 先决条件

    ① Android Studio 3.2.0或更高版本

    ② 运行Android API等级21或更高版本的设备或模拟器

    ③ 添加依赖:

    
    implementation 'com.android.support:appcompat-v7:27.0.2'
    
    implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'
    
    

    或者

    
    implementation 'androidx.appcompat:appcompat:1.0.0-beta1'
    
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha1'
    
    

    这边要说明一下androidx也是今年IO刚刚推出的一个依赖,用来替代之前的com.android.support依赖。添加了它就不用添加一大堆v4、v7、v13等依赖了。

    3.2.2 定义布局

    前面提到过MotionLayout是ConstraintLayout的子类,所以MotionLayout可以直接替换ConstraintLayout。因为ConstraintLayout有的功能MotionLayout都有。

    3.2.3 创建MotionScene

    这一步是MotionLayout的关键,在res下的xml文件夹中创建MotionScene。其实在MotionLayout中可以不用添加想进行动画的视图的约束,而将约束放在ConstraintSet中,在将ConstraintSet放在MotionScene中。

    
    <MotionScene
    
        xmlns:android="http://schemas.android.com/apk/res/android"
    
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <ConstraintSet android:id="@+id/starting_set">
    
            <Constraint android:id="@+id/actor"
    
                app:layout_constraintBottom_toBottomOf="parent"
    
                app:layout_constraintRight_toRightOf="parent"
    
                android:layout_width="60dp"
    
                android:layout_height="60dp"/>
    
        </ConstraintSet >
    
        <ConstraintSet  android:id="@+id/ending_set" >
    
            <Constraint android:id="@+id/actor"
    
                app:layout_constraintTop_toTopOf="parent"
    
                app:layout_constraintLeft_toLeftOf="parent"
    
                android:layout_width="60dp"
    
                android:layout_height="60dp"/>
    
        </ConstraintSet >
    
    </MotionScene>
    
    

    这边需要注意的是每个ConstraintSet 里面的元素必须始终指定所需的位置和所需的大小。它会覆盖任何以前设置的布局信息。并且里面的id和MotionLayout中的视图的id要对应才会有反应。

    为了帮助MotionLayout 的视图理解必须约束集的顺序,需要创建一个Transition 元素。通过使用其直观命名 constraintSetStart 和constraintSetEnd 属性,可以指定首先应用哪个集合以及最后应用哪个集合。该Transition 元素还允许指定动画的持续时间。

    
    // 放在上面的<MotionScene> 和</MotionScene>之中和ConstraintSet 标签平级。
    
    <Transition
    
        android:id="@+id/my_transition"
    
        app:constraintSetStart="@+id/starting_set"
    
        app:constraintSetEnd="@+id/ending_set"
    
        app:duration="2000"/>
    
    

    此时,一个简单的MotionScene完成。但是此时任然没有和MotionLayout进行绑定。需要给MotionLayout添加app:layoutDescription属性来将上面的MotionScene绑定:

    
    app:layoutDescription="@xml/my_scene"
    
    

    3.2.4 启动动画

    运行应用程序时,MotionLayout 视图将自动将constraintSetStart 属性中指定的约束集设置到自己身上。因此,要启动动画,需要做的就是调用transitionToEnd() 方法从而实现ConstraintSet之间的转换:

    
    motion_container.transitionToEnd();
    
    
    动画效果

    3.2.5 动画执行进度监听

    可以通过给MotionLayout 设置监听器来监听动画进度,和动画完成时的回调:

    
    motionLayout.setTransitionListener(new MotionLayout.TransitionListener() {
    
                @Override
    
                public void onTransitionChange(MotionLayout motionLayout, int i, int i1, float v) {
    
                    seekBar.setProgress((int)(v*100));
    
                }
    
                @Override
    
                public void onTransitionCompleted(MotionLayout motionLayout, int i)             {
    
                }
    
    });
    
    

    上面对进度的监听通过seekbar表示出来

    动画进度监听

    3.3 关键帧(Key Frames)

    在上面的动画中,Button小部件看起来像在直线的路径中移动。这是因为MotionLayout 的视图此时其实只有两个关键帧:起始帧Button位于屏幕的右下角,终点帧Button位于屏幕的左上角。如果要改变路径的形状,则必须提供一些介于起点和终点之间关键。

    在开始创建关键帧之前,必须将KeyFrameSet 标签添加到MotionScene之中。可以自由创建任意数量的关键帧。

    
    <KeyFrameSet >
    
                ...
    
    </KeyFrameSet >
    
    

    MotionLayout 视图支持许多不同类型的关键帧。这里使用其中两种类型:KeyPosition 和KeyCycle 。

    3.3.1 KeyPosition

    KeyPosition 可以帮助视图改变运动路径的形状。创建它们时,请确保提供目标视图的ID,沿时间轴的位置,可以是0到100之间的任意数字,以及指定X或Y坐标已经运行到的百分比。可以设置type参数指出坐标是相对于实际的X或Y轴,还是相对于路径本身。

    
    <KeyFrameSet >
    
        <KeyPosition
    
        app:target="@+id/button"
    
        app:framePosition="30"
    
        app:type="deltaRelative"
    
        app:percentX="0.85"/>
    
        <KeyPosition
    
        app:target="@+id/button"
    
        app:framePosition="60"
    
        app:type="deltaRelative"
    
        app:percentX="1"/>
    
    </KeyFrameSet>
    
    

    上面第一个KeyPosition代表button按钮在运行道30%的时候,相对于运行轨迹x已经运行了85%了。第二个KeyPosition代表button按钮在运行道60%的时候,相对于运行轨迹x已经运行了100%了.效果如下,这样就可以避开和seekbar的冲突了:

    KeyPosition关键帧

    3.3.2 KeyCycle

    KeyCycle用来给动画添加振动。可以通过提供诸如要使用的波形和波形周期等详细信息来配置KeyCycle。下面是KeyCycle支持的各种振动波形:

    KeyCycle波形

    在上述动画中加入如下KeyCycle

    
    <KeyCycle
    
        app:target="@+id/button"
    
        app:framePosition="30"
    
        android:rotation="50"
    
        app:waveShape="sin"
    
        app:wavePeriod="1"/>
    
    
    KeyCycle关键帧

    3.4 交互式动画

    上面的动画运行我都是通过对Button按钮设置点击监听事件,然后调用motion_container.transitionToEnd();方法来使他运行的。其实完全不必这么麻烦,因为MotionLayout的视图允许开发者将触摸事件直接附加到视图中。截止到现在,它支持点击和滑动事件。要实现上面实现的点击事件可以在MotionScene中增加代码如下:

    
    <OnClick
    
        app:target="@+id/button"
    
        app:mode="transitionToEnd"/>
    
    

    而可以通过给MotionScene增加OnSwipe标签来使视图通过在屏幕滑动而大运行。在创建该标签时,必须确保提供正确的拖动方向以及应作为拖动控制柄的视图的边。可以这么理解,相对于初始位置,如果想往上滑起到增加动画进度就设置为dragUp,想往下滑起到增加动画进度就设置为dragDown,左右同样道理。至于touchAnchorSide这个参数的本意应该设置拉目标视图的边,但我发现就算不设置touchAnchorSide这个参数或者设置成任意值top bottom或者left right,对动画都没有影响。这可能是MotionLayout的一个bug毕竟现在还只是alpha版。

    
    <OnSwipe
    
        app:touchAnchorId="@+id/actor"
    
        app:dragDirection="dragUp"/>
    
    
    OnSwipe动画

    5、MotionEditor

    之前一篇讨论ConstraintLayout的文章,基本上都是在布局编辑器中进行操作。这也是ConstraintLayout的一大优点,MotionLayout作为其子类,官方也为它专门提供了强大的可视化编辑器。不过可惜的是,到目前为止还不能使用,下面是MotionEditor的官方预告片的一个节选:

    MotionEditor编辑动画

    相关文章

      网友评论

      • 有点健忘:你有试过 onClick的 mode=jumpToStart或者jumpToEnd 吗,我以为是直接结束动画,可试了下直接就挂了,看日志state是空的。这源码方法也都没注释,字段啥意思也看不懂。不好研究,你知道为啥吗
        高丕基:@有点健忘 StateSet 这是一个状态集合的标签,里面可以设置 默认状态属性(不过我试了下好像默认状态不起作用) 和子标签State,然后State标签里面可以添加constraints属性 这里面填的是上面说的ConstraintSet 的id和State的自己的id属性, 同时可以添加Variant子标签,这个标签里面可以设置在对应motionLayout的宽高小于或大于某一范围时候所采取的约束条件。每个状态可以通过motionLayout的transitionToState(int id) 和 transitionToState(int id, int screenWidth, int screenHeight) 来进行切换。也可以在motionLayout的布局中添加 app:currentState 标签来设置初始状态。举个例子 可以通过StateSet 和transitionToState() 方法组合 实现前面的Transition 标签和transitionToEnd()方法组合 达到的 那个按钮移动的动画效果。了解更多看官方手册哈 https://developer.android.com/reference/android/support/constraint/motion/MotionLayout 。还有 我回答了这么多,不给我文章点个赞再走嘛:joy:
        有点健忘:@高丕基 谢谢解答,那我就不研究了。StateSet 有研究吗,我咋试了试,都没啥反应,也不知道这玩意干啥的,看解释也是云里雾里的,完全不知道干啥的。
        高丕基:这两个字面意思应该是跳转到动画开始状态或结尾状态。不过我这边式了下也直接崩溃,怀疑是官方bug毕竟是alpha版。已经上报给他们了。
      • 有点健忘:constraint-layout:2.0.0-alpha1 这个玩意咋要求最低版本是18.。我的最低版本是16.
        另外开头代码
        TransitionManager.beginDelayedTransition(); // 没这个方法啊,我的必须要有参数的,你咋不用参数,写错了吧

        constraintLayout.apply(constraintSet);
        constraintLayout 是这个吗android.support.constraint.ConstraintLayout ,也米有apply方法
        有点健忘:@高丕基 非常感谢,看完终于可以入门了。
        高丕基:那段代码确实有误,现已修改。

      本文标题:Android MotionLayout相关

      本文链接:https://www.haomeiwen.com/subject/fpjfnftx.html