MotionLayout使用入门

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长14分钟
小课

MotionLayout是安卓新推出的一种布局,用来实现一些炫酷的交互效果,它是ConstraintLayout的子类,不仅拥有自适应的能力,还能实现组件的一些动画和运动效果,兼容Android API 14及以上。 我们可以像使用ConstraintLayout一样,添加约束来控制布局显示,不同的是,我们还可以在XML中为布局和视图新增过渡、属性变化等动画效果。

为什么使用MotionLayout?

在Android中,已经有不少实现视图和布局动画效果的方法和库,比如:

上面几种方式各有特点,比如Animated Vector Drawable和Property Animation专注的是动画效果,但是没有结合布局变换,而LayoutTransition和TransitionManager虽然结合了布局变换,但是却不能很方便的实现一些较复杂的动画,CoordinatorLayout虽然结合了布局变换和动画效果,但是不够灵活,有一定的局限性。

MotionLayout就相当于这几者的结合体,它不仅能实现布局变换的过渡效果,还能同时为每一个视图和布局实现属性变化动画,而且还支持手势处理,另外一个优点是它是声明式的,所有动画和过渡效果,无论多复杂都可以在XML中定义。

如何使用MotionLayout?

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中主要由四个元素组成,MotionScene、Transition、KeyFrameSet和ConstraintSet。

  • MotionScene,根元素,所有的配置都在该元素内配置,可以配置一个或多个Transition元素。
  • Transition,过渡动画配置,指定过渡之前的布局约束和过渡之后的布局约束以及触发机制,一般Transition对应两个ConstraintSet,一个开始状态和一个结束状态。
  • ConstraintSet,布局约束集合,包括多个Constraint元素,所有需要实现过渡动画的视图和布局都可以通过一个Constraint来配置约束。
  • KeyFrameSet,关键帧配置,默认过渡效果从开始状态逐渐到结束状态,这期间的状态可以通过该元素来配置,这样就可以实现较复杂的动画效果。

更多关于MotionScene的参考可以查看MotionLayout 参考文档

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>

最终实现的效果图如下:

MotionLayout使用入门
MotionLayout使用示例二

添加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>

最终实现的效果图如下:

MotionLayout使用入门
MotionLayout使用示例三

通过手势来触发和控制动画,实现功能是:通过手势可以控制动画的开始结束和进度。该示例和示例一的布局文件一样,MotionScene文件也基本上一致,只是Transition中的OnClick改为了OnSwipe

<!--配置开始和结束状态的约束,以及动画的时长-->
<Transition
    app:constraintSetEnd="@id/end"
    app:constraintSetStart="@id/start"
    app:duration="3000">
    <!--配置触发机制-->
    <OnSwipe app:touchAnchorId="@id/icon" />
</Transition>

最终实现的效果如下:

MotionLayout使用入门
MotionLayout使用示例四

一个播放栏的真实案例,实现功能是:当向上拖动播放栏时,将整个状态栏放大至全屏,当向下拖动播放栏时,缩小播放栏到屏幕底部,最终实现效果如下:

MotionLayout使用入门

由于涉及代码较多,就不一一贴出代码了,感兴趣可以在底部查看或下载整个项目,

文件大小修改时间
app
2022年03月02日
build.gradle
368 B 2022年03月01日
gradle/wrapper
2022年03月01日
gradle.properties
1 kB 2022年03月01日
gradlew
6 kB 2022年03月01日
gradlew.bat
3 kB 2022年03月01日
local.properties
437 B 2022年03月01日
settings.gradle
329 B 2022年03月01日
MotionLayoutandroidview