1. 从ConstraintLayout到MotionLayout:你的第一个动画
大家好,我是老张,一个在Android开发里摸爬滚打了快十年的老码农。今天咱们不聊那些虚的架构设计,就聊聊怎么让咱们的App“动”起来,而且动得优雅、动得流畅。说到Android动画,你可能用过ViewPropertyAnimator,也折腾过ObjectAnimator,但有没有觉得,一旦动画逻辑复杂点,代码就变得又臭又长,维护起来简直是一场噩梦?
几年前,Google推出了MotionLayout,我当时第一反应是:又来一个新玩意儿?但真正用上之后,我只能说“真香”!它完美地解决了复杂动画中状态管理和过渡平滑性的问题。简单来说,MotionLayout是ConstraintLayout的子类,但它把动画的“起点”、“终点”以及“怎么动”都通过XML声明式地定义好了,代码逻辑瞬间清爽。
1.1 环境准备:两步接入MotionLayout
别怕,接入它比你想象中简单得多。总共就两步,咱们一步步来。
第一步:升级依赖 首先,确保你的build.gradle文件里,ConstraintLayout的版本在2.0.0以上。我写这篇文章时,最新稳定版已经是2.1.x了,你可以去官方仓库看看最新版本。
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
第二步:转换布局 你之前用ConstraintLayout写的布局文件,几乎可以无缝切换到MotionLayout。在Android Studio的布局预览界面或者Component Tree里,右键点击你的ConstraintLayout,选择 “Convert to MotionLayout”。

就这么一点,Android Studio会自动做两件事:
- 把你的根布局标签从
ConstraintLayout改成MotionLayout。 - 在
res/xml/目录下,为你创建一个同名的MotionScene文件(比如activity_main_scene.xml),并自动关联到布局。
这个MotionScene文件,就是MotionLayout的灵魂所在,所有动画的剧本都写在这里。
1.2 认识核心三要素:ConstraintSet, Transition, KeyFrameSet
打开自动生成的MotionScene文件,你会看到一个基础结构:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<OnClick />
<KeyFrameSet>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
</ConstraintSet>
</MotionScene>
我来给你翻译一下这三个核心“演员”:
- ConstraintSet(约束集):你可以把它理解为一个“快照”,保存了屏幕上所有视图在某一时刻的位置、大小、旋转角度、透明度等所有属性。一个动画至少需要两个快照:开始(
start)和结束(end)。 - Transition(过渡):它定义了动画从哪个
ConstraintSet(start)变化到哪个ConstraintSet(end),以及动画的持续时间(duration)。它还负责监听触发事件,比如点击(OnClick)或滑动(OnSwipe)。 - KeyFrameSet(关键帧集合):这是让动画脱离“两点一线”单调运动,变得丰富多彩的关键。它允许你在动画过程的中间点(比如50%进度时)插入额外的状态,让视图的路径或属性产生变化,比如让一个View先上移再下落,或者在中途旋转一圈。
理解这三者的关系,就掌握了MotionLayout的七成功力。剩下的三成,就是熟练运用它们。
2. 实战入门:创建一个平移动画
光说不练假把式,咱们立刻动手做一个最简单的方块平移动画。目标是让一个红色方块从屏幕左边平滑移动到右边。
2.1 定义视图和起始状态
首先,在activity_main.xml布局文件中,我们放置一个MotionLayout和一个作为动画主角的View(一个红色方块)。
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/a%3C/code%3E%3C/pre%3E%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/div%3E%20%20%20%20%20%20%20%20%3C/div%3E%20%20%20%20%3C/article%3E%20%20%3Cscript%3E%20%20%20%20$(function()%20%7B%20%20%20%20%20%20setTimeout(function%20()%20%7B%20%20%20%20%20%20%20%20var%20mathcodeList%20=%20document.querySelectorAll('.htmledit_views img.mathcode');
if (mathcodeList.length > 0) {
for (let i = 0; i < mathcodeList.length; i++) {
if (mathcodeList[i].complete) {
if (mathcodeList[i].naturalWidth === 0 || mathcodeList[i].naturalHeight === 0) {
var alt = mathcodeList[i].alt;
alt = '\\(' + alt + '\\)';
var curSpan = $('');
curSpan.text(alt);
$(mathcodeList[i]).before(curSpan);
$(mathcodeList[i]).remove();
}
} else {
mathcodeList[i].onerror = function() {
var alt = mathcodeList[i].alt;
alt = '\\(' + alt + '\\)';
var curSpan = $('');
curSpan.text(alt);
$(mathcodeList[i]).before(curSpan);
$(mathcodeList[i]).remove();
};
}
}
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
}
}, 500)
});

445

被折叠的 条评论
为什么被折叠?



