什么是MotionLayout
在2018 年 5 月发布的安卓 ConstraintLayout 2.0中,有这样一个新布局——MotionLayout。那么这个布局有什么用呢,在我看来,它的作用很像加强版的补间动画,你只需要提供给它开始和结束的xml布局文件,它就能自动帮你生成变换布局的动画,如下面安卓的官方示例图所示:
更为强大的是,它还支持滑动触发动画和点击触发动画两种方式,可以帮助我们实现复杂的用户界面动画和过渡效果,还是以安卓的示例图举例:
下面我们就来详细说说怎么使用这个神奇的布局控件。
简单使用
首先我们要简单了解下这个布局:
MotionLayout is a layout type that helps you manage motion and widget animation in your app. MotionLayout is a subclass of ConstraintLayout and builds on its rich layout capabilities. As part of the ConstraintLayout library, MotionLayout is available as a support library.
MotionLayout bridges the gap between layout transitions and complex motion handling, offering a mix of features between the property animation framework, TransitionManager, and CoordinatorLayout.
MotionLayout 是一种布局类型,可帮助您管理应用程序中的运动和小部件动画。 MotionLayout 是 ConstraintLayout 的子类,建立在其丰富的布局功能之上。作为 ConstraintLayout 库的一部分,MotionLayout 可作为支持库使用。
MotionLayout 弥合了布局转换和复杂运动处理之间的差距,提供了属性动画框架、TransitionManager 和 CoordinatorLayout 之间的混合功能。
根据安卓官方文档的介绍,我们可以知道MotionLayout其实是ConstraintLayout的子类,所以我们依然可以使用ConstraintLayout中控件的定位方式来写布局,这里我们先把MotionLayout当ConstraintLayout布局用,写一个变化前的界面:
<?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"
android:id="@+id/motionLayout"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layoutDescription="@xml/activity_main_scene1_scene"
app:showPaths="true">
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/black_333333"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:id="@+id/tv_show" />
</androidx.constraintlayout.motion.widget.MotionLayout>
可以看到,除了 app:layoutDescription和app:showPaths这两个属性外,其他的写法和ConstraintLayout基本一样,并且MotionLayout的子View也可以使用约束属性来调整位置。
我们先说app:showPaths这个属性,顾名思义,这个属性主要是展示MotionLayout的动画滑动轨迹,这里设置为true是为了方便调试。
而 app:layoutDescription这个属性需要传入一个布局文件,这个文件就是MotionLayout用来控制布局变换的关键所在,你可以通过android studio的提示来生成这个xml文件,也可以手动在res/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">
//动画开始前控件位置
<ConstraintSet android:id="@+id/start">
//每个控件的位置,通过id与上面xml中的控件关联
<Constraint android:id="@id/tv_show" />
</ConstraintSet>
//动画结束时控件位置
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@id/tv_show" />
</ConstraintSet>
//这里用来设置滑动还是点击触发变化
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start" >
</Transition>
</MotionScene>
我们可以看到MotionScene文件主要由三部分构成,我们先说起始和结束位置中子View的写法:
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/tv_show"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/tv_show"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</ConstraintSet>
可以看到写法和在约束布局中是基本一致的,我们只需要更改要变化的位置属性就可以了,下面重点说下Transition的用法:
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start"
app:duration="1000">
<OnSwipe
app:dragDirection="dragStart"
app:touchAnchorId="@id/tv_show"
app:touchAnchorSide="bottom" />
<OnClick
app:clickAction="toggle"
app:targetId="@id/tv_show"/>
</Transition>
如上文代码所示,Transition有两种触发动画的方式,分别是滑动触发OnSwipe和点击触发OnClick:
1、OnClick
通过 **app:duration=“1000”**来设置点击触发滑动后,动画时间
app:targetId:不用多说,设置点击哪个控件触发动画,通过id绑定。
app:clickAction有以下几个属性:
jumpToEnd——跳到结束位置,再次点击控件无法回到开始位置,
jumpToStart——跳到开始位置(要控件处于结束位置才有效),再次点击控件无法回到结束位置,
toggle——每次点击从当前位置滑动到相反的位置,
transitionToEnd——滑动到结束位置,再次点击控件无法回到开始位置,
transitionToStart——滑动到开始位置(要控件处于结束位置才有效),再次点击控件无法回到结束位置。
2、OnSwipe
app:touchAnchorId:设置拖动哪个控件触发动画,通过id绑定
app:dragDirection:设置拖动方向,有以下几个属性
dragAnticlockwise——逆时针拖动,很少使用,
dragClockwise——顺时针拖动,很少使用,
dragDown——向下托动,
dragEnd——向右拖动,
dragLeft——向左拖动,
dragRight——向右拖动,
dragStart——向左拖动,
dragUp——向上拖动,
touchAnchorSide: 跟踪手指的一侧(right / left / top / bottom)
了解了这些后,下面我们补全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">
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/tv_show"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/tv_show"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</ConstraintSet>
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start"
app:duration="1000">
<OnSwipe
app:dragDirection="dragStart"
app:touchAnchorId="@id/tv_show"
app:touchAnchorSide="start" />
</Transition>
</MotionScene>
运行后,我们就实现了上面的第一个例子:
小结
通过上面的学习,相信大家也都了解到了MotionLayout的使用,其实MotionLayout还有关键帧等技术可以辅助实现功能,关于MotionLayout的使用也有很多优秀的文章可供参考,这里附下我学习时的文章,下节我们来讲下实际开发中MotionLayout的坑点,也就是恨的部分: