跳到主要内容

Android 动画

问题

Android 动画系统有哪些类型?属性动画的原理是什么?

答案

1. 动画类型概览

类型说明API
帧动画逐帧播放图片序列AnimationDrawable
补间动画平移/缩放/旋转/透明度(仅改变绘制,不改变属性)Animation
属性动画真正改变对象属性值ObjectAnimator / ValueAnimator
转场动画Activity/Fragment 切换动画Transition
MotionLayoutConstraintLayout 的动画子类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 1API 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,它在每一帧通过以下步骤计算属性值:

  1. TimeInterpolator:根据已过时间比例,通过插值器计算时间因子(0~1 之间的非线性映射)
  2. TypeEvaluator:根据时间因子计算当前属性值(startValue + fraction * (endValue - startValue)
  3. 反射调用 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: setTranslationXsetX 有什么区别?

答案

  • translationX:相对于 View 布局位置的偏移量,初始值为 0
  • x:View 在父容器中的实际位置,x = left + translationX

动画中通常使用 translationX,因为它不会改变 View 的 layout 位置。

Q5: MotionLayout 有什么优势?

答案

MotionLayout 是 ConstraintLayout 的子类,它通过两组 ConstraintSet(起始状态和结束状态) 定义动画,支持:

  • 拖拽驱动的动画(类似 iOS 的交互式转场)
  • 关键帧(KeyFrameSet)控制中间过渡
  • 与 CoordinatorLayout、ViewPager2 联动
  • 可视化编辑器(Motion Editor)

适合复杂的页面过渡动画,如折叠 AppBar、引导页等。

相关链接