过渡动画及MotionLayout

1,031 阅读4分钟

一、过渡动画

以下面的布局为例,让上面的球变大二倍

布局文件如下

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/play_basketball"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/iv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/play_basketball"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_one" />

</androidx.constraintlayout.widget.ConstraintLayout>

布局效果

image.png

1、属性动画

binding.ivOne.setOnClickListener {
    with(binding.ivOne.animate()){
        scaleX(2f)
        scaleY(2f)
        duration = 800
        start()
    }
}

效果展示

2022.02.16.10.21.48.gif

2、TransitionManager.beginDelayedTransition

binding.ivOne.setOnClickListener {
    val width = binding.ivOne.width
    //设置过渡动画(底层也是属性动画,关注captureStartValues和captureEndValues方法)
    TransitionManager.beginDelayedTransition(binding.root)
    val constraintSet = ConstraintSet()
    constraintSet.clone(binding.root)
    //修改ImageView的右边为父控件的右边,如果想修改多个属性需要写多条constraintSet.connect()
    //constraintSet.connect(
    //    binding.ivOne.id, ConstraintSet.END, binding.root.id, ConstraintSet.END
    //)
    constraintSet.constrainWidth(binding.ivOne.id, width * 2)
    constraintSet.constrainHeight(binding.ivOne.id, width * 2)
    constraintSet.applyTo(binding.root)
}

效果展示

2022.02.16.11.06.51.gif

这个方式的优点是可以改变控件的属性并重新排布。

3、TransitionManager.go

通过设置开始布局和结束布局实现动画。

Activity的布局代码:

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        layout="@layout/go_start" />

</androidx.constraintlayout.widget.ConstraintLayout>

go_start.xml的代码:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="150dp"
        android:layout_height="0dp"
        android:src="@mipmap/ig_guimie"
        app:layout_constraintDimensionRatio="0.685"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="@string/text_one"
        android:textColor="@color/black"
        android:textSize="26sp"
        app:layout_constraintLeft_toRightOf="@id/iv"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/text_two"
        android:textColor="#666666"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv" />


</merge>

go_end.xml的代码:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ig_guimie"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="@string/text_one"
        android:textColor="@color/black"
        android:textSize="26sp"
        app:layout_constraintLeft_toRightOf="@id/iv"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/text_two"
        android:textColor="#666666"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv" />


</merge>

注意go_start.xmlgo_end.xml的布局ID必须一致。

Activity中的代码:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    var toggle = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bindData()
    }

    override fun onClick(v: View?) {
        val root  = findViewById<ConstraintLayout>(R.id.root)
        val startScene = Scene.getSceneForLayout(root, R.layout.go_start, this)
        val endScene = Scene.getSceneForLayout(root, R.layout.go_end, this)

        if (!toggle) {
            TransitionManager.go(endScene,ChangeBounds())
        } else {
            TransitionManager.go(startScene,ChangeBounds())
        }
        //重新绑定数据
        bindData()

        toggle = !toggle
    }

    private fun bindData() {
        findViewById<ImageView>(R.id.iv).setOnClickListener(this)
        findViewById<TextView>(R.id.tv_one).text = getString(R.string.text_one)
        findViewById<TextView>(R.id.tv_two).text = getString(R.string.text_two)
    }
}

效果展示:

2022.02.16.17.57.47.gif

TransitionManager.go方法第二个参数就是动画效果,主要包括以下这些:

继承Transiton类的效果

ChangeBounds:检测view的位置边界创建移动和缩放动画

ChangeTransform:检测view的scale和rotation创建缩放和旋转动画

ChangeClipBounds:检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

ChangeImageTransform:检测ImageView的尺寸,位置以及ScaleType,并创建相应动画。

Fade,Slide,Explode:根据view的visibility的状态执行渐入渐出,滑动,分解动画。

注意,基于基类封装的ViewBinding获取的ID不起作用,原因是Scene类中方法getSceneRoot().removeAllViews把View都移除掉了,需要重新获取ID

4、使用ConstraintSet优化代码

那么有没有其他方式可以优化上面的代码呢? 是有的,不需要重新绑定数据(bindData),代码如下:

go_start.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"    //根布局添加ID
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="150dp"
        android:layout_height="0dp"
        android:src="@mipmap/ig_guimie"
        app:layout_constraintDimensionRatio="0.685"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="@string/text_one"
        android:textColor="@color/black"
        android:textSize="26sp"
        app:layout_constraintLeft_toRightOf="@id/iv"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/text_two"
        android:textColor="#666666"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv" />


</androidx.constraintlayout.widget.ConstraintLayout>

go_end.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"   //根布局添加ID
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ig_guimie"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="@string/text_one"
        android:textColor="@color/black"
        android:textSize="26sp"
        app:layout_constraintLeft_toRightOf="@id/iv"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/text_two"
        android:textColor="#666666"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv" />


</androidx.constraintlayout.widget.ConstraintLayout>

Activity的代码

class MainActivity : AppCompatActivity(), View.OnClickListener {
    var toggle = false
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.go_start)
        findViewById<ImageView>(R.id.iv).setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        val root  = findViewById<ConstraintLayout>(R.id.root)
        TransitionManager.beginDelayedTransition(root)
       val constraintSet = ConstraintSet()
        if (!toggle) {
            constraintSet.clone(this,R.layout.go_end)
        } else {
            constraintSet.clone(this,R.layout.go_start)
        }
        constraintSet.applyTo(root)
        toggle = !toggle
    }

}

效果同上,避免每次重新绑定数据

2022.02.16.17.57.47.gif

二、MotionLayout

1、使用MotionLayout进一步优化上面的代码

使用MotionLayout只需要写一个布局文件,但是需要增加一个描述控件属性变化的xml文件。

image.png

activity_main.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/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion">  //引入motion文件(必须)

    <ImageView   //变化的只有ImageView下面二个控件不用管
        android:id="@+id/iv"
        android:layout_width="150dp"
        android:layout_height="0dp"
        android:src="@mipmap/ig_guimie"
        app:layout_constraintDimensionRatio="0.685"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:text="@string/text_one"
        android:textColor="@color/black"
        android:textSize="26sp"
        app:layout_constraintLeft_toRightOf="@id/iv"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/text_two"
        android:textColor="#666666"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv" />


</androidx.constraintlayout.motion.widget.MotionLayout>

motion.xml的代码

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

    <Transition
        //指定开始和结束的ID
        app:constraintSetEnd="@id/end"  
        app:constraintSetStart="@id/start">

        <!--点击事件,当前版本点击事件和触摸事件的控件ID不能相同,如果相同则二个事件只能二选一-->
        <OnClick
            app:clickAction="toggle"  //toggle切换,此外还有jumpToStart/End,transitionToStart/End
            app:targetId="@+id/iv" />

        <!--触摸事件,当前版本点击事件和触摸事件的控件ID不能相同,如果相同则二个事件只能二选一-->
        <!--<OnSwipe
            app:touchRegionId="@+id/iv"
            app:dragDirection="dragDown"  //拖拽的方向:dragDown/End/Left/Right/Start/Up
            app:onTouchUp="autoComplete"  //手指抬起时: autoComplete/autoCompleteToStart/autoCompleteToEnd/Stop等等
            />-->

    </Transition>

    //设置开始的ConstraintSet
    <ConstraintSet android:id="@+id/start"></ConstraintSet>
    
    //设置结束的ConstraintSet,里面包裹的就是变化后的Imageview,但必须把节点修改为Constraint,如果有多个变化的控件都要这样改
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@mipmap/ig_guimie"
            app:layout_constraintDimensionRatio="0.685"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

</MotionScene>

MainActivity中不需要写任何控制代码。

class MainActivity : BaseBindingActivity<ActivityMainBinding>(){

}

效果同上,不需要写二个布局文件,且不影响ViewBinding的封装。但是写到motion.xml的控件(有定义OnClick)会不再响应Activity写的点击事件。

延伸:如果将点击事件改触摸事件,会随着手指移动完成动画,也可以快速滑动,MotionLayout会根据滑动的距离自动判断回到起点还是完成动画。修改onTouchUp参数会有不同的效果。

2、MotionLayout特性介绍

注意:MotionLayout是继承自ConstraintLayout,MotionLayout的子控件必须有ID,不然会报下面的错误: java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet

布局代码如下:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion"
    app:showPaths="true"  //查看路径的属性设置为true
    tools:context=".MainActivity">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/iv"  //子控件必须有ID
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@mipmap/girl"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.motion.widget.MotionLayout>

motion.xml代码如下,很简单,后面再一点点加:

<?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:constraintSetStart="@id/start"
        app:constraintSetEnd="@id/end">
    </Transition>

    <ConstraintSet android:id="@+id/start">

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">

    </ConstraintSet>

</MotionScene>

效果展示:

image.png

1)添加结束布局,让图片从左上角移动到右下角

<?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"  //动画执行的时间
        app:motionInterpolator="@string/fab_transformation_scrim_behavior">  //添加插值器

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />
    </Transition>

    <ConstraintSet android:id="@+id/start">  //Set是一个集合,可以定义多个Constraint
        <Constraint android:id="@+id/iv"></Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">  //Set是一个集合,可以定义多个Constraint
        <Constraint android:id="@+id/iv">
            <Layout   //修改结束的布局属性
                android:id="@+id/iv"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

            <CustomAttribute  //添加布局中没有的属性,但这个属性必须控件有,而且这个属性设置后不可逆见效果),如果希望属性可逆在开始节点设置回来就可以了
                app:attributeName="saturation"  //设置饱和度
                app:customFloatValue="0.5" />

            <PropertySet android:alpha="0.5" />  //透明度,Visibility的三个属性也在这里,这里设置的属性可逆
        </Constraint>

    </ConstraintSet>

</MotionScene>

效果展示

2022.02.17.22.20.40.gif

2)修改运动轨迹

<?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">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Motion  //开始时定义Motion节点
                app:pathMotionArc="startHorizontal"  //开始时先水平
                app:transitionEasing="accelerate" />  //accelerate:加速/decelerate:减速

        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:id="@+id/iv"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />
        </Constraint>

    </ConstraintSet>

</MotionScene>

效果展示

2022.02.17.22.33.14.gif

3)增加旋转缩放效果

在ConstraintSet的end节点增加这些效果,使动画结束时实现

<?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="1500">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Motion app:pathMotionArc="startHorizontal" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:id="@+id/iv"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

            <!--结束节点增加常规动画,旋转180度,放大2倍-->
            <Transform
                android:rotation="180"
                android:scaleX="2.0"
                android:scaleY="2.0" />
        </Constraint>

    </ConstraintSet>

</MotionScene>

效果展示

2022.02.23.12.38.16.gif

4)属性关键帧

<?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="1500">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />

        <!--关键帧-->
        <KeyFrameSet>
            <!--属性关键帧-->
            <KeyAttribute
                android:rotation="360" //旋转360度
                android:scaleX="2.0"  //放大2倍
                android:scaleY="2.0"
                app:framePosition="50"  //关键帧的位置动画执行到50%的时候
                app:motionTarget="@id/iv" />  //作用的对象的ID
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Motion app:pathMotionArc="startHorizontal" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:id="@+id/iv"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

        </Constraint>

    </ConstraintSet>

</MotionScene>

在关键帧时实现效果,动画结束时实现结束节点的效果。效果展示

2022.02.23.12.54.30.gif

5)位置关键帧

<?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="1500">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />

        <!--关键帧-->
        <KeyFrameSet>
            <!--位置关键帧-->
            <KeyPosition
                app:motionTarget="@id/iv"  //作用的对象的ID
                app:framePosition="50"    //关键帧的位置动画执行到50%的时候
                app:pathMotionArc="startVertical"  //如果开始节点不设置pathMotionArc这里设置无效
                app:keyPositionType="pathRelative"  //有三种,最常用的是二种parentRelative和deltaRelative这里是以pathRelative举例说明坐标系parentRelative是以父容器为基准设置坐标系deltaRelative是
                以MotionLayout为基准设置坐标系而pathRelative则是以路径方向定义X轴相应定义Y轴
                app:percentX="0.90"  //X坐标的位置
                app:percentY="0.25" />   //Y坐标的位置
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Motion app:pathMotionArc="startHorizontal" />  //这里设置pathMotionArc,上面的pathMotionArc才会生效
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:id="@+id/iv"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

        </Constraint>

    </ConstraintSet>

</MotionScene>

效果展示

2022.02.23.13.45.20.gif

坐标系

image.png

6)循环关键帧

<?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="1500">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@+id/iv" />

        <!--关键帧-->
        <KeyFrameSet>
            <!--循环关键帧-->
            <KeyCycle
                android:rotation="0"
                app:framePosition="0"     //动画完成度0%
                app:motionTarget="@id/iv"
                app:wavePeriod="0"
                app:waveShape="sin" />

            <KeyCycle
                android:rotation="90"    //旋转90度
                app:framePosition="50"   //动画完成度50%
                app:motionTarget="@id/iv"
                app:wavePeriod="2"       //循环次数
                app:waveShape="sin" />   //循环参数(见下方)

            <KeyCycle
                android:rotation="0"
                app:framePosition="100"  //动画完成度100%
                app:motionTarget="@id/iv"
                app:wavePeriod="0"
                app:waveShape="sin" />
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Layout
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintLeft_toLeftOf="parent" />
            <Motion app:pathMotionArc="startHorizontal" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

        </Constraint>

    </ConstraintSet>

</MotionScene>

参数说明 waveShape参数有以下几种,具体效果可自行尝试

<attr format="enum|string" name="waveShape">
    <enum name="sin" value="0"/>   //按三角函数的计算结果
    <enum name="square" value="1"/>  //方形翻转
    <enum name="triangle" value="2"/>  //三角形
    <enum name="sawtooth" value="3"/>  //锯齿,有停顿
    <enum name="reverseSawtooth" value="4"/>  //与sawtooth方向相反
    <enum name="cos" value="5"/>  //按三角函数的计算结果
    <enum name="bounce" value="6"/>  //有阻尼的反弹
</attr>

效果展示

2022.02.23.14.58.21.gif

7)时间关键帧

改成触摸事件更能看出随时间的变化

<?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="1500">

        <!--改成触摸事件-->
        <OnSwipe
            app:dragDirection="dragDown"
            app:onTouchUp="autoComplete"
            app:touchRegionId="@+id/iv" />

        <!--关键帧-->
        <KeyFrameSet>
            <!--时间关键帧-->
            <KeyTimeCycle
                android:rotation="0"
                app:framePosition="0"
                app:motionTarget="@id/iv"
                app:wavePeriod="0"
                app:waveShape="sin" />

            <KeyTimeCycle
                android:rotation="90"
                app:framePosition="50"
                app:motionTarget="@id/iv"
                app:wavePeriod="2"   //每秒循环的次数
                app:waveShape="sin" />

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

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@+id/iv">
            <Motion app:pathMotionArc="startHorizontal" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@+id/iv">
            <Layout
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@mipmap/girl"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

        </Constraint>

    </ConstraintSet>

</MotionScene>

触摸,中途不抬起的效果展示

2022.02.23.15.20.36.gif

8)AS对MotionLayout的支持

点击start可以看到开始,点击end可以看到结束

image.png

点击中间的线可以看到关键帧,点击播放按钮可以播放

image.png

--个人学习笔记