动画与过渡
Android 动画体系:View 补间动画(alpha/scale/translate/rotate)、属性动画(ObjectAnimator/ValueAnimator/AnimatorSet)、Transition API 共享元素、MotionLayout 入门与动画性能注意事项。
学习目标
- 掌握 View 补间动画四类与 XML 资源定义
- 理解属性动画原理并熟练使用 ObjectAnimator 与 ValueAnimator
- 能够用 AnimatorSet 组合动画
- 了解 Transition API 与共享元素过渡
- 了解 MotionLayout 入门用法与动画性能注意事项
学习目标
- 掌握 View 补间动画四类与 XML 资源定义;
- 理解属性动画原理并熟练使用
ObjectAnimator与ValueAnimator; - 能够用
AnimatorSet组合动画; - 了解 Transition API 与共享元素过渡;
- 了解 MotionLayout 入门用法与动画性能注意事项。
动画与过渡
Android 动画体系经过多年演进,目前主要分为三大类:View 补间动画(Tween Animation)、属性动画(Property Animation) 与 Transition / Motion。三者层次与适用场景不同,掌握它们的边界是写出流畅动画的前提。
三种动画体系对比
| 体系 | 引入版本 | 改变什么 | 是否影响真实属性 | 性能 |
|---|---|---|---|---|
| View 补间动画 | API 1 | View 的绘制 | 否(仅视觉) | 中 |
| 属性动画 | API 11(Honeycomb) | 对象属性 | 是 | 中高 |
| Transition / Motion | API 21 / MotionLayout 1.0 | 场景过渡 | 是 | 高 |
View 补间动画
补间动画通过给出“起始值”和“结束值”,由系统插值出中间帧。支持四种基本变换:alpha(透明度)、scale(缩放)、translate(平移)、rotate(旋转)。
XML 定义动画
res/anim/fade_in.xml:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300"
android:interpolator="@android:interpolator/accelerate_decelerate" />
res/anim/scale_in.xml:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.5"
android:toXScale="1.0"
android:fromYScale="0.5"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="300" />
res/anim/set_combo.xml:用 <set> 组合多种变换:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300" />
<translate
android:fromXDelta="0%"
android:toXDelta="0%"
android:fromYDelta="-20%"
android:toYDelta="0%"
android:duration="300" />
</set>
代码加载与启动
public class FadeActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fade);
imageView = findViewById(R.id.iv_logo);
Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.fade_in);
imageView.startAnimation(fadeIn);
}
}
也可以纯代码构建:
AlphaAnimation alpha = new AlphaAnimation(0f, 1f);
alpha.setDuration(300);
alpha.setInterpolator(new AccelerateDecelerateInterpolator());
imageView.startAnimation(alpha);
补间动画的局限
补间动画 只改变 View 的绘制,并不改变 View 的真实位置和属性:
- 一个 Button 平移后,点击区域仍在原位置;
- 不支持 3D 变换;
- 不支持动画背景色等非绘制属性。
这就是属性动画被引入的原因。
属性动画
属性动画通过 不断调用对象 setter 改变属性真实值。核心类是 ValueAnimator(计算器)与 ObjectAnimator(自动 setter 调用器)。
ValueAnimator:手动取值
ValueAnimator 在指定时长内,按插值器和类型评估器,从起始值过渡到结束值:
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(300);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
imageView.setAlpha(value);
imageView.setScaleX(value);
});
animator.start();
ObjectAnimator:自动绑定
ObjectAnimator 通过反射调用目标对象的 setter(如 setAlpha、setTranslationX),更简洁:
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
alpha.setDuration(300);
alpha.start();
// 同时多个属性
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.5f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.5f, 1f);
View 提供了 animate() 链式 API,背后就是 ObjectAnimator:
imageView.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(300)
.setInterpolator(new DecelerateInterpolator())
.start();
PropertyValuesHolder:单对象多属性
PropertyValuesHolder pvhAlpha = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f);
PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1f);
ObjectAnimator.ofPropertyValuesHolder(imageView, pvhAlpha, pvhScaleX, pvhScaleY)
.setDuration(300)
.start();
这种方式比创建多个 ObjectAnimator 更高效,内部只注册一个动画回调。
插值器与估值器
- 插值器(Interpolator):决定时间流逝比例到“动画进度”的映射。
LinearInterpolator匀速;AccelerateDecelerateInterpolator两端慢中间快;DecelerateInterpolator先快后慢;AnticipateOvershootInterpolator超出再回弹。 - 估值器(TypeEvaluator):决定从起始值到结束值的过渡算法。
FloatEvaluator/IntEvaluator是默认;ArgbEvaluator用于颜色过渡:
ObjectAnimator colorAnim = ObjectAnimator.ofObject(view, "backgroundColor",
new ArgbEvaluator(),
Color.parseColor("#FF5722"),
Color.parseColor("#3F51B5"));
colorAnim.setDuration(800);
colorAnim.start();
AnimatorSet:组合编排
AnimatorSet 用于将多个 Animator 按顺序或同时执行:
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
ObjectAnimator moveUp = ObjectAnimator.ofFloat(view, "translationY", 0f, -100f);
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
AnimatorSet set = new AnimatorSet();
set.play(fadeOut).before(fadeIn); // fadeOut 先于 fadeIn
set.playTogether(fadeOut, moveUp); // fadeOut 和 moveUp 同时
set.setDuration(300);
set.start();
也可以用 playSequentially:
AnimatorSet set = new AnimatorSet();
set.playSequentially(fadeOut, fadeIn);
set.start();
监听器
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Toast.makeText(MainActivity.this, "动画结束", Toast.LENGTH_SHORT).show();
}
});
用 XML 定义属性动画
res/animator/scale_with_alpha.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:propertyName="alpha"
android:duration="300"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
<objectAnimator
android:propertyName="scaleX"
android:duration="300"
android:valueFrom="0.5"
android:valueTo="1"
android:valueType="floatType" />
</set>
加载:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.scale_with_alpha);
set.setTarget(imageView);
set.start();
Transition API 与共享元素
Transition API(API 21+)用于描述“场景变化”,框架自动捕获前后两个 Scene 的属性差异并做动画。
Activity 过渡
启用过渡动画:
<!-- styles.xml -->
<style name="AppTheme" parent="Theme.Material3.DayNight">
<item name="android:windowActivityTransitions">true</item>
</style>
在调用方 Activity 中:
// 给共享 View 设置 transitionName
View sharedView = findViewById(R.id.iv_avatar);
sharedView.setTransitionName("avatar");
Intent intent = new Intent(this, DetailActivity.class);
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, sharedView, "avatar");
startActivity(intent, options.toBundle());
在被调用方 Activity 中,给对应的 View 设置相同的 transitionName:
<ImageView
android:id="@+id/iv_avatar"
android:transitionName="avatar"
android:layout_width="200dp"
android:layout_height="200dp" />
自定义 Transition
public class ChangeColor extends Transition {
private static final String PROPNAME_BACKGROUND = "android:changeColor:background";
@Override
public void captureStartValues(TransitionValues values) {
values.values.put(PROPNAME_BACKGROUND,
((View) values.view).getBackground());
}
@Override
public void captureEndValues(TransitionValues values) {
values.values.put(PROPNAME_BACKGROUND,
((View) values.view).getBackground());
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues start, TransitionValues end) {
if (start == null || end == null) return null;
// 简化示例:背景色 Argb 过渡
return ObjectAnimator.ofObject((View) end.view, "backgroundColor",
new ArgbEvaluator(), Color.RED, Color.BLUE);
}
}
MotionLayout 入门
MotionLayout 是 ConstraintLayout 子类,专为“复杂状态过渡”设计。它通过一个布局文件 + 一个场景文件(MotionScene)描述两个状态之间的动画。
依赖
dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
}
布局文件
res/layout/motion_scene_start.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.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">
<ImageView
android:id="@+id/iv_ball"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/ic_ball"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
场景文件
res/xml/motion_scene.xml:
<?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">
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/iv_ball">
<Transform
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@id/iv_ball">
<Transform
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</Constraint>
</ConstraintSet>
<Transition
app:constraintSetStart="@id/start"
app:constraintSetEnd="@id/end"
app:duration="500"
app:motionInterpolator="easeInOut">
<OnClick app:targetId="@id/iv_ball"
app:clickAction="toggle" />
</Transition>
</MotionScene>
代码控制过渡:
MotionLayout motionLayout = findViewById(R.id.motion_layout);
// 跳到 end 状态
motionLayout.transitionToEnd();
// 跳回 start 状态
motionLayout.transitionToStart();
性能注意事项
动画是性能问题的重灾区。以下几个原则务必遵守:
- 优先硬件层:复杂动画对目标 View 调用
setLayerType(View.LAYER_TYPE_HARDWARE, null)(或view.animate().withLayer()),让 GPU 缓存绘制结果。动画结束后关闭,避免额外内存:
imageView.animate()
.alpha(0f)
.scaleX(0.5f)
.scaleY(0.5f)
.setDuration(300)
.withLayer() // 期间使用硬件层
.withEndAction(() -> {
// 完成后清理
});
- 避免每帧布局:
translationX/Y、scaleX/Y、rotationX/Y、alpha走的是 GPU 合成,不会触发重新布局(onMeasure/onLayout);而改width、height、margin会导致每帧布局,性能差很多。能用translationX就不要改left/right。 - 慎用
requestLayout/invalidate:自定义 View 动画中频繁触发重新测量会导致掉帧。 - 节流属性动画监听:
addUpdateListener里只更新必要的 View,不要在每帧分配新对象。 - 不要在动画中做 IO:数据库、网络操作都不应在动画回调中执行。
常见坑与最佳实践
- 补间动画后点击位置不变:补间动画不改变真实位置。需要响应新位置的应用
ObjectAnimator.ofFloat(view, "translationX", ...)或直接改x/y。 - ObjectAnimator setter 不存在:反射找不到 setter 会静默失效。给自定义属性务必提供
setName方法,或使用PropertyValuesHolder.ofObject+TypeEvaluator配合View.set*。 - animation 没设置插值器时按线性:人眼对线性动画感知生硬,默认建议
DecelerateInterpolator或AccelerateDecelerateInterpolator。 - 硬件层未释放:
LAYER_TYPE_HARDWARE会占用 GPU 内存。短暂动画结束后应恢复LAYER_TYPE_NONE,除非该 View 持续有动画。 - MotionScene 中属性覆盖了 layout:MotionLayout 的
ConstraintSet中约束会覆盖原 layout 中的layout_*属性,让人误以为布局没生效。两者要协调一致。 - 大量动画共用一个监听器分配大对象:在
addUpdateListener内new对象会造成 GC 抖动,应预先创建并复用。 - 在
onPause中忘记取消动画:Activity 销毁时仍在跑的 Animator 应调用cancel(),避免持有 Context 导致泄漏。 - 对
BackgroundColor用补间动画:补间动画不支持颜色变化,必须用属性动画 +ArgbEvaluator。
章节小结
本章覆盖了 Android 三大动画体系:View 补间动画(alpha/scale/translate/rotate,仅改变绘制)、属性动画(ValueAnimator / ObjectAnimator / AnimatorSet,真实修改属性、性能与表达力更强)、Transition 与共享元素过渡(API 21+ 场景驱动动画)、MotionLayout 入门(ConstraintLayout 子类,用 MotionScene 描述状态过渡)。性能要点:优先 GPU 友好属性(translation*、scale*、rotation*、alpha),合理使用硬件层,避免每帧布局与对象分配。
下一章预告
下一章将进入深色模式与主题领域:Material 主题、颜色资源定义、DayNight 主题(Theme.Material3.DayNight)、Force Dark、Android 12+ 动态颜色(Dynamic Color),以及主题切换的持久化方案。