本文主要是介绍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
的一些基本使用,因为 MotionLayout
是 ConstraintLayout
的子类,参考文章:ConstraintLayout的基本使用。
MotionLayout
作为 ConstraintLayout
的子类,既然是子类自然就是对父类功能的一些增强和优化,ConstraintLyout
已经将布局等相关的工作已经做完了,那么 MotionLayout
是干嘛的?顾名思义,Motion
是运动的意思,ConstriantLayout
是静态布局,而 MotionLayout
自然就是可以让布局“运动”起来的布局。
MotionLayout
可以控制布局控件之间实现动画联动,它是一个动画框架,所使用的动画为过渡动画。过渡动画又是什么?接下来慢慢细说。
1.1 动画的演进
目前的动画主要分为三种:View动画、属性动画、过渡动画。
View动画 Animation
是在API 1很早就已经出现,但现在也基本比较少使用它们,因为使用属性动画也可以完全实现同样的效果。比如 AlphaAnimation
、ScaleAnimation
等。
属性动画是在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.xml
和 film_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 | 设置点击按钮时动画的执行方式,toggle 、transitionToEnd 、transitionToStart 、 jumpToEnd 、jumpToStart |
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 | 设置执行过渡动画的触摸拖拽方向,dragUp 、dragDown 、dragLeft/dragStart 、dragRight/dragEnd |
touchAnchorId | 设置启动触摸拖拽动画的控件id |
onTouchUp | 设置触摸松手后执行的场景状态,autoComplete 、autoCompleteToStart 、autoCompleteToEnd 、stop 、decelarate 、decelarateToComplete ;当值为 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 | 指定关键帧使用的坐标系,有三种坐标系:parentRelative 、deltaRelative 和 pathRelative |
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 | 振幅循环模型,比如 sin 、cos |
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
KeyTimeCycle
和 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><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>
通过 constraintSetStart
和 constraintSetEnd
设置一组过渡动画的场景切换。
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
Layout
是 Constraint
的子节点,主要是声明控件的布局属性。
<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
Motion
是 Constraint
的子节点,可以指定控件的运动轨迹,默认是直线运动轨迹,通过它可以很方便的将运动轨迹修改为曲线。
<MotionScene><ConstraintSet><Constraint><Motion app:pathMotionArc="startVertical"app:transitionEasing="decelerate" /></Constraint></ConstraintSet>
</MotionScene>
属性 | 描述 |
---|---|
pathMotionArc | 指定曲线运动轨迹方向,startHorizontal 表示起始方向是水平方向,startVertical 表示起始方向是垂直方向,最终会让运动轨迹形成弧形 |
transitionEasing | 指定动画运动的插值器,支持 accelarate 、decelarate 、linear 、standard |
- startHorizontal
- startVertical
2.3.4 CustomAttribute
CustomAttribute
是 Constraint
的子节点,在该节点声明控件背景、文本颜色等其他自定义属性。
<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
Transform
是 Constraint
的子节点,可以指定从开始场景到结束场景之间添加动画效果,比如缩放、旋转等。
<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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!