MotionLayout

2023-10-23 13:40
文章标签 motionlayout

本文主要是介绍MotionLayout,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1 MotionLayout的背景
    • 1.1 动画的演进
    • 1.2 属性动画与过渡动画的对比
    • 1.3 过渡动画的限制
    • 1.4 ConstraintLayout场景过渡
    • 1.5 MotionLayout场景过渡
  • 2 MotionLayout属性配置及使用示例
    • 2.1 MotionScene
    • 2.2 Transition
      • 2.2.1 OnClick
      • 2.2.2 OnSwipe
      • 2.2.3 KeyFrameSet
        • 2.2.3.1 KeyAttribute
        • 2.2.3.2 KeyPosition
        • 2.2.3.3 KeyPositionType坐标系
        • 2.2.3.4 KeyCycle
        • 2.2.3.5 KeyTimeCycle
    • 2.3 ConstraintSet
      • 2.3.1 Constarint
      • 2.3.2 Layout
      • 2.3.3 Motion
      • 2.3.4 CustomAttribute
      • 2.3.5 Transform

1 MotionLayout的背景

在了解 MotionLayout 之前,建议先了解 ConstriantLayout 的一些基本使用,因为 MotionLayoutConstraintLayout 的子类,参考文章:ConstraintLayout的基本使用。

MotionLayout 作为 ConstraintLayout 的子类,既然是子类自然就是对父类功能的一些增强和优化,ConstraintLyout 已经将布局等相关的工作已经做完了,那么 MotionLayout 是干嘛的?顾名思义,Motion 是运动的意思,ConstriantLayout 是静态布局,而 MotionLayout 自然就是可以让布局“运动”起来的布局。

MotionLayout 可以控制布局控件之间实现动画联动,它是一个动画框架,所使用的动画为过渡动画。过渡动画又是什么?接下来慢慢细说。

1.1 动画的演进

目前的动画主要分为三种:View动画、属性动画、过渡动画。

View动画 Animation 是在API 1很早就已经出现,但现在也基本比较少使用它们,因为使用属性动画也可以完全实现同样的效果。比如 AlphaAnimationScaleAnimation 等。

属性动画是在API 11时出现,是目前最常见和使用的动画,可以很方便的实现控件的透明度、平移、缩放、旋转等动画效果。

过渡动画是在API 18时引入,它主要是用于在两种场景或两种状态之间的切换。

1.2 属性动画与过渡动画的对比

先用属性动画实现一个简单的例子:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid:id="@+id/image"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@mipmap/ic_launcher"tools:ignore="ContentDescription" />
</FrameLayout>public class ObjectAnimatorActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_object_animator);final View root = findViewById(R.id.root);final ImageView image = findViewById(R.id.image);image.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {int distance = root.getWidth() - image.getWidth();image.animate().translationX(distance).start();}});}
}

在这里插入图片描述
功能非常简单,点击时使用属性动画将ImageView从左边移动到右边。

使用属性动画实现这个动画经过了两个步骤:

  • 计算图片从左边移动到右边的距离

  • 创建属性动画执行动画

如果我们想直接通过改变ImageView的属性实现这个效果会是怎样的?

FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) image.getLayoutParams();
lp.gravity = Gravity.END;
image.setLayoutParams(lp);

在这里插入图片描述

我们修改了ImageView的 gravity 属性,但是这样是没有动画效果会直接闪现过去,我们再加上一句代码:

// 添加这句代码,在修改控件参数之前
TransitionManager.beginDelayedTransition((ViewGroup) image.getParent());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) image.getLayoutParams();
lp.gravity = Gravity.END;
image.setLayoutParams(lp);

运行程序可以发现实现效果和属性动画一样有了平移动画的效果。通过加上一句 TransitionManager.beginDelayedTransiton() 就实现了过渡动画的平移效果,从一个状态过渡到另一个状态。

如果单从这个例子说明过渡动画更方便,说服力还是不够,属性动画能实现而且也可以做更多复杂的动画,还是比过渡动画好用。那过渡动画还有哪些是属性动画不具备的?过渡动画可以改变控件的属性,属性动画不能。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/image1"android:layout_width="100dp"android:layout_height="100dp"android:background="@mipmap/ic_launcher"tools:ignore="ContentDescription" /><ImageViewandroid:id="@+id/image2"android:layout_width="100dp"android:layout_height="100dp"android:layout_margin="5dp"android:background="@mipmap/ic_launcher"tools:ignore="ContentDescription" /><ImageViewandroid:id="@+id/image3"android:layout_width="100dp"android:layout_height="100dp"android:background="@mipmap/ic_launcher"tools:ignore="ContentDescription" />
</LinearLayout>// 属性动画
final ImageView image = findViewById(R.id.image2);
image.animate().scaleX(2.0f).scaleY(2.0f).start();// 过渡动画
TransitionManager.beginDelayedTransition((ViewGroup) image.getParent());
ViewGroup.LayoutParams lp = image.getLayoutParams();
lp.width *= 2;
lp.height *= 2;
image.setLayoutParams(lp);

属性动画效果如下:
在这里插入图片描述
过渡动画效果如下:
在这里插入图片描述

过渡动画的实现本质可以简单分为两个步骤:

  • 定义两个场景之间的过渡:肯定有开始场景结束场景,会记录两个场景控件的各种参数

  • 创建动画并执行动画:有了上一步记录的控件参数,就可以创建执行动画的参数

1.3 过渡动画的限制

还是先上一个过渡动画的示例:

在这里插入图片描述

具体示例代码如下:

activity_film.xml<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/film_start_scene" />
</android.support.constraint.ConstraintLayout>film_start_scene.xml<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><Viewandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="200dp"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/image_film_cover"android:layout_width="120dp"android:layout_height="wrap_content"android:layout_margin="16dp"android:adjustViewBounds="true"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="@+id/background"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/background" /><TextViewandroid:id="@+id/text_film_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:paddingVertical="8dp"android:textColor="@android:color/white"android:textSize="14sp"app:layout_constraintStart_toEndOf="@id/image_film_cover"app:layout_constraintTop_toTopOf="@id/image_film_cover" /><RatingBarandroid:id="@+id/rating_film_rating"style="?attr/ratingBarStyleSmall"android:layout_width="wrap_content"android:layout_height="wrap_content"android:numStars="5"android:paddingVertical="8dp"android:progressTint="#FFD600"app:layout_constraintStart_toStartOf="@+id/text_film_title"app:layout_constraintTop_toBottomOf="@id/text_film_title" /><TextViewandroid:id="@+id/film_description_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="16dp"android:text="@string/file_description_title"android:textColor="@color/colorPrimary"android:textSize="16sp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/background" /><TextViewandroid:id="@+id/text_film_description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingHorizontal="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/film_description_title" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:src="@drawable/ic_bookmark"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="@id/background"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toBottomOf="@id/background" />
</merge>film_end_scene.xml<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><Viewandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/image_film_cover"android:layout_width="0dp"android:layout_height="0dp"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/text_film_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:paddingVertical="8dp"android:textColor="@android:color/white"android:textSize="14sp"app:layout_constraintStart_toEndOf="@id/image_film_cover"app:layout_constraintTop_toTopOf="@id/image_film_cover" /><RatingBarandroid:id="@+id/rating_film_rating"style="?attr/ratingBarStyleSmall"android:layout_width="wrap_content"android:layout_height="wrap_content"android:numStars="5"android:paddingVertical="8dp"android:progressTint="#FFD600"app:layout_constraintStart_toStartOf="@+id/text_film_title"app:layout_constraintTop_toBottomOf="@id/text_film_title" /><TextViewandroid:id="@+id/film_description_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="16dp"android:text="@string/file_description_title"android:textColor="@color/colorPrimary"android:textSize="16sp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/background" /><TextViewandroid:id="@+id/text_film_description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingHorizontal="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/film_description_title" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_marginBottom="16dp"android:src="@drawable/ic_bookmark"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent" />
</merge>ic_bookmark.xml<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24.0"android:viewportHeight="24.0"><pathandroid:fillColor="@android:color/white"android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z" />
</vector>public class FilmActivity extends AppCompatActivity implements View.OnClickListener  {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_film);bindData();}private void bindData() {findViewById(R.id.image_film_cover).setOnClickListener(this);((RatingBar) findViewById(R.id.rating_film_rating)).setRating(4.5f);((TextView) findViewById(R.id.text_film_title)).setText(R.string.film_title);((TextView) findViewById(R.id.text_film_description)).setText(R.string.film_description);}private boolean toggle = true;@Overridepublic void onClick(View v) {ViewGroup root = findViewById(R.id.root);Scene startScene = Scene.getSceneForLayout(root, R.layout.film_start_scene, this);Scene endScene = Scene.getSceneForLayout(root, R.layout.film_end_scene, this);if (toggle) {TransitionManager.go(endScene);} else {TransitionManager.go(startScene);}// 需要重新绑定数据,否则点击后会无法切换场景// TransitionManager.go()切换场景后会将当前场景的控件全部移除替换为结束场景的控件对象// 两个场景的对象不一样,所以需要重新绑定// 虽然会重复的创建对象,但不需要过于担心性能问题bindData();toggle = !toggle;}
}

如果你的程序运行的是support而不是androidx,在使用 ic_bookmark 资源可能会编译失败,需要在 build.gradle 添加支持:

android {defaultConfig {vectorDrawables.useSupportLibrary = true}
}

如果使用上面讲解的 TransitionManager.beginDelayedTransition() 实现上面的处理将会是比较麻烦的事情,你需要在代码对控件各个参数属性进行修改。

这个示例将控件的开始场景和结束场景分别用 film_start_scene.xmlfilm_end_scene.xml 两个布局文件管理,再用 TransitionManager.go() 将场景装载。但缺点也比较明显:

  • 每次场景切换都会移除控件替换并需要重新绑定调用 bindData()

  • 场景之间有一些控件参数并不需要但还是要加上。比如结束场景最终展示只需要 FloatinActionButton 、封面 ImageView 和一个背景 View,但还是得加上开始场景那些控件,否则会抛出异常

有没有一种办法可以实现:既可以在xml添加修改属性管理,又可以不重复绑定添加控件?使用 ConstraintLayout 作为根布局可以解决这个问题。

1.4 ConstraintLayout场景过渡

activity_start_scene.xml<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><Viewandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="200dp"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/image_film_cover"android:layout_width="120dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="16dp"android:layout_marginEnd="16dp"android:layout_marginBottom="16dp"android:adjustViewBounds="true"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="@+id/background"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/background" /><TextViewandroid:id="@+id/text_film_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:paddingVertical="8dp"android:textColor="@android:color/white"android:textSize="14sp"app:layout_constraintStart_toEndOf="@id/image_film_cover"app:layout_constraintTop_toTopOf="@id/image_film_cover" /><RatingBarandroid:id="@+id/rating_film_rating"style="?attr/ratingBarStyleSmall"android:layout_width="wrap_content"android:layout_height="wrap_content"android:numStars="5"android:paddingVertical="8dp"android:progressTint="#FFD600"app:layout_constraintStart_toStartOf="@+id/text_film_title"app:layout_constraintTop_toBottomOf="@id/text_film_title" /><TextViewandroid:id="@+id/film_description_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="16dp"android:text="@string/file_description_title"android:textColor="@color/colorPrimary"android:textSize="16sp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/background" /><TextViewandroid:id="@+id/text_film_description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingHorizontal="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/film_description_title" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:src="@drawable/ic_bookmark_24dp"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="@id/background"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toBottomOf="@id/background" />
</android.support.constraint.ConstraintLayout>activity_end_scene.xml<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"><Viewandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/image_film_cover"android:layout_width="0dp"android:layout_height="0dp"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_marginBottom="16dp"android:src="@drawable/ic_bookmark_24dp"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent" /></android.support.constraint.ConstraintLayout>public class FilmActivity extends AppCompatActivity implements View.OnClickListener  {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_start_scene);findViewById(R.id.image_film_cover).setOnClickListener(this);((RatingBar) findViewById(R.id.rating_film_rating)).setRating(4.5f);((TextView) findViewById(R.id.text_film_title)).setText(R.string.film_title);((TextView) findViewById(R.id.text_film_description)).setText(R.string.film_description);}private boolean toggle = true;@Overridepublic void onClick(View v) {ConstraintLayout root = findViewById(R.id.root);TransitionManager.beginDelayedTransition(root);ConstraintSet constraintSet = new ConstraintSet();if (toggle) {constraintSet.clone(this, R.layout.activity_end_scene);} else {constraintSet.clone(this, R.layout.activity_start_scene);}constraintSet.applyTo(root);toggle = !toggle;}
}

实现的效果和上面使用 TransitionManager.go() 相同。

ConstraintLayout 通过 ConstraintSet.clone() 后调用 ConstraintSet.applyTo() 就可以实现场景的切换。虽然解决了上面提出的问题,但这种方式实现场景过渡还是不够完美:

  • 不能停留在任意位置,applyTo() 设置了结束场景后就只能执行完成

  • 不支持触摸反馈,根据手指的拖动伸缩

  • 定义的两个xml文件有很多重复的控件属性,需要两个xml布局文件放在layout目录管理

根据上面探讨的问题,能解决这些问题的动画框架 MotionLayout 就诞生了。

1.5 MotionLayout场景过渡

现在使用 MotionLayout 实现上面的效果。

activity_motion_layout.xml<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/film_motion"><Viewandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="200dp"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/image_film_cover"android:layout_width="120dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="16dp"android:layout_marginEnd="16dp"android:layout_marginBottom="16dp"android:adjustViewBounds="true"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="@+id/background"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/background" /><TextViewandroid:id="@+id/text_film_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:paddingVertical="8dp"android:textColor="@android:color/white"android:textSize="14sp"app:layout_constraintStart_toEndOf="@id/image_film_cover"app:layout_constraintTop_toTopOf="@id/image_film_cover" /><RatingBarandroid:id="@+id/rating_film_rating"style="?attr/ratingBarStyleSmall"android:layout_width="wrap_content"android:layout_height="wrap_content"android:numStars="5"android:paddingVertical="8dp"android:progressTint="#FFD600"app:layout_constraintStart_toStartOf="@+id/text_film_title"app:layout_constraintTop_toBottomOf="@id/text_film_title" /><TextViewandroid:id="@+id/film_description_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="16dp"android:text="@string/file_description_title"android:textColor="@color/colorPrimary"android:textSize="16sp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/background" /><TextViewandroid:id="@+id/text_film_description"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingHorizontal="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/film_description_title" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:src="@drawable/ic_bookmark_24dp"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="@id/background"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toBottomOf="@id/background" />
</android.support.constraint.motion.MotionLayout>

res/xml 目录定义 MotionLayout 使用的动画执行场景:

<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="200"><!--    设置点击封面执行过渡动画    --><OnClickapp:clickAction="toggle"app:targetId="@id/image_film_cover" /></Transition><!--  因为开始场景已经默认设置在setContentView(),这里不设置也关系不大  --><ConstraintSet android:id="@+id/start_scene"></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><!--   结束场景的控件,在MotionLayout只会认Constraint布局相关属性也可以写在<Constraint>标签下的<Layout>--><Constraintandroid:id="@+id/background"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Constraintandroid:id="@+id/image_film_cover"android:layout_width="0dp"android:layout_height="0dp"android:contentDescription="@null"android:src="@drawable/film_cover"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Constraintandroid:id="@+id/fab_favourite"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_marginBottom="16dp"android:src="@drawable/ic_bookmark_24dp"android:tint="#FFD600"app:fabSize="mini"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent" /></ConstraintSet>
</MotionScene>

除了可以通过点击外,还可以实现触摸反馈处理场景切换:

<!--    设置向右拖动封面执行过渡动画    -->
<OnSwipeapp:dragDirection="dragEnd"app:onTouchUp="autoComplete"app:touchAnchorId="@id/image_film_cover" />

在这里插入图片描述

跟随手指拖动停止:

<OnSwipeapp:dragDirection="dragEnd"app:onTouchUp="stop"app:touchAnchorId="@id/image_film_cover" />

在这里插入图片描述

2 MotionLayout属性配置及使用示例

MotionLayout 作为 ConstraintLayout 的子类,需要修改 ConstraintLayout 的依赖版本为2.0或以上才能使用:

implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta7'

关于 MotionLayout 这篇文章会讲解比较常用的一些属性参数,具体其他参数的使用和示例可以参考官网:MotionLayout、MotionLayout示例

2.1 MotionScene

MotionScene 定义 MotionLayout 布局动画执行场景等具体参数,作为xml文件的根元素,可以定义三个子元素:<Transition><ConstraintSet><StateSet>

<MotionScene><Transition/><ConstraintSet/><StateSet/>
</MotionScene>

2.2 Transition

Transition 定义一组过渡动画,支持修改动画执行期间布局控件的运动轨迹等。

<MotionScene><Transitionapp:constraintSetStart="@+id/start"app:constrantSetEnd="@+id/end"app:duration="1000"><OnClick/><OnSwipe/><KeyFrameSet><KeyAttribure/><KeyCycle/><KeyPosition/><KeyTimeCycle/></KeyFrameSet></Transition>
</MotionScene>
属性描述
constraintSetStart指定开始场景 ConstraintSet 的id或布局xml,即开始时的控件布局状态
constraintSetEnd指定结束场景 ConstraintSet 的id或布局xml,即结束时的控件布局状态
duration动画执行时间
motionInterpolator设置动画执行插值器
<?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"><!--  bounce实现回弹效果  --><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"app:motionInterpolator="bounce"><OnClickapp:clickAction="toggle"app:targetId="@id/view" /></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><Motion app:pathMotionArc="startHorizontal" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.2.1 OnClick

OnClick 设置在点击时触发执行过渡动画。

<MotionScene><Transition><OnClickapp:clickAction="toggle"app:targetId="@id/btn_toggle" /></Transition>
</MotionScene>
属性描述
clickAction设置点击按钮时动画的执行方式,toggletransitionToEndtransitionToStartjumpToEndjumpToStart
targetId设置点击执行动画的控件id

注意:如果一个控件已经定义了 <OnClick> 就不能去响应 <OnSwipe>

2.2.2 OnSwipe

OnSwipe 设置在触摸拖拽滑动时执行过渡动画。

<MotionScene><Transition><OnSwipeapp:dragDirection="dragStart"app:touchAnchorId="@+id/anchor_id"app:onTouchUp="autoComplete" /></Transition>
</MotionScene>
属性描述
dragDirection设置执行过渡动画的触摸拖拽方向,dragUpdragDowndragLeft/dragStartdragRight/dragEnd
touchAnchorId设置启动触摸拖拽动画的控件id
onTouchUp设置触摸松手后执行的场景状态,autoCompleteautoCompleteToStartautoCompleteToEndstopdecelaratedecelarateToComplete;当值为 autoComplete 时,如果触摸拖动松手时的状态靠近开始场景就会往开始场景的状态执行,否则就执行结束场景的状态;当值为 stop 表示跟随手指拖动的位置停在某个位置

注意:如果一个控件已经定义了 <OnClick> 就不能去响应 <OnSwipe>

2.2.3 KeyFrameSet

KeyFrameSet 定义控件在动画执行期间具体可以怎样移动,通过属性 <KeyAttribute><KeyCycle><KeyPosition><KeyTimeCycle> 设置。

<MotionScene><Transition><KeyFrameSet><KeyAttribute/><KeyCycle/><KeyPosition/><KeyTimeCycle/></KeyFrameSet></Transition>
</MotionScene>
2.2.3.1 KeyAttribute

KeyAttribute 控制动画执行期间在关键帧再处理特定的动画属性,也可以称为属性关键帧。

<MotionScene><Transition><KeyAttributeapp:framePosition="50"app:motionTarget="@id/view"app:rotation="180"><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@android:color/black" /></KeyAttribute></Transition>
</MotionScene>
属性描述
framePosition动画关键帧百分比,取值范围0-100。比如值为50,表示动画执行到50%的位置
motionTarget被处理的控件id
rotation设置view的旋转角度
rotationX设置view横坐标的旋转角度
rotationY设置view纵坐标的旋转角度
scaleX设置view水平方向的缩放
scaleY设置view垂直方向的缩放
translationX设置viewX轴平移
translationY设置viewY轴平移
translationZ设置viewZ轴平移

KeyAttribute 也支持在对应关键帧动态设置 CustomAttribute 修改控件属性,例如控件背景、文本颜色等等,这在后续节点说明该属性。

<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/view" /><KeyFrameSet><!--  实现动画执行到50%的时候旋转180度并且放大,然后恢复原样 --><KeyAttributeandroid:rotation="180"android:scaleX="2"android:scaleY="2"app:framePosition="50"app:motionTarget="@id/view" /></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><Motion app:pathMotionArc="startHorizontal" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.2.3.2 KeyPosition

KeyPosition 处理动画执行期间在某个关键帧移动位置,可以称为位置关键帧。

<MotionScene><Transition><KeyPositionapp:framePosition="50"app:keyPositionType="deltaRelative"app:motionTarget="@id/view"app:percentX="1"app:percentY="0" /></Transition>
</MotionScene>
属性描述
motionTarget被处理的控件id
framePosition动画关键帧百分比,取值范围0-100
keyPositionType指定关键帧使用的坐标系,有三种坐标系:parentRelativedeltaRelativepathRelative
percentX指定关键帧运动轨迹的水平相对位置,取值范围0-1;keyPositionType 选择不同的坐标系运动轨迹位置也有所不同
percentY指定关键帧运动轨迹的垂直相对位置,取值范围0-1;keyPositionType 选择不同的坐标系运动轨迹位置也有所不同
pathMotionArc修改所在关键帧的运动轨迹,startHorizontal 表示起始方向是水平方向;startVertical 表示起始方向是垂直方向,最终会让运动轨迹形成弧形;none 表示不处理运动轨迹为直线;flip 表示设置往后的运动轨迹与之前的相反
<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/view" /><KeyFrameSet><!-- 动画执行到50%时移动到相对于parent的中间位置、指定运动轨迹和之前的相反 需要注意的是,pathMotionArc要生效,控件的起始场景Constraint也必须设置pathMotionArc,否则会失效默认为直线--><KeyPositionapp:framePosition="50"app:keyPositionType="parentRelative"app:motionTarget="@id/view"app:pathMotionArc="flip"app:percentX="0.5"app:percentY="0.5" /></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><!-- 起始场景设置pathMotionArc,否则位置关键帧的pathMotionArc不生效  --><Motion app:pathMotionArc="startHorizontal" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.2.3.3 KeyPositionType坐标系

keyPositionType 指定不同的坐标系统,每一个关键帧 KeyPosition 都是独立的可以按需指定自己使用的坐标系统

  • parentRelative

该坐标系是让控件的位置关键帧相对于父容器,比如上面的例子就是相对于父容器 MotionLayout

在这里插入图片描述

  • deltaRelative

该坐标系是让控件的位置关键帧相对于开始位置和结束位置,开始位置的中心坐标是控件当前关键帧的中心点,结束位置的中心坐标是控件当前关键帧的中心点。

如果你想要控件在水平方向或垂直方向上移动,这个坐标系会非常有用,因为坐标系是相对于自身位置的,所以可以根据自身位置很方便拿到位置信息。

为了能够清晰看清坐标系的差别,将上面例子的结束位置调整到中间位置:

在这里插入图片描述

  • pathRelative

简单可以理解为该坐标系是通过开始位置和结束位置的长度定义到y轴的长度。

在这里插入图片描述
如果上面的不好理解,那我们将开始位置移动到中间位置:

在这里插入图片描述

使用三种坐标系演示一个示例:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/motion_layout"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/motion_scene_test"><TextViewandroid:id="@+id/a"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="A"android:textSize="24sp"android:textStyle="bold" /><TextViewandroid:id="@+id/b"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="B"android:textSize="24sp"android:textStyle="bold" /><TextViewandroid:id="@+id/c"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="C"android:textSize="24sp"android:textStyle="bold" /><Buttonandroid:id="@+id/btn_toggle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="toggle"android:textAllCaps="false" /><Buttonandroid:id="@+id/btn_show_debug"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="showPath"android:textAllCaps="false" />
</android.support.constraint.motion.MotionLayout>final MotionLayout motionLayout = findViewById(R.id.motion_layout);findViewById(R.id.btn_show_debug).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {motionLayout.setDebugMode(MotionLayout.DEBUG_SHOW_PATH);}
});<?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"><Transitionapp:constraintSetEnd="@+id/end"app:constraintSetStart="@+id/start"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/btn_toggle" /><KeyFrameSet><KeyPositionapp:framePosition="50"app:keyPositionType="deltaRelative"app:motionTarget="@id/a"app:percentX="1"app:percentY="0" /><KeyPositionapp:framePosition="50"app:keyPositionType="pathRelative"app:motionTarget="@id/b"app:percentX="0.5"app:percentY="-0.5" /><KeyPositionapp:framePosition="50"app:keyPositionType="parentRelative"app:motionTarget="@id/c"app:percentX="0.5"app:percentY="0.25" /></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start"><Constraintandroid:id="@+id/a"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="A"android:textSize="24sp"android:textStyle="bold"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@android:color/darker_gray" /></Constraint><Constraintandroid:id="@+id/b"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="B"android:textSize="24sp"android:textStyle="bold"app:layout_constraintBottom_toTopOf="@+id/c"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/a"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@android:color/darker_gray" /></Constraint><Constraintandroid:id="@+id/c"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="C"android:textSize="24sp"android:textStyle="bold"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/b"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@android:color/darker_gray" /></Constraint><Constraintandroid:id="@+id/btn_toggle"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@+id/btn_show_debug"app:layout_constraintRight_toRightOf="parent" /><Constraintandroid:id="@+id/btn_show_debug"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toRightOf="@+id/btn_toggle"app:layout_constraintRight_toRightOf="parent" /></ConstraintSet><ConstraintSet android:id="@+id/end"><Constraintandroid:id="@+id/a"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="A"android:textSize="24sp"android:textStyle="bold"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toLeftOf="@+id/b"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@color/colorPrimary" /></Constraint><Constraintandroid:id="@+id/b"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="B"android:textSize="24sp"android:textStyle="bold"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@color/colorAccent" /></Constraint><Constraintandroid:id="@+id/c"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="C"android:textSize="24sp"android:textStyle="bold"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toRightOf="@+id/b"app:layout_constraintTop_toTopOf="parent"><CustomAttributeapp:attributeName="textColor"app:customColorValue="@android:color/black" /></Constraint><Constraintandroid:id="@+id/btn_toggle"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@+id/btn_show_debug" /><Constraintandroid:id="@+id/btn_show_debug"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toRightOf="@+id/btn_toggle"app:layout_constraintRight_toRightOf="parent" /></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.2.3.4 KeyCycle

KeyCycle 属性可以指定在关键帧范围内执行重复动画。

属性描述
motionTarget被处理的控件id
framePosition动画关键帧百分比,取值范围0-100
wavePeriod循环次数,在指定的 framePosition 执行重复动画次数
waveShape振幅循环模型,比如 sincos
rotation设置view的旋转角度
rotationX设置view横坐标的旋转角度
rotationY设置view纵坐标的旋转角度
scaleX设置view水平方向的缩放
scaleY设置view垂直方向的缩放
translationX设置viewX轴平移
translationY设置viewY轴平移
translationZ设置viewZ轴平移

比如现在有一个需求:拖动一个View在执行到一半时左右各旋转45°。可以用 KeyAttribute 属性关键帧实现:

<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnSwipeapp:dragDirection="dragRight"app:touchAnchorId="@id/view" /><KeyFrameSet><KeyAttributeandroid:rotation="-45"app:framePosition="45"app:motionTarget="@id/view" /><KeyAttributeandroid:rotation="0"app:framePosition="50"app:motionTarget="@id/view"/><KeyAttributeandroid:rotation="45"app:framePosition="55"app:motionTarget="@id/view"/></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

上面需求比较简单,如果后续要求在执行到一半时循环旋转45°各10次,那就要写很多的 KeyAttribute。针对这种需求,完全可以通过 KeyCycle 实现:

<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnSwipeapp:dragDirection="dragRight"app:touchAnchorId="@id/view" /><KeyFrameSet><!--     KeyCycle定义的是一个区间第一个KeyCycle和最后一个KeyCycle是定义区间范围的中间的KeyCycle可以随意定义振幅循环次数       --><KeyCycleandroid:rotation="0"app:framePosition="0"app:motionTarget="@id/view"app:wavePeriod="0"app:waveShape="sin" /><KeyCycleandroid:rotation="45"app:framePosition="50"app:motionTarget="@id/view"app:wavePeriod="1"app:waveShape="sin" /><KeyCycleandroid:rotation="0"app:framePosition="100"app:motionTarget="@id/view"app:wavePeriod="0"app:waveShape="sin" /></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

使用 KeyCycle 指定 wavePeriod 就可以指定在对应关键帧动画循环次数。

在这里插入图片描述

2.2.3.5 KeyTimeCycle

KeyTimeCycleKeyCycle 一样也是处理重复动画,但不同在于它是以时间为度量来处理的。

<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnSwipeapp:dragDirection="dragRight"app:touchAnchorId="@id/view" /><KeyFrameSet><KeyTimeCycleandroid:rotation="0"app:framePosition="0"app:motionTarget="@id/view"app:wavePeriod="0"app:waveShape="sin" /><KeyTimeCycleandroid:rotation="45"app:framePosition="50"app:motionTarget="@id/view"app:wavePeriod="0.5"app:waveShape="sin" /><KeyTimeCycleandroid:rotation="0"app:framePosition="100"app:motionTarget="@id/view"app:wavePeriod="0"app:waveShape="sin" /></KeyFrameSet></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.3 ConstraintSet

ConstraintSet 指定开始场景和结束场景,对 ConstriantSet 指定id后在 <Transition> 通过 constraintSetStartconstraintSetEnd 设置一组过渡动画的场景切换。

2.3.1 Constarint

MotionLayout 配置文件中,可以将 Constraint 理解为在对应 ConstraintSet 场景下的View控件,只是在配置文件中是不会认ImageView或TextView等等,都用 Constraint 代替。

<MotionScene><ConstraintSet><Constraintandroid:id="@+id/view"android:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /></ConstraintSet>
</MotionScene>

2.3.2 Layout

LayoutConstraint 的子节点,主要是声明控件的布局属性。

<MotionScene><ConstraintSet><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /></Constraint></ConstraintSet>
</MotionScene>

相比直接将布局属性写在 Constraint 节点,使用 Layout 会更加合理。

2.3.3 Motion

MotionConstraint 的子节点,可以指定控件的运动轨迹,默认是直线运动轨迹,通过它可以很方便的将运动轨迹修改为曲线。

<MotionScene><ConstraintSet><Constraint><Motion app:pathMotionArc="startVertical"app:transitionEasing="decelerate" /></Constraint></ConstraintSet>
</MotionScene>
属性描述
pathMotionArc指定曲线运动轨迹方向,startHorizontal 表示起始方向是水平方向,startVertical 表示起始方向是垂直方向,最终会让运动轨迹形成弧形
transitionEasing指定动画运动的插值器,支持 accelaratedecelaratelinearstandard
  • startHorizontal
    在这里插入图片描述
  • startVertical
    在这里插入图片描述

2.3.4 CustomAttribute

CustomAttributeConstraint 的子节点,在该节点声明控件背景、文本颜色等其他自定义属性。

<MotionScene><ConstraintSet><Constraint><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@color/colorAccent" /></Constraint></ConstraintSet>
</MotionScene>
属性描述
attributeName设置要修改的控件属性,比如要调用控件的 setTextColor,则执行属性名 textColor
customColorValue修改颜色属性值
customIntegerValue修改int属性值
customFloatValue修改float属性值
customStringValue修改string属性值
customDimension修改dimension属性值
customBoolean修改boolean属性值
<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/view" /></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><Motionapp:pathMotionArc="startVertical"app:transitionEasing="decelerate" /><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@color/colorAccent" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@color/colorPrimary" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

2.3.5 Transform

TransformConstraint 的子节点,可以指定从开始场景到结束场景之间添加动画效果,比如缩放、旋转等。

<MotionScene><ConstraintSet><Constraint><Transformandroid:rotation="360"android:scaleX="2"android:scaleY="2" /></Constraint></ConstraintSet>
</MotionScene>
属性描述
rotation设置view的旋转角度
rotationX设置view横坐标的旋转角度
rotationY设置view纵坐标的旋转角度
scaleX设置view水平方向的缩放
scaleY设置view垂直方向的缩放
translationX设置viewX轴平移
translationY设置viewY轴平移
translationZ设置viewZ轴平移
<?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"><Transitionapp:constraintSetEnd="@id/end_scene"app:constraintSetStart="@id/start_scene"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/view" /></Transition><ConstraintSet android:id="@+id/start_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><Motionapp:pathMotionArc="startVertical"app:transitionEasing="decelerate" /><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@color/colorAccent" /></Constraint></ConstraintSet><ConstraintSet android:id="@+id/end_scene"><Constraint android:id="@+id/view"><Layoutandroid:layout_width="80dp"android:layout_height="80dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent" /><Transformandroid:rotation="360"android:scaleX="1.33"android:scaleY="1.33"android:translationZ="12dp" /><CustomAttributeapp:attributeName="backgroundColor"app:customColorValue="@color/colorPrimary" /></Constraint></ConstraintSet>
</MotionScene>

在这里插入图片描述

这篇关于MotionLayout的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/268217

相关文章

【Android】MotionLayout实现动画效果

【Android】MotionLayout实现开场动画 在移动应用开发中,动画不仅仅是美化界面的工具,它更是提升用户体验的关键手段。Android 平台一直以来都提供了丰富的动画框架,但随着应用复杂性的增加,开发者对动画的需求也变得更加复杂和多样化。为了更好地应对这些需求,Google 在 ConstraintLayout 的基础上推出了 MotionLayout。 效果展示 先来看看Mot

MotionLayout第一篇前言Android 最丝滑的动画--(后面陆续更新)(带效果图,视频)

前言 前端时间看到了一篇有关Android 的动画,这几天抽空看了一下,确实很丝滑,话不多说先看视频看界面 贴了几张图片,视频太模糊了,只能看一下交互 一、用到的一些技术 1、拟态 2、MotionLayout 这篇博客后面会有好几篇,内容逐渐深入 这个动画是谷歌推出的布局类型动画,可帮助开发者管理应用中的运动和微件动画。 下一篇开始写相关的博客,这段时间实在是太忙了

秒懂Android开发之MotionLayout简单上手

【版权申明】非商业目的注明出处可自由转载 博文地址:https://blog.csdn.net/ShuSheng0007/article/details/106182408 出自:shusheng007 文章目录 概述动画布局基本元素MotionLayoutMotionSceneConstraintSetTransition MotionLayout基础使用扩展总结 概述

秒懂Android开发之MotionLayout简单上手

【版权申明】非商业目的注明出处可自由转载 博文地址:https://blog.csdn.net/ShuSheng0007/article/details/106182408 出自:shusheng007 文章目录 概述动画布局基本元素MotionLayoutMotionSceneConstraintSetTransition MotionLayout基础使用扩展总结 概述

带你领略MotionLayout的魅力(中)

距离上一篇文章「 MotionLayout:打开动画新世界大门(part I)」已经过去了很久,由于个人原因,MotionLayout 系列文章姗姗来迟。在之前的文章中,我们领略到了 MotionLayout 的魅力,了解到它继承自 ConstraintLayout,并具有它“约束布局”的特性。同时,关于如何创建和使用 MotionScene 及其内部的 KeyFrameSet 也都做了一些简单介

用MotionLayout实现这些不可思议的效果

/   今日科技快讯   / 近日,据外媒报道,自从研发电动汽车的计划取消后,家电品牌戴森正对其产品路线图进行重组,承诺在人工智能、机器人和能源储存等领域进行投资。该公司表示,计划在未来5年投资27.5亿英镑(约合36.6亿美元)用于开发新技术和产品。 /   作者简介   / 大家周一好,这周开始,冬天就正式来临了,小伙伴们注意保暖哦~ 本篇来自knight康康的投稿,给大家讲解Motion