MotionLayout是安卓新推出的一种布局,用来实现一些炫酷的交互效果,它是ConstraintLayout的子类,不仅拥有自适应的能力,还能实现组件的一些动画和运动效果,兼容Android API 14及以上。 我们可以像使用ConstraintLayout一样,添加约束来控制布局显示,不同的是,我们还可以在XML中为布局和视图新增过渡、属性变化等动画效果。
在Android中,已经有不少实现视图和布局动画效果的方法和库,比如:
上面几种方式各有特点,比如Animated Vector Drawable和Property Animation专注的是动画效果,但是没有结合布局变换,而LayoutTransition和TransitionManager虽然结合了布局变换,但是却不能很方便的实现一些较复杂的动画,CoordinatorLayout虽然结合了布局变换和动画效果,但是不够灵活,有一定的局限性。
MotionLayout就相当于这几者的结合体,它不仅能实现布局变换的过渡效果,还能同时为每一个视图和布局实现属性变化动画,而且还支持手势处理,另外一个优点是它是声明式的,所有动画和过渡效果,无论多复杂都可以在XML中定义。
MotionLayout这么强大,那么如何来使用呢?下面一步步看看如何在项目中使用MotionLayout。
首先,在build.gradle中,添加MotionLayout的依赖,其实就是ConstraintLayout的依赖,不过它是2.0版本才加进来的,所以记得引入2.x.x的版本。
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
然后在布局中添加MotionLayout,如果是从ConstraintLayout改造的话,只需要把ConstraintLayout改为MotionLayout即可。
<androidx.constraintlayout.motion.widget.MotionLayout
app:layoutDescription="@xml/example1"
app:motionDebug="SHOW_PATH" …/>
接着为MotionLayout创建MotionScene,每个MotionLayout都对应一个MotionScene文件,它是一个XML文件,它里面包含了所有的动画效果的配置。从XML上面来看,MotionLayout就比ConstraintLayout多了一个MotionScene文件的配置,这样它俩之间转换就非常容易,因为关于动画的所有配置都在一个单独的MotionScene文件中。
在MotionScene中主要由四个元素组成,MotionScene、Transition、KeyFrameSet和ConstraintSet。
更多关于MotionScene的参考可以查看MotionLayout 参考文档。
基础示例,实现的功能是:当点击开始动画
按钮后,图标在3秒内从左上方移动到右下方,布局文件如下:
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 app:layoutDescription="@xml/example1"
8 app:motionDebug="SHOW_PATH"
9 tools:context=".Example1">
10
11 <ImageView
12 android:id="@+id/icon"
13 android:layout_width="40dp"
14 android:layout_height="40dp"
15 android:src="@mipmap/ic_launcher" />
16
17 <Button
18 android:id="@+id/motion"
19 android:layout_width="wrap_content"
20 android:layout_height="wrap_content"
21 android:text="开始动画"
22 app:layout_constraintEnd_toEndOf="parent"
23 app:layout_constraintStart_toStartOf="parent"
24 app:layout_constraintTop_toTopOf="parent" />
25
26</androidx.constraintlayout.motion.widget.MotionLayout>
MotionScene文件配置如下:
1<?xml version="1.0" encoding="utf-8"?>
2<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto">
4
5 <!--配置开始和结束状态的约束,以及动画的时长-->
6 <Transition
7 app:constraintSetEnd="@id/end"
8 app:constraintSetStart="@id/start"
9 app:duration="3000">
10 <!--配置触发机制-->
11 <OnClick
12 app:clickAction="toggle"
13 app:targetId="@id/motion" />
14 </Transition>
15
16 <!--配置开始状态的约束-->
17 <ConstraintSet android:id="@+id/start">
18 <Constraint
19 android:id="@+id/icon"
20 android:layout_width="40dp"
21 android:layout_height="40dp"
22 app:layout_constraintStart_toStartOf="parent"
23 app:layout_constraintTop_toTopOf="parent" />
24 </ConstraintSet>
25
26 <!--配置结束状态的约束-->
27 <ConstraintSet android:id="@+id/end">
28 <Constraint
29 android:id="@+id/icon"
30 android:layout_width="40dp"
31 android:layout_height="40dp"
32 app:layout_constraintBottom_toBottomOf="parent"
33 app:layout_constraintEnd_toEndOf="parent" />
34 </ConstraintSet>
35
36</MotionScene>
最终实现的效果图如下:
添加KeyFrameSet,改变中间过程,实现复杂的动画效果,该示例的功能是:当点击开始动画
按钮后,图标在3秒内从左上方移动到右下方,并且在动画的过程中,先逐渐转圈放大1倍,最后再转圈恢复原始大小,另外从动画开始到运行到30%的时候,需要将图标的x坐标从开始逐渐转换到父视图70%宽的位置处,从动画30%到70%的过程中,需要将图标的x坐标从父视图70%宽的位置处逐渐转换的父视图30%宽的位置处。
该示例与实例一的布局一样,MotionScene文件配置如下:
1<?xml version="1.0" encoding="utf-8"?>
2<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto">
4
5 <!--配置开始和结束状态的约束,以及动画的时长-->
6 <Transition
7 app:constraintSetEnd="@id/end"
8 app:constraintSetStart="@id/start"
9 app:duration="3000">
10 <!--配置触发机制-->
11 <OnClick
12 app:clickAction="toggle"
13 app:targetId="@id/motion" />
14
15 <!--修改中间过程-->
16 <KeyFrameSet>
17 <!--动画进行到30%这过程中,将x坐标从开始状态转换到父View宽度的的70%的位置-->
18 <KeyPosition
19 app:framePosition="30"
20 app:keyPositionType="parentRelative"
21 app:motionTarget="@id/icon"
22 app:percentX="0.7" />
23 <!--动画进行到70%这过程中,将x坐标从当前状态转换到父View宽度的的30%的位置-->
24 <KeyPosition
25 app:framePosition="70"
26 app:keyPositionType="parentRelative"
27 app:motionTarget="@id/icon"
28 app:percentX="0.3" />
29 <!--前半段的动画效果:逆时针旋转一圈,同时放大一倍-->
30 <KeyAttribute
31 android:rotation="-360"
32 android:scaleX="2.0"
33 android:scaleY="2.0"
34 app:framePosition="50"
35 app:motionTarget="@id/icon" />
36 <!--后半段的动画效果:逆时针旋转一圈,同时变回原样-->
37 <KeyAttribute
38 android:rotation="-720"
39 app:framePosition="100"
40 app:motionTarget="@id/icon" />
41 </KeyFrameSet>
42 </Transition>
43
44 <!--配置开始状态的约束-->
45 <ConstraintSet android:id="@+id/start">
46 <Constraint
47 android:id="@+id/icon"
48 android:layout_width="40dp"
49 android:layout_height="40dp"
50 app:layout_constraintStart_toStartOf="parent"
51 app:layout_constraintTop_toTopOf="parent" />
52 </ConstraintSet>
53
54 <!--配置结束状态的约束-->
55 <ConstraintSet android:id="@+id/end">
56 <Constraint
57 android:id="@+id/icon"
58 android:layout_width="40dp"
59 android:layout_height="40dp"
60 app:layout_constraintBottom_toBottomOf="parent"
61 app:layout_constraintEnd_toEndOf="parent" />
62 </ConstraintSet>
63
64</MotionScene>
最终实现的效果图如下:
通过手势来触发和控制动画,实现功能是:通过手势可以控制动画的开始结束和进度。该示例和示例一的布局文件一样,MotionScene文件也基本上一致,只是Transition中的OnClick
改为了OnSwipe
。
<!--配置开始和结束状态的约束,以及动画的时长-->
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@id/start"
app:duration="3000">
<!--配置触发机制-->
<OnSwipe app:touchAnchorId="@id/icon" />
</Transition>
最终实现的效果如下:
一个播放栏的真实案例,实现功能是:当向上拖动播放栏时,将整个状态栏放大至全屏,当向下拖动播放栏时,缩小播放栏到屏幕底部,最终实现效果如下:
由于涉及代码较多,就不一一贴出代码了,感兴趣可以在底部查看或下载整个项目,