ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

android 动画详解

android 动画详解

Android 动画详解:从原理到实战

Android 动画体系经历了从传统 View 动画 → 属性动画 → 过渡动画 → MotionLayout → Jetpack Compose 的演进。以下从分类、原理、API、实战四个维度系统梳理。


一、动画分类总览

Android 动画按实现方式可分为四大类:

类别包名适用场景特点
View 动画(补间动画)android.view.animation简单 UI 动效只改变绘制位置,不改变实际属性
帧动画(Drawable 动画)android.graphics.drawable加载动画、序列帧逐帧切换图片,内存占用高
属性动画android.animation任意对象属性动画真正改变对象属性,功能最强大
过渡动画android.transitionActivity/Fragment 切换场景切换间的连贯体验
MotionLayoutandroidx.constraintlayout.motion复杂交互动效基于 ConstraintLayout 的声明式动画
Compose 动画androidx.compose.animation现代 UI 开发声明式、状态驱动、代码简洁

二、View 动画(补间动画)

⚠️已不推荐:View 动画只能作用于 View,且只改变绘制位置,不改变实际属性(如按钮移动后点击区域仍在原处)。新项目请优先使用属性动画。

2.1 四种基本变换

动画类型类名属性
平移TranslateAnimationfromXDelta,toXDelta,fromYDelta,toYDelta
缩放ScaleAnimationfromXScale,toXScale,fromYScale,toYScale
旋转RotateAnimationfromDegrees,toDegrees,pivotX,pivotY
透明度AlphaAnimationfromAlpha,toAlpha

2.2 XML 定义

xml

<!-- res/anim/scale_rotate.xml --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:fillAfter="true" android:interpolator="@android:anim/accelerate_decelerate_interpolator"> <scale android:fromXScale="1.0" android:toXScale="2.0" android:fromYScale="1.0" android:toYScale="2.0" android:pivotX="50%" android:pivotY="50%" /> <rotate android:fromDegrees="0" android:toDegrees="360" android:pivotX="50%" android:pivotY="50%" /> </set>

2.3 代码调用

kotlin

val animation = AnimationUtils.loadAnimation(context, R.anim.scale_rotate) view.startAnimation(animation)

三、帧动画(Drawable 动画)

将一系列 Drawable 按时间间隔逐帧显示,适合简单的加载动画。

xml

<!-- res/drawable/loading_animation.xml --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/frame1" android:duration="100" /> <item android:drawable="@drawable/frame2" android:duration="100" /> <item android:drawable="@drawable/frame3" android:duration="100" /> </animation-list>

kotlin

val imageView: ImageView = findViewById(R.id.imageView) imageView.setImageResource(R.drawable.loading_animation) val animationDrawable = imageView.drawable as AnimationDrawable animationDrawable.start()

⚠️注意:帧动画会预加载所有帧到内存,图片过多或过大时容易造成 OOM。建议用 Lottie 替代复杂帧动画。


四、属性动画(Property Animation)

属性动画是 Android 3.0(API 11)引入的动画框架,真正修改对象的属性值,功能强大且灵活。

4.1 核心类

表格

作用
ValueAnimator动画引擎,计算动画值,不直接操作对象
ObjectAnimatorValueAnimator子类,直接操作目标对象的属性
AnimatorSet组合多个动画,控制执行顺序
Interpolator插值器,定义动画变化速率
TypeEvaluator估值器,定义属性值的计算方式

4.2 ValueAnimator

kotlin

val animator = ValueAnimator.ofFloat(0f, 100f).apply { duration = 1000 interpolator = AccelerateDecelerateInterpolator() addUpdateListener { animation -> val value = animation.animatedValue as Float // 手动将计算值应用到目标对象 view.translationX = value } } animator.start()

4.3 ObjectAnimator(最常用)

kotlin

// 平移 ObjectAnimator.ofFloat(view, "translationX", 0f, 200f).apply { duration = 1000 start() } // 旋转 ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply { duration = 1000 start() } // 透明度 ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f).apply { duration = 1500 start() } // 背景色变化(需要 ArgbEvaluator) ObjectAnimator.ofInt(view, "backgroundColor", Color.RED, Color.BLUE).apply { duration = 2000 setEvaluator(ArgbEvaluator()) start() }

4.4 可动画的 View 属性

属性说明
translationX/translationY相对于 left/top 的偏移
x/y最终位置(left + translationX)
rotation/rotationX/rotationY2D/3D 旋转
scaleX/scaleY缩放
pivotX/pivotY旋转/缩放的中心点
alpha透明度
width/height宽高(需配合 wrapper)

4.5 AnimatorSet 组合动画

kotlin

val fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f) val mover = ObjectAnimator.ofFloat(view, "translationX", -500f, 0f) val fadeIn = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f) AnimatorSet().apply { play(mover).with(fadeIn).after(fadeOut) // fadeOut 先执行,然后 mover 和 fadeIn 同时执行 duration = 2000 start() }

4.6 ViewPropertyAnimator(链式调用)

kotlin

view.animate() .translationX(200f) .translationY(100f) .rotation(360f) .alpha(0.5f) .setDuration(1000) .setInterpolator(BounceInterpolator()) .withLayer() // 开启硬件层加速 .withStartAction { /* 动画开始前 */ } .withEndAction { /* 动画结束后 */ } .start()

ViewPropertyAnimator内部优化了多个属性同时动画的性能,比多个ObjectAnimator更高效。

4.7 插值器(Interpolator)

插值器效果
LinearInterpolator匀速
AccelerateInterpolator加速
DecelerateInterpolator减速
AccelerateDecelerateInterpolator先加速后减速
BounceInterpolator弹跳效果
OvershootInterpolator冲过头再回弹
AnticipateInterpolator先回退再前进
AnticipateOvershootInterpolator先回退,冲过头再回弹

4.8 XML 声明属性动画

xml

<!-- res/animator/property_animator.xml --> <set android:ordering="sequentially"> <objectAnimator android:propertyName="x" android:duration="500" android:valueTo="400" android:valueType="intType" /> <objectAnimator android:propertyName="alpha" android:duration="500" android:valueTo="1f" /> </set>

kotlin

val set = AnimatorInflater.loadAnimator(context, R.animator.property_animator) as AnimatorSet set.setTarget(view) set.start()

五、过渡动画(Transition)

5.1 Activity 过渡动画

kotlin

// 传统方式(API 21 以下兼容) val intent = Intent(this, TargetActivity::class.java) startActivity(intent) overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)

5.2 共享元素过渡(Shared Element)

kotlin

// Activity A val options = ActivityOptions.makeSceneTransitionAnimation( this, sharedImageView, "shared_image" // transitionName 必须匹配 ) startActivity(intent, options.toBundle()) // Activity B 的 layout 中 <ImageView android:id="@+id/sharedImageView" android:transitionName="shared_image" ... />

5.3 Fragment 过渡

kotlin

val fragmentB = FragmentB() fragmentB.sharedElementEnterTransition = TransitionInflater.from(context) .inflateTransition(android.R.transition.move) supportFragmentManager.beginTransaction() .replace(R.id.container, fragmentB) .addSharedElement(sharedView, "shared_name") .commit()

六、MotionLayout

MotionLayout 是 ConstraintLayout 的子类,通过声明式 XML 定义复杂动画,是传统 View 系统中处理复杂交互动画的最佳选择

6.1 核心概念

概念说明
MotionScene动画场景描述文件
ConstraintSet定义布局的起始/结束状态
Transition定义两个状态之间的过渡
KeyFrameSet关键帧,控制中间状态
OnClick/OnSwipe触发方式

6.2 基本示例

xml

<!-- res/layout/activity_main.xml --> <androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/scene"> <View android:id="@+id/box" android:layout_width="64dp" android:layout_height="64dp" android:background="@color/purple" /> </androidx.constraintlayout.motion.widget.MotionLayout>

xml

<!-- res/xml/scene.xml --> <MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- 起始状态 --> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/box" android:layout_width="64dp" android:layout_height="64dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <!-- 结束状态 --> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/box" android:layout_width="64dp" android:layout_height="64dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </ConstraintSet> <!-- 过渡定义 --> <Transition app:constraintSetStart="@id/start" app:constraintSetEnd="@id/end" app:duration="1000"> <OnClick app:targetId="@id/box" app:clickAction="toggle" /> </Transition> </MotionScene>

6.3 关键帧(KeyFrame)

xml

<Transition ...> <KeyFrameSet> <!-- 中间位置关键帧:动画 50% 时位于屏幕中心 --> <KeyPosition app:framePosition="50" app:motionTarget="@id/box" app:keyPositionType="parentRelative" app:percentX="0.5" app:percentY="0.5" /> <!-- 中间属性关键帧:50% 时旋转 180 度 --> <KeyAttribute app:framePosition="50" app:motionTarget="@id/box" android:rotation="180" /> </KeyFrameSet> </Transition>

6.4 代码控制

kotlin

motionLayout.transitionToState(R.id.end) // 过渡到结束状态 motionLayout.transitionToStart() // 回到起始状态 motionLayout.progress = 0.5f // 设置进度(0~1) motionLayout.setTransition(R.id.start, R.id.end) // 设置过渡

七、Jetpack Compose 动画(推荐)

2026 年,Jetpack Compose 已成为 Android UI 开发的默认方式,其动画 API 声明式、简洁且功能强大。

7.1 状态驱动动画

kotlin

@Composable fun AnimatedBox() { var expanded by remember { mutableStateOf(false) } // 尺寸动画 val size by animateDpAsState( targetValue = if (expanded) 120.dp else 60.dp, animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing), label = "size" ) // 颜色动画 val color by animateColorAsState( targetValue = if (expanded) Color.Green else Color.Blue, label = "color" ) Box( modifier = Modifier .size(size) .background(color, CircleShape) .clickable { expanded = !expanded } ) }

7.2 可见性动画

kotlin

var visible by remember { mutableStateOf(true) } AnimatedVisibility( visible = visible, enter = fadeIn() + slideInHorizontally(), exit = fadeOut() + slideOutHorizontally() ) { Text("Hello Compose!") }

7.3 内容切换动画

kotlin

var selected by remember { mutableStateOf(false) } Crossfade(targetState = selected, label = "crossfade") { state -> when (state) { true -> Icon(Icons.Default.Check, null) false -> Icon(Icons.Default.Close, null) } }

7.4 内容变更动画

kotlin

var count by remember { mutableIntStateOf(0) } AnimatedContent( targetState = count, transitionSpec = { if (targetState > initialState) { slideInVertically { it } + fadeIn() togetherWith slideOutVertically { -it } + fadeOut() } else { slideInVertically { -it } + fadeIn() togetherWith slideOutVertically { it } + fadeOut() } }, label = "count" ) { targetCount -> Text(text = "$targetCount", fontSize = 48.sp) }

7.5 无限循环动画

kotlin

val infiniteTransition = rememberInfiniteTransition(label = "pulse") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 1.5f, animationSpec = infiniteRepeatable( animation = tween(1000), repeatMode = RepeatMode.Reverse ), label = "scale" ) Box( modifier = Modifier .size(100.dp) .scale(scale) .background(Color.Red, CircleShape) )

7.6 手势驱动动画

kotlin

val offset = remember { Animatable(Offset.Zero, Offset.VectorConverter) } Box( modifier = Modifier .offset { offset.value.toIntOffset() } .size(100.dp) .background(Color.Blue) .pointerInput(Unit) { detectDragGestures( onDrag = { change, dragAmount -> change.consume() scope.launch { offset.snapTo(offset.value + dragAmount) } }, onDragEnd = { scope.launch { // 弹簧回弹到原点 offset.animateTo( targetValue = Offset.Zero, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ) ) } } ) } )

7.7 共享元素过渡(Compose)

kotlin

SharedTransitionLayout { AnimatedContent( targetState = selectedItem, transitionSpec = { (fadeIn() togetherWith fadeOut()).using(SizeTransform(clip = false)) } ) { item -> if (item == null) { // 列表页 LazyColumn { items(items) { photo -> Image( painter = rememberAsyncImagePainter(photo.url), contentDescription = null, modifier = Modifier .sharedElement( state = rememberSharedContentState(key = photo.id), animatedVisibilityScope = this@AnimatedContent ) .clickable { selectedItem = photo } ) } } } else { // 详情页 Image( painter = rememberAsyncImagePainter(item.url), contentDescription = null, modifier = Modifier .sharedElement( state = rememberSharedContentState(key = item.id), animatedVisibilityScope = this@AnimatedContent ) .fillMaxSize() ) } } }

八、动画核心机制

8.1 Vsync + Choreographer 机制

Android 动画的流畅性依赖于Vsync(垂直同步)Choreographer的协同工作:

  1. Vsync 信号:显示器硬件每帧刷新完成后发出同步信号(60Hz = 16.6ms/帧,120Hz = 8.3ms/帧)

  2. Choreographer:通过postCallback()注册动画回调,在 Vsync 信号到达时触发doFrame()

  3. 动画更新ValueAnimator计算当前帧的属性值 → 调用Interpolator计算插值 → 调用TypeEvaluator计算最终值 → 更新 View 属性 →invalidate()触发重绘

  4. SurfaceFlinger:合成所有图层,输出到屏幕

8.2 属性动画计算流程

plain

start() → Choreographer 注册回调 → 等待 Vsync 信号 → doFrame(frameTimeNanos) → 计算 elapsedFraction (0~1) → Interpolator 计算 interpolatedFraction → TypeEvaluator 计算属性值 → 更新目标对象属性 → invalidate() 请求重绘 → 下一帧循环直到动画结束

九、性能优化最佳实践

9.1 避免掉帧

表格

优化手段说明
使用硬件层view.animate().withLayer()view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
避免主线程阻塞动画回调中不做 IO/复杂计算
减少过度绘制使用 GPU 调试模式分析
控制同时动画数MotionLayout 同时动画 50+ 个 View 可能掉帧
使用 Profile GPU Rendering开发者选项中开启,查看各阶段耗时

9.2 各场景选型建议

场景推荐方案
简单属性变化(位移、透明度)ObjectAnimator/ViewPropertyAnimator
复杂交互动效(折叠头、拖拽)MotionLayout
新项目的所有动画Jetpack Compose Animation API
列表项增删动画RecyclerView.ItemAnimator/ ComposeLazyColumn+AnimatedVisibility
跨页面共享元素SharedElementTransition/ ComposeSharedTransitionLayout
复杂帧动画Lottie(替代 AnimationDrawable)

9.3 Compose 动画性能

  • Compose 动画基于重组(Recomposition)智能跳过,只有状态变化的 Composable 会重新执行

  • 使用remember缓存动画状态,避免不必要的计算

  • 复杂动画使用LaunchedEffect+Animatable精确控制


十、总结对比

维度View 动画属性动画MotionLayoutCompose 动画
实际改变属性❌ 否✅ 是✅ 是✅ 是
非 View 对象❌ 不支持✅ 支持❌ 仅 View✅ 任意状态
代码量少(XML 声明)
复杂交互❌ 困难⚠️ 较复杂✅ 擅长✅ 擅长
维护性一般一般优秀
推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

2026 年建议:新项目直接使用Jetpack Compose + Compose Animation API;维护中的传统项目,复杂动画迁移到MotionLayout,简单动画使用属性动画替代 View 动画。

返回列表