Android 动画
问题
Android 动画系统有哪些类型?属性动画的原理是什么?
答案
1. 动画类型概览
| 类型 | 说明 | API |
|---|---|---|
| 帧动画 | 逐帧播放图片序列 | AnimationDrawable |
| 补间动画 | 平移/缩放/旋转/透明度(仅改变绘制,不改变属性) | Animation |
| 属性动画 | 真正改变对象属性值 | ObjectAnimator / ValueAnimator |
| 转场动画 | Activity/Fragment 切换动画 | Transition |
| MotionLayout | ConstraintLayout 的动画子类 | MotionLayout |
| Lottie | 播放 AE 导出的 JSON 动画 | LottieAnimationView |
2. 属性动画(核心)
属性动画通过反射或直接调用 setter 方法不断改变对象的属性值来实现动画效果:
// ObjectAnimator - 直接操作属性
ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
duration = 500
interpolator = OvershootInterpolator()
start()
}
// ValueAnimator - 手动更新属性
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 300
addUpdateListener { animation ->
val value = animation.animatedValue as Float
view.alpha = value
view.scaleX = 1f + value * 0.2f
}
start()
}
// AnimatorSet - 组合动画
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.2f, 1f),
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.2f, 1f),
ObjectAnimator.ofFloat(view, "alpha", 1f, 0.8f, 1f)
)
duration = 400
start()
}
3. ViewPropertyAnimator(推荐简写)
// 链式调用,性能优于 ObjectAnimator(合并属性更新、减少反射)
view.animate()
.translationX(300f)
.alpha(0.5f)
.scaleX(1.5f)
.setDuration(500)
.setInterpolator(DecelerateInterpolator())
.withStartAction { /* 动画开始 */ }
.withEndAction { /* 动画结束 */ }
.start()
4. 常用 Interpolator
| 插值器 | 效果 |
|---|---|
LinearInterpolator | 匀速 |
AccelerateInterpolator | 加速 |
DecelerateInterpolator | 减速 |
AccelerateDecelerateInterpolator | 先加速后减速 |
OvershootInterpolator | 超出目标再回弹 |
BounceInterpolator | 弹跳效果 |
AnticipateInterpolator | 先后退再前进 |
5. 补间动画 vs 属性动画
| 特性 | 补间动画 | 属性动画 |
|---|---|---|
| 改变属性 | ❌ 仅改变绘制位置 | ✅ 真正改变属性值 |
| 点击区域 | 停留在原位 | 跟随动画移动 |
| 支持目标 | 仅 View | 任意对象 |
| API 级别 | API 1 | API 11+ |
补间动画的坑
使用补间动画平移 View 后,点击事件响应区域仍在原位。所以需要交互的 View 动画应使用属性动画。
6. Lottie 动画
// XML 中使用
// <com.airbnb.lottie.LottieAnimationView
// android:id="@+id/lottieView"
// app:lottie_rawRes="@raw/animation"
// app:lottie_autoPlay="true"
// app:lottie_loop="true" />
// 代码控制
binding.lottieView.apply {
setAnimation(R.raw.loading)
repeatCount = LottieDrawable.INFINITE
playAnimation()
}
// 监听进度
binding.lottieView.addAnimatorUpdateListener { animation ->
val progress = animation.animatedFraction
}
常见面试问题
Q1: 属性动画的原理是什么?
答案:
属性动画的核心是 ValueAnimator,它在每一帧通过以下步骤计算属性值:
- TimeInterpolator:根据已过时间比例,通过插值器计算时间因子(0~1 之间的非线性映射)
- TypeEvaluator:根据时间因子计算当前属性值(
startValue + fraction * (endValue - startValue)) - 反射调用 setter 或通过
AnimatorUpdateListener手动更新
底层通过 Choreographer 订阅 VSYNC 信号,在每一帧回调中更新动画值。
Q2: 如何实现一个弹性动画效果?
答案:
可以使用 SpringAnimation(AndroidX Dynamic Animation):
SpringAnimation(view, DynamicAnimation.TRANSLATION_X, 0f).apply {
spring.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
spring.stiffness = SpringForce.STIFFNESS_LOW
start()
}
也可以自定义 Interpolator 或使用 OvershootInterpolator。
Q3: 动画导致内存泄漏怎么办?
答案:
属性动画如果设置了无限循环(repeatCount = INFINITE),Activity 关闭时动画不会自动停止,导致 View 无法回收。
解决方案:在 onDestroy(Activity)或 onDestroyView(Fragment)中调用 animator.cancel()。Lottie 动画同理。
Q4: setTranslationX 和 setX 有什么区别?
答案:
translationX:相对于 View 布局位置的偏移量,初始值为 0x:View 在父容器中的实际位置,x = left + translationX
动画中通常使用 translationX,因为它不会改变 View 的 layout 位置。
Q5: MotionLayout 有什么优势?
答案:
MotionLayout 是 ConstraintLayout 的子类,它通过两组 ConstraintSet(起始状态和结束状态) 定义动画,支持:
- 拖拽驱动的动画(类似 iOS 的交互式转场)
- 关键帧(KeyFrameSet)控制中间过渡
- 与 CoordinatorLayout、ViewPager2 联动
- 可视化编辑器(Motion Editor)
适合复杂的页面过渡动画,如折叠 AppBar、引导页等。