Android 动画学习
一、Android 动画的作用
动画不是单纯为了“好看”,它更多是为了让用户理解界面变化。
比如:
按钮点击后有缩放反馈,用户知道自己点中了;
页面跳转时有过渡动画,用户知道自己从哪里来到哪里;
列表项展开时有高度变化,用户知道内容是“展开”出来的;
加载动画可以缓解等待焦虑;
拖拽、滑动、弹性回弹可以增强真实感。
所以 Android 动画的核心目的可以总结为:
让界面变化更自然,让用户更容易理解状态变化。
二、Android 动画体系总览
Android 动画大体可以分为两套体系:
如果是传统 View 项目,最重要的是 Property Animation。
如果是新项目使用 Jetpack Compose,最重要的是 Compose Animation。
三、View Animation:早期补间动画
View Animation 是 Android 早期的动画系统,也叫补间动画。
所谓补间动画,就是你只需要告诉系统:
开始状态;
结束状态;
动画时间;
动画变化方式。
然后系统会自动计算中间过程。
常见类型有:
示例:
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0"
android:toAlpha="1"
android:duration="300" />
代码使用:
val animation = AnimationUtils.loadAnimation(context, R.anim.fade_in)
view.startAnimation(animation)
View Animation 的问题
View Animation 最大的问题是:
它改变的是 View 的“显示效果”,不一定改变 View 的真实属性。
比如一个按钮从左边平移到了右边,但它真实的点击区域可能还在原来的位置。
这就是为什么后来 Android 更推荐使用属性动画。
四、Property Animation:属性动画
Property Animation 是 Android 动画中最核心的一套机制。
它的思想是:
在一段时间内,不断修改某个对象的属性值。
例如:
修改
translationX,让 View 水平移动;修改
alpha,让 View 渐隐渐显;修改
scaleX/scaleY,让 View 缩放;修改
rotation,让 View 旋转;修改自定义对象的属性,实现业务动画。
属性动画常用类:
五、ValueAnimator
ValueAnimator 是属性动画的基础。
它本身不关心你要改哪个 View,也不直接修改属性。它只负责在指定时间内产生一系列变化中的值。
示例:
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = 300
animator.addUpdateListener { animation ->
val value = animation.animatedValue as Float
view.alpha = value
}
animator.start()
这段代码的含义是:
从
0f变化到1f;动画持续 300 毫秒;
每一帧回调一次;
手动把当前值设置给
view.alpha。
所以 ValueAnimator 更底层,也更灵活。
它适合用在:
自定义 View;
自定义绘制;
非 View 对象动画;
需要自己控制每一帧变化的场景。
六、ObjectAnimator
ObjectAnimator 是 ValueAnimator 的子类,但它更方便。
它不仅会计算动画值,还会自动调用对象的 setter 方法修改属性。
示例:
ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
duration = 300
start()
}
这段代码会自动修改:
view.setAlpha(value)
再比如平移动画:
ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
duration = 500
start()
}
常见可动画属性:
注意:
ObjectAnimator 依赖属性名和 setter 方法。
比如:
ObjectAnimator.ofFloat(obj, "progress", 0f, 100f)
对象里最好有:
fun setProgress(value: Float)
或者 Kotlin 属性:
var progress: Float = 0f
七、AnimatorSet:组合动画
有时候一个动画不是单一变化,而是多个动画组合。
比如一个按钮先缩小,再放大,同时透明度变化。
这时可以使用 AnimatorSet。
示例:
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.8f, 1f)
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.8f, 1f)
val alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0.5f, 1f)
AnimatorSet().apply {
playTogether(scaleX, scaleY, alpha)
duration = 300
start()
}
常见方法:
八、Interpolator:插值器
Interpolator 用来控制动画的速度变化。
例如:
匀速;
先慢后快;
先快后慢;
回弹;
超出再回来。
它解决的问题不是“值从哪里到哪里”,而是:
动画过程中的速度怎么变化。
常见插值器:
示例:
ObjectAnimator.ofFloat(view, "translationY", 0f, 500f).apply {
duration = 600
interpolator = DecelerateInterpolator()
start()
}
一个简单理解:
Animator 决定“数值怎么变”,Interpolator 决定“变快还是变慢”。
九、TypeEvaluator:估值器
Interpolator 决定时间进度,TypeEvaluator 决定具体数值。
比如动画从 0 到 100,当前进度是 50%,那么结果通常是 50。
但是如果动画对象不是普通数字,而是颜色、坐标点、自定义对象,就需要 TypeEvaluator 计算中间值。
例如颜色动画:
val animator = ValueAnimator.ofArgb(Color.RED, Color.BLUE)
animator.duration = 500
animator.addUpdateListener {
view.setBackgroundColor(it.animatedValue as Int)
}
animator.start()
自定义对象时,可以实现自己的 TypeEvaluator。
十、ViewPropertyAnimator
如果只是对 View 做简单动画,可以用 view.animate()。
示例:
view.animate()
.translationX(300f)
.alpha(0.5f)
.setDuration(300)
.start()
它的优点是写法简单,链式调用,非常适合简单 View 动画。
常见用法:
view.animate()
.scaleX(1.2f)
.scaleY(1.2f)
.setDuration(200)
.withEndAction {
view.animate()
.scaleX(1f)
.scaleY(1f)
.start()
}
.start()
实际开发中,如果只是做 View 的透明度、缩放、平移,ViewPropertyAnimator 非常好用。
十一、Drawable Animation:帧动画
帧动画就是一张一张图片连续播放,类似 GIF。
XML 示例:
<!-- res/drawable/loading_anim.xml -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/loading_1"
android:duration="100" />
<item
android:drawable="@drawable/loading_2"
android:duration="100" />
<item
android:drawable="@drawable/loading_3"
android:duration="100" />
</animation-list>
代码启动:
imageView.setBackgroundResource(R.drawable.loading_anim)
val animation = imageView.background as AnimationDrawable
animation.start()
帧动画的问题是:
图片多时占内存;
不够灵活;
不适合复杂交互动画。
现在很多场景会使用 Lottie、AnimatedVectorDrawable 或 Compose 动画替代。
十二、Transition Animation:过渡动画
Transition 主要用于布局变化。
它关心的是:
从一个界面状态切换到另一个界面状态时,中间过程如何过渡。
常见场景:
View 显示 / 隐藏;
View 大小变化;
View 位置变化;
Activity 共享元素动画;
Fragment 切换动画。
简单示例:
TransitionManager.beginDelayedTransition(rootView)
view.visibility = View.GONE
执行后,布局变化不会突然发生,而是带有过渡效果。
常见 Transition:
Transition 适合处理“布局状态变化”,而不是单个属性的精细控制。
十三、MotionLayout
MotionLayout 是 ConstraintLayout 的子类,适合做复杂 UI 状态过渡。
它可以理解为:
用声明式方式描述两个布局状态之间的动画。
例如:
折叠头部;
AppBar 联动;
卡片展开;
引导页复杂动效;
复杂手势拖拽动画。
MotionLayout 的核心概念:
MotionLayout 的优点是适合复杂动画,但缺点是学习成本较高。
普通透明度、平移、缩放,不一定需要 MotionLayout。
十四、Physics Animation:物理动画
普通动画更多是“时间驱动”。
比如:
300ms 内从 A 到 B。
物理动画更像是“力驱动”。
比如:
弹簧回弹;
惯性滑动;
拖拽释放后的自然运动。
常见 API:
示例:
val springAnim = SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0f)
springAnim.spring.stiffness = SpringForce.STIFFNESS_LOW
springAnim.spring.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
springAnim.start()
物理动画适合做:
下拉回弹;
卡片拖拽;
滑动释放;
弹性按钮;
手势跟随动画。
十五、Jetpack Compose 动画
如果项目使用 Jetpack Compose,动画思路和传统 View 不太一样。
传统 View 动画通常是:
找到 View,然后修改它的属性。
Compose 动画通常是:
状态变化后,UI 根据状态自动重组,动画负责让状态变化变得平滑。
也就是说,Compose 动画更强调“状态驱动”。
十六、animate*AsState
animate*AsState 是 Compose 中最常用、最简单的动画 API。
它适合一个状态值的平滑变化。
示例:
@Composable
fun LikeButton(selected: Boolean) {
val scale by animateFloatAsState(
targetValue = if (selected) 1.2f else 1f,
label = "scale"
)
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null,
modifier = Modifier.scale(scale)
)
}
当 selected 改变时,scale 不会瞬间变成目标值,而是平滑过渡。
常见 API:
十七、AnimatedVisibility
AnimatedVisibility 用来处理显示和隐藏动画。
示例:
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Text("Hello Android")
}
它适合:
弹窗内容出现;
列表项显示 / 隐藏;
错误提示显示;
空状态页面切换;
卡片展开内容。
十八、AnimatedContent
AnimatedContent 用来处理内容切换。
比如数字变化、状态页面切换。
示例:
AnimatedContent(
targetState = count,
label = "count animation"
) { targetCount ->
Text(text = "Count: $targetCount")
}
适合场景:
数字滚动变化;
Tab 内容切换;
加载 / 成功 / 失败状态切换;
页面局部状态切换。
十九、Transition
如果多个动画值要根据同一个状态一起变化,可以使用 Transition。
示例:
val transition = updateTransition(
targetState = expanded,
label = "card transition"
)
val height by transition.animateDp(label = "height") {
if (it) 200.dp else 80.dp
}
val alpha by transition.animateFloat(label = "alpha") {
if (it) 1f else 0.5f
}
它适合:
卡片展开;
多个属性同步变化;
状态机式动画;
稍复杂的 Compose 动画。
二十、InfiniteTransition
InfiniteTransition 用来做无限循环动画。
比如:
loading 动画;
呼吸灯;
骨架屏闪烁;
雷达扫描;
波纹扩散。
示例:
val infiniteTransition = rememberInfiniteTransition(label = "loading")
val alpha by infiniteTransition.animateFloat(
initialValue = 0.3f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(800),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
二十一、传统 View 动画和 Compose 动画对比
简单理解:
View 动画是“我命令这个 View 动起来”。
Compose 动画是“状态变了,界面自然过渡过去”。
二十二、动画性能优化
动画性能最核心的目标是:
尽量避免卡顿,保证每一帧都能按时绘制。
常见优化建议:
1. 优先使用 transform 类属性
例如:
translationXtranslationYscaleXscaleYrotationalpha
这些属性通常比频繁改布局参数更适合动画。
不推荐频繁动画修改:
widthheightmarginpadding复杂布局约束
因为这些可能触发布局重新测量和重新布局。
2. 避免在动画回调里做重活
不要在 addUpdateListener 里做:
大量计算;
网络请求;
数据库操作;
创建大量对象;
复杂集合遍历。
动画回调可能每一帧都会执行,里面的代码越重,越容易掉帧。
3. 控制动画时长
常见 UI 动画时长:
动画不是越长越高级,太长反而会让用户觉得慢。
4. 生命周期中及时取消动画
View 被销毁后,如果动画还在执行,可能导致:
内存泄漏;
无效回调;
页面关闭后动画继续执行;
状态错乱。
可以在合适位置取消:
animator.cancel()
view.animate().cancel()
在 RecyclerView 中尤其要注意 item 复用问题。
5. RecyclerView 中要小心动画
RecyclerView 的 item 会复用,如果动画状态没有重置,可能出现:
某些 item 透明度异常;
某些 item 缩放异常;
滚动回来状态不对;
动画重复执行。
绑定数据时最好明确设置初始状态:
holder.itemView.alpha = 1f
holder.itemView.scaleX = 1f
holder.itemView.scaleY = 1f
二十三、常见业务场景怎么选动画方案
二十四、面试常问点
1. View Animation 和 Property Animation 有什么区别?
View Animation 改变的是 View 的显示效果,不一定改变真实属性。
Property Animation 改变的是对象的真实属性。
比如一个按钮通过 View Animation 平移后,看起来位置变了,但点击区域可能还在原地。Property Animation 修改 translationX 等真实属性,表现和交互更一致。
2. ValueAnimator 和 ObjectAnimator 有什么区别?
ValueAnimator 只负责产生动画值,不会自动修改对象。
ObjectAnimator 会在产生动画值的同时,自动修改目标对象的属性。
所以:
需要完全自定义控制时,用
ValueAnimator;只是修改某个对象属性时,用
ObjectAnimator。
3. Interpolator 和 TypeEvaluator 有什么区别?
Interpolator 决定动画进度的变化速度。
TypeEvaluator 决定根据当前进度计算出什么值。
简单说:
Interpolator 管时间曲线。
TypeEvaluator 管数值计算。
4. 为什么动画会卡顿?
常见原因:
主线程任务太重;
动画回调里做复杂计算;
频繁 requestLayout;
布局层级太深;
图片过大;
创建大量临时对象导致 GC;
RecyclerView item 动画状态没有处理好。
5. 为什么推荐属性动画?
因为属性动画更灵活,可以作用于任何对象,不局限于 View。
它也能真正改变对象属性,更适合现代 Android UI 开发。
二十五、总结
Android 动画可以按开发方式分成两类:
第一类是传统 View 动画:
View Animation:早期补间动画;
Property Animation:最核心,最常用;
Drawable Animation:帧动画;
Transition:布局过渡;
MotionLayout:复杂状态动画;
Physics Animation:物理动画。
第二类是 Compose 动画:
animate*AsState:单个状态动画;AnimatedVisibility:显示隐藏动画;AnimatedContent:内容切换动画;Transition:多个属性同步动画;InfiniteTransition:无限循环动画。
学习 Android 动画时,不要只记 API,更重要的是理解:
动画的本质是状态变化的可视化。
好动画不是炫技,而是让用户更容易理解界面变化。
Android 动画学习
https://lautung.com/archives/9OZLL2es