【Android】MotionLayout实现开场动画
在移动应用开发中,动画不仅仅是美化界面的工具,它更是提升用户体验的关键手段。Android 平台一直以来都提供了丰富的动画框架,但随着应用复杂性的增加,开发者对动画的需求也变得更加复杂和多样化。为了更好地应对这些需求,Google 在 ConstraintLayout 的基础上推出了 MotionLayout。
效果展示
先来看看MotionLayout可以实现的效果吧~
来看看笔者用MotionLayout做的蒟蒻效果:
关于MotionLayout
如何去制作这样一个流畅的动画效果呢?我们直接进入本篇博客的主题:MotionLayout
MotionLayout简述
首先来简单介绍一下MotionLayout。MotionLayout 是 Android ConstraintLayout 的扩展,集布局和动画于一体,旨在简化复杂的界面过渡和动画效果的实现。与传统的动画框架相比,MotionLayout 更加声明性、更易于可视化,并且能够处理非常复杂的动画场景。通过 MotionLayout,开发者可以在布局文件中定义多个状态,并在这些状态之间进行平滑的过渡,从而实现丰富的动画效果,例如转场动画、导航菜单的展开与收起等。
这是Google官方文档中MotionLayout的介绍,我们可以看到它继承于ConstraintLayout,这在本文的后半部分,具体的代码操作中会有所体现,学习MotionLayout的使用可能需要对ConstraintLayout有一定的了解。
那么,MotionLayout是如何去实现动画的呢?所谓的声明性,可视性又是什么呢?又该如何定义多个状态,进行平滑过渡呢?我们接着往下看。
MotionScene
要做到动画效果的实现,我们离不开一个关键的配置文件:MotionScene。
它是一个 XML 文件,定义了动画的所有相关信息,包括状态、过渡、关键帧等。MotionScene
通过描述 ConstraintSet
(状态集)、Transition
(过渡)、KeyFrame
(关键帧)等来组织和管理动画的逻辑。
MotionScene
的整体结构可以分为以下几个主要部分:
ConstraintSet: 定义不同的布局状态(开始状态和结束状态)。Transition: 定义状态之间的过渡及其控制方式。KeyFrame: 定义动画过程中的关键变化点。OnSwipe: 定义与用户手势交互的动画触发器。
ConstraintSet
ConstraintSet
是 MotionScene 的基础部分,用于定义组件在不同状态下的布局约束。在 MotionScene 中,通常至少定义两个 ConstraintSet
,分别对应 start
和 end
状态。
每一个 ConstraintSet
都包含了布局中所有控件的约束信息。通过定义多个 ConstraintSet
,你可以控制动画在不同状态下的表现。(一个ConstraintSet即为一个状态下,UI控件的集合)
以下是一些常用的属性:
ConstraintSet
中每个Constraint
的属性基本与 ConstraintLayout 的属性相同。以下是一些常用属性:layout_constraintStart_toStartOf: 将组件的开始边缘与另一个组件的开始边缘对齐。layout_constraintEnd_toEndOf: 将组件的结束边缘与另一个组件的结束边缘对齐。layout_constraintTop_toTopOf: 将组件的顶部与另一个组件的顶部对齐。layout_constraintBottom_toBottomOf: 将组件的底部与另一个组件的底部对齐。layout_width 和 layout_height: 定义组件的宽度和高度。rotation: 设置组件的旋转角度。alpha: 设置组件的透明度。
开始状态与结束状态(Start and End States)
MotionLayout 的动画本质上是在两个状态之间进行转换:开始状态(start
)和结束状态(end
)。这两个状态定义了动画的起点和终点,分别通过 ConstraintSet
在 XML 中描述。
开始状态(Start State):这是动画的初始状态,定义了组件的布局、大小、位置、旋转角度等属性。结束状态(End State):这是动画的最终状态,描述了动画结束时组件应达到的目标属性。
在动画开始时,组件处于 start
状态,随着用户的操作或代码控制,MotionLayout 会将组件逐步过渡到 end
状态。这个转换过程是通过 Transition
来定义和控制的。
例如:笔者制作的动画效果的配置文件如下:
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.7"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintBottom_toTopOf="parent" />
<Constraint
android:id="@+id/rv_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
motion:layout_constraintStart_toEndOf="parent"
motion:layout_constraintTop_toTopOf="@id/linearLayout" />
<Constraint
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="10dp"
motion:layout_constraintTop_toBottomOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/toolbar"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.7"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@+id/rv_msg"
android:layout_width="match_parent"
android:layout_height="0dp"
motion:layout_constraintBottom_toTopOf="@id/linearLayout"
motion:layout_constraintTop_toBottomOf="@+id/toolbar"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="10dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
在 start
状态下,所有元素(toolbar
、rv_msg
、linearLayout
)都在屏幕外部,因此不可见。在 end
状态下,所有元素都移入屏幕内,toolbar
位于顶部,rv_msg
在 toolbar
下面,并占据屏幕的中间部分,linearLayout
位于屏幕底部。
当 MotionLayout 执行从 start
到 end
的过渡时,toolbar
、rv_msg
和 linearLayout
将从屏幕外部滑动到它们在 end
状态下的位置,创建一个滑动进入的动画效果。
Transition
Transition
是 MotionLayout 中的关键概念,用于定义从一个状态(ConstraintSet
)到另一个状态的过渡动画。Transition
不仅控制动画的时间和插值器,还可以定义触发动画的条件和动画的具体行为(例如响应触摸事件或特定的时间)。
例如,在这个实例中,可以通过滑动来控制方块控件的位置,这一操作可以定义在Transition中。
在笔者制作的动画效果中,Transition如下:
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="2000"
motion:autoTransition="animateToEnd">
</Transition>
123456
app:constraintSetStart
: 定义动画的起始状态,这个属性引用了一个 ConstraintSet
。app:constraintSetEnd
: 定义动画的结束状态,这个属性也引用了一个 ConstraintSet
。app:duration
: 动画的持续时间,以毫秒为单位。
除了基本属性,Transition
还提供了一些高级属性和功能,帮助你更细致地控制动画行为。
触摸事件(OnSwipe 和 OnClick)
你可以通过 OnSwipe
或 OnClick
来定义触摸事件,触发 Transition
的执行。
OnSwipe
: 定义了在滑动手势下如何触发 Transition
,例如上下滑动或左右滑动。
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@id/imageView"
app:touchAnchorSide="bottom" />
1234
app:dragDirection
: 指定滑动的方向,例如 dragUp
表示向上滑动。app:touchAnchorId
: 触摸事件的锚点,即滑动时要跟踪的视图。app:touchAnchorSide
: 指定锚点的哪一侧被用作触摸参考点。
OnClick
: 定义点击事件触发 Transition
,例如点击某个按钮时触发动画。
<OnClick
app:targetId="@id/button"
app:clickAction="transitionToEnd" />
123
app:targetId
: 触发点击事件的目标视图 ID。app:clickAction
: 定义点击后的动作,例如 transitionToEnd
表示触发从 start
到 end
的过渡。
自动过渡(AutoTransition)
Transition
还支持自动过渡,即在满足特定条件时自动触发动画。你可以通过以下属性控制自动过渡:
app:autoTransition
设置自动过渡的类型。
jumpToEnd
: 动画直接跳到结束状态。animateToEnd
: 动画自动播放到结束状态。jumpToStart
: 动画直接跳回开始状态。animateToStart
: 动画自动回到开始状态。
KeyFrame
KeyFrame
是 MotionLayout 中用于精确控制动画过程的工具。它允许你在动画的特定时间点(关键帧)对视图的属性进行自定义设置,以实现复杂的动画效果。KeyFrame
可以让你在动画过程中定义位置、属性变化、周期性动画等,从而获得更细致的动画表现。
其实就是实现帧动画
在 MotionLayout 中,KeyFrame
用于指定在动画过程中的某些关键时间点上视图的状态。这些关键时间点通常是动画的百分比位置(0% 到 100%),即动画开始到结束的过程中的特定位置。
可以实现如下的效果:
KeyFrame 类型
MotionLayout 提供了几种不同类型的 KeyFrame
,每种类型用于不同的动画控制需求:
KeyPositionKeyAttributeKeyCycle
1. KeyPosition
KeyPosition
主要用于控制视图的位置变化。通过 KeyPosition
,你可以定义在动画的特定时刻视图的位置(x
和 y
坐标)。
属性
app:framePosition
: 关键帧的位置,范围从 0 到 100,表示动画的百分比。app:motionTarget
: 关键帧作用的目标视图。app:percentX
: 目标视图在动画时刻的相对 X 坐标(0.0 到 1.0)。app:percentY
: 目标视图在动画时刻的相对 Y 坐标(0.0 到 1.0)。
示例
<KeyPosition
app:framePosition="50"
app:motionTarget="@id/targetView"
app:percentX="0.5"
app:percentY="0.5" />
12345
在这个示例中,KeyPosition
定义了在动画的 50% 位置上,targetView
的位置应为相对父视图的中心(50% X 和 50% Y)。
2. KeyAttribute
KeyAttribute
用于控制视图的属性变化,例如尺寸、透明度、旋转等。你可以在关键帧中定义这些属性在特定时刻的值。
属性
app:framePosition
: 关键帧的位置,范围从 0 到 100,表示动画的百分比。app:motionTarget
: 关键帧作用的目标视图。app:attributeName
: 要设置的属性名称(如 alpha
、rotation
、scaleX
、scaleY
等)。app:attributeValue
: 属性在关键帧时刻的值。
示例
<KeyAttribute
app:framePosition="50"
app:motionTarget="@id/targetView"
app:attributeName="alpha"
app:attributeValue="0.5" />
12345
3. KeyCycle
KeyCycle
用于控制周期性动画,即动画过程中的重复效果。它允许你创建周期性的动画效果,如摆动或波动。
属性
app:framePosition
: 关键帧的位置,范围从 0 到 100,表示动画的百分比。app:motionTarget
: 关键帧作用的目标视图。app:cycle
: 循环次数或周期。app:waveShape
: 波形形状,例如 sine
、triangle
等。app:wavePeriod
: 波形周期。
示例
<KeyCycle
app:framePosition="50"
app:motionTarget="@id/targetView"
app:waveShape="sine"
app:wavePeriod="1"
app:rotation="360" />
123456
怎么创建MotionLayout
可以像写ConstraintLayout一样,直接在创建的时候就选择使用MotionLayout,但是笔者更加推荐先写一个ConstraintLayout,再转换为MotionLayout的方法。(对喜欢改UI的人比较友好)
首先我们拿到一个ConstraintLayout
右侧工具栏,启动!ComponentTree,启动!右键main然后点击Convert to MotionLayout,就可以自动转换成MotionLayout啦,同时也会自动创建一个MotionScene配置文件。
不想写代码怎么办
那么这个时候可能就有同学会问了,虽然已经把需要写的代码简化成这样了,但我还是不想写,怎么办?(谁问你了?)
不要慌,请看VCR:
我们把右侧工具栏拉出来,发现了…一个AndroidStudio自带的图形化工具!
可以在这里自由注册Constraint中的组件,也可以自由设置start以及end状态中组件的位置,大小,角度等等属性。
点击左上角的箭头,还可以直接设置Transition的属性,添加关键帧等等。
(但是这个只能作为便利开发的一种手段,笔者亲测感觉这个东西不是太好用,主要体现在设置填入的时候总是不及时反馈,有时候设置了许多东西,运行发现并未设置上,叫人抓狂得很)
结语
参考文档:
突破传统动画:探索MotionLayout的独特优势-腾讯云开发者社区-腾讯云 (tencent.com)
MotionLayout examples | Views | Android Developers (google.cn)
Android | MotionLayout入门级使用教程(一)_motionlayout的使用-CSDN博客
希望这篇文章可以给大家的UI开发做一些思路上的扩展~