MontionLayout:打开动画新世界大门(其一)

64 阅读9分钟

布局文件很简单,只不过你可能会注意到,我们对 ImageView 并没有添加任何约束,原因在于:我们会在 MotionScene 中声明ConstraintSet,里面将包含该 ImageView 的“运动”起始点和终点的约束信息。当然你也可以在布局文件中对其加以约束,MotionScene 中对于控件约束的优先级会高于布局文件中的设定 。这里我们通过 layoutDescription 来为MotionLayout 设置它的 MotionScene 为 step1,接下来就让我们一睹 MotionScene 的芳容:

1<?xml version="1.0" encoding="utf-8"?>

2<!--describe the animation for activity_motion_sample_step1.xml-->

3<MotionScene xmlns:android="schemas.android.com/apk/res/and…"

4             xmlns:app="schemas.android.com/apk/res-aut…">

5    <!-- A transition describes an animation via start and end state -->

6    <Transition

7        app:constraintSetStart="@id/start"

8        app:constraintSetEnd="@id/end"

9        app:duration="2200">

10        <OnClick

11                app:targetId="@id/ball"

12                app:clickAction="toggle" />

13    </Transition>

14

15    <!-- Constraints to apply at the start of the animation -->

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

17        <Constraint

18                android:id="@+id/ball"

19                android:layout_width="48dp"

20                android:layout_height="48dp"

21                android:layout_marginStart="12dp"

22                android:layout_marginTop="12dp"

23                app:layout_constraintStart_toStartOf="parent"

24                app:layout_constraintTop_toTopOf="parent"/>

25    </ConstraintSet>

26

27    <!-- Constraints to apply at the end of the animation -->

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

29        <Constraint

30                android:id="@+id/ball"

31                android:layout_width="48dp"

32                android:layout_height="48dp"

33                android:layout_marginEnd="12dp"

34                android:layout_marginBottom="12dp"

35                app:layout_constraintEnd_toEndOf="parent"

36                app:layout_constraintBottom_toBottomOf="parent"/>

37    </ConstraintSet>

38

39</MotionScene>

40复制代码

首先,可以发现我们定义了两个 <ConstraintSet> ,分别描述了这个? ImageView<Transition> 元素了。事实上,我们都知道,动画都是有开始位置和结束位置的,而 MotionLayoutPath

回到上面这个例子,我们只需要为 Transition 设置起始位置和结束位置的 ConstraintSet 并设置动画时间即可,剩下的都交给MotionLayout 自动去帮我们完成。当然你也可以通过 onClick 点击事件来触发动画,绑定目标控件的 id 以及通过clickAction 属性来设置点击事件的类型,这里我们设置的是toggle,即通过反复点击控件来切换动画的状态,其他还有很多属性可以参照官方文档去研究,比较简单,这里就不一一讲解它们的效果了。如此一来,运行一下就能看到上面的效果了。另外,为了方便测试,我们可以给MotionLayout 加上调试属性: app:motionDebug="SHOW_PATH" ,然后就能轻易的查看其动画内部的运动轨迹:

640?wx_fmt=gif

什么?你说这个动画效果太基础?那好,我就来个简陋版的“百花齐放”效果吧,比如下面这样:

640?wx_fmt=gif

首先,让我们分析一下这个效果:仔细看我们可以发现,通过向上滑动蓝色的 Android

1<?xml version="1.0" encoding="utf-8"?>

2<android.support.constraint.motion.MotionLayout

3        xmlns:android="schemas.android.com/apk/res/and…"

4        xmlns:tools="schemas.android.com/tools"

5        xmlns:app="schemas.android.com/apk/res-aut…"

6        android:layout_width="match_parent"

7        android:layout_height="match_parent"

8        app:motionDebug="SHOW_PATH"

9        app:layoutDescription="@xml/step2"

10        tools:context=".practice.MotionSampleActivity">

11    <ImageView

12            android:id="@+id/ic_android_blue"

13            android:layout_width="42dp"

14            android:layout_height="42dp"

15            android:src="@mipmap/android_icon_blue"/>

16    <ImageView

17            android:id="@+id/ic_android_left"

18            android:layout_width="42dp"

19            android:layout_height="42dp"

20            android:src="@mipmap/android_icon_purple"/>

21    <ImageView

22            android:id="@+id/ic_android_right"

23            android:layout_width="42dp"

24            android:layout_height="42dp"

25            android:src="@mipmap/android_icon_orange"/>

26    <TextView

27            android:id="@+id/tipText"

28            android:text="Swipe the blue android icon up"

29            android:layout_width="wrap_content"

30            android:layout_height="wrap_content"

31            app:layout_constraintEnd_toEndOf="parent"

32            android:layout_marginEnd="16dp"

33            android:layout_marginTop="16dp"

34            app:layout_constraintTop_toTopOf="parent"/>

35</android.support.constraint.motion.MotionLayout>

36复制代码

下面我们来看下 step2 中的 MotionScene:

1<?xml version="1.0" encoding="utf-8"?>

2<!--describe the animation for activity_motion_sample_step2.xml-->

3<!--animate by dragging target view-->

4<MotionScene xmlns:android="schemas.android.com/apk/res/and…"

5             xmlns:app="schemas.android.com/apk/res-aut…">

6    <!--At the start, all three stars are centered at the bottom of the screen.-->

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

8        <Constraint

9                android:id="@+id/ic_android_blue"

10                android:layout_width="42dp"

11                android:layout_height="42dp"

12                android:layout_marginBottom="20dp"

13                app:layout_constraintStart_toStartOf="parent"

14                app:layout_constraintEnd_toEndOf="parent"

15                app:layout_constraintBottom_toBottomOf="parent"/>

16        <Constraint

17                android:id="@+id/ic_android_left"

18                android:layout_width="42dp"

19                android:layout_height="42dp"

20                android:alpha="0.0"

21                android:layout_marginBottom="20dp"

22                app:layout_constraintStart_toStartOf="parent"

23                app:layout_constraintEnd_toEndOf="parent"

24                app:layout_constraintBottom_toBottomOf="parent"/>

25        <Constraint

26                android:id="@+id/ic_android_right"

27                android:layout_width="42dp"

28                android:layout_height="42dp"

29                android:layout_marginBottom="20dp"

30                android:alpha="0.0"

31                app:layout_constraintStart_toStartOf="parent"

32                app:layout_constraintEnd_toEndOf="parent"

33                app:layout_constraintBottom_toBottomOf="parent"/>

34    </ConstraintSet>

35

36    <!--Define the end constraint to set use a chain to position all three stars together below @id/tipText.-->

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

38        <Constraint

39                android:id="@+id/ic_android_left"

40                android:layout_width="58dp"

41                android:layout_height="58dp"

42                android:layout_marginEnd="90dp"

43                android:alpha="1.0"

44                app:layout_constraintHorizontal_chainStyle="packed"

45                app:layout_constraintStart_toStartOf="parent"

46                app:layout_constraintEnd_toStartOf="@id/ic_android_blue"

47                app:layout_constraintTop_toBottomOf="@id/tipText"/>

48        <Constraint

49                android:id="@+id/ic_android_blue"

50                android:layout_width="58dp"

51                android:layout_height="58dp"

52                app:layout_constraintEnd_toStartOf="@id/ic_android_right"

53                app:layout_constraintStart_toEndOf="@id/ic_android_left"

54                app:layout_constraintTop_toBottomOf="@id/tipText"/>

55        <Constraint

56                android:id="@+id/ic_android_right"

57                android:layout_width="58dp"

58                android:layout_height="58dp"

59                android:layout_marginStart="90dp"

60                android:alpha="1.0"

61                app:layout_constraintStart_toEndOf="@id/ic_android_blue"

62                app:layout_constraintEnd_toEndOf="parent"

63                app:layout_constraintTop_toBottomOf="@id/tipText"/>

64    </ConstraintSet>

65    <!-- A transition describes an animation via start and end state -->

66    <Transition

67            app:constraintSetStart="@id/start"

68            app:constraintSetEnd="@id/end">

69        <!-- MotionLayout will track swipes relative to this view -->

70        <OnSwipe app:touchAnchorId="@id/ic_android_blue"/>

71    </Transition>

72</MotionScene>

73复制代码

上面代码其实很好理解,之前我们定义了一个控件的 Constraint,现在只需要多加两个即可。由于三个 AndroidConstraintLayout 的基础,就不多说了。接着将结束位置的左、右 Android 机器人透明度设置为MotionLayout 会自动处理目标控件 alpha 属性的变化效果,让其看起来依旧丝滑。

另外,我们这里没有再通过 <OnClick> 来触发动画效果,类似的,我们使用了 <OnSwipe> 手势滑动来触发动画,只需要指定touchAnchorId 为蓝色小机器人即可,怎么样,是不是有种“拍案惊奇”的感觉?。此外,你可以通过指定 touchAnchorSide 和dragDirection

到这里,你可能会说:前面两个示例的动画轨迹一直是"直线",如果想要某段动画过程的轨迹是"曲线"效果可以吗?当然没问题! Keyframes

KeyFrameSet:让动画独树一帜


如果我们想实现“独树一帜”的动画交互效果,那就离不开 KeyFrameSet

640?wx_fmt=gif

以大家的慧眼不难发现:风车的运动轨迹为曲线,并且旋转并放大至中间位置时会达到零界点,然后开始缩小。布局代码就不上了,很简单,里面唯一重要的就是我们需要实现的step3.xml 了:

1<?xml version="1.0" encoding="utf-8"?>

2<!--describe the animation for activity_motion_sample_step3.xml-->

3<!--animate in the path way on a view-->

4<MotionScene xmlns:android="schemas.android.com/apk/res/and…"

5             xmlns:app="schemas.android.com/apk/res-aut…">

6    <!-- Constraints to apply at the start of the animation -->

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

8

9        <Constraint

10                android:id="@id/windmill"

11                android:layout_width="40dp"

12                android:layout_height="40dp"

13                android:layout_marginStart="12dp"

14                android:layout_marginBottom="12dp"

15                app:layout_constraintStart_toStartOf="parent"

16                app:layout_constraintBottom_toBottomOf="parent"/>

17

18        <Constraint

19                android:id="@id/tipText"

20                android:layout_width="wrap_content"

21                android:layout_height="wrap_content"

22                android:alpha="0.0"

23                app:layout_constraintStart_toStartOf="parent"

24                app:layout_constraintBottom_toBottomOf="@id/windmill"

25                app:layout_constraintTop_toTopOf="@id/windmill"/>

26    </ConstraintSet>

27    <!-- Constraints to apply at the end of the animation -->

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

29        <!--this view end point should be at bottom of parent-->

30        <Constraint

31                android:id="@id/windmill"

32                android:layout_width="40dp"

33                android:layout_height="40dp"

34                android:layout_marginBottom="12dp"

35                android:layout_marginEnd="12dp"

36                app:layout_constraintEnd_toEndOf="parent"

37                app:layout_constraintBottom_toBottomOf="parent"/>

38        <Constraint

39                android:id="@+id/tipText"

40                android:layout_width="wrap_content"

41                android:layout_height="wrap_content"

42                android:layout_marginBottom="12dp"

43                android:alpha="1.0"

44                android:layout_marginEnd="72dp"

45                app:layout_constraintEnd_toEndOf="parent"

46                app:layout_constraintBottom_toBottomOf="parent"/>

47    </ConstraintSet>

48

49    <!-- A transition describes an animation via start and end state -->

50    <Transition

51            app:constraintSetStart="@id/start"

52            app:constraintSetEnd="@id/end">

53

54        <KeyFrameSet>

55            <KeyPosition

56                    app:framePosition="50"

57                    app:motionTarget="@id/windmill"

58                    app:keyPositionType="parentRelative"

59                    app:percentY="0.5"/>

60            <!--apply other animation attributes-->

61            <!--前半段的动画效果:逆时针旋转一圈,同时放大一倍-->

62            <KeyAttribute

63                    app:motionTarget="@id/windmill"

64                    android:rotation="-360"

65                    android:scaleX="2.0"

66                    android:scaleY="2.0"

67                    app:framePosition="50"/>

68            <!--后半段的动画效果:逆时针旋转一圈,同时变回原样-->

69            <KeyAttribute

70                    app:motionTarget="@id/windmill"

71                    android:rotation="-720"

72                    app:framePosition="100"/>

73            <!--延迟动画——0-85过程中将透明度一直维持在0.0-->

74            <KeyAttribute

75                    app:motionTarget="@id/tipText"

76                    app:framePosition="85"

77                    android:alpha="0.0"/>

78        </KeyFrameSet>

79

80        <OnSwipe

81            app:touchAnchorId="@id/windmill"

82            app:touchAnchorSide="bottom"

83            app:dragDirection="dragRight"/>

84    </Transition>

85

86</MotionScene>

87复制代码

从上述代码我们可以发现:KeyFrameSet 需要被包含在 Transition 里面,同时 KeyFrameSet 中定义了_<KeyPosition>_ 和 <KeyAttribute> 两种元素,它们主要用来设置动画某个位置的关键帧,进而为某段动画指定所期望的效果 。顾名思义,KeyPosition 用于指定动画某个关键帧的位置信息,而 KeyAttribute``KeyFrameSet 中还支持 <KeyCycle> 和_<KeyTimeCycle>_ 来让动画变得更加有趣和灵活,因篇幅有限,将在后续文章对二者进行讲解。

我们先来看下 KeyPosition 的构成:

640?wx_fmt=png

从上图可见,keyPositionType 一共有三种,本文使用的是 parentRelative,即以整个 MotionLayout``framePosition 属性来 指定关键帧所在的位置 ,取值范围为 0 - 100,本示例中设置的 50``percentX 和 percentY 来设置该关键帧位置的偏移量,它们取值一般为 0 — 1,当然也可以设置为负数或者大于一,比如,本示例中如果没有设置偏移量,那么动画的轨迹无疑是一条平行于 x 轴的直线,但通过设置app:percentY="0.5",那么风车就会在动画中点位置向 y 轴方向偏移一半的高度,即下图的效果(开始 debug 模式):

640?wx_fmt=png

可能会有人问了:为什么轨迹不是三角形,而是曲线呢?哈哈,这个问题问得好!因为 MotionLayout