android 动画详解

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

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


一、动画分类总览

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

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

二、View 动画(补间动画)

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

2.1 四种基本变换

动画类型 类名 属性
平移 TranslateAnimation fromXDelta, toXDelta, fromYDelta, toYDelta
缩放 ScaleAnimation fromXScale, toXScale, fromYScale, toYScale
旋转 RotateAnimation fromDegrees, toDegrees, pivotX, pivotY
透明度 AlphaAnimation fromAlpha, 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 动画引擎,计算动画值,不直接操作对象
ObjectAnimator ValueAnimator 子类,直接操作目标对象的属性
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 / rotationY 2D/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 / Compose LazyColumn + AnimatedVisibility
跨页面共享元素 SharedElementTransition / Compose SharedTransitionLayout
复杂帧动画 Lottie(替代 AnimationDrawable)

9.3 Compose 动画性能

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

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

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


十、总结对比

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

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

相关推荐
小孔龙6 小时前
GraphicBuffer 跨进程共享
android
行业研究员7 小时前
Android内置GPS与腾讯地图定位技术对比分析
android·android定位·腾讯地图定位
Android-Flutter7 小时前
android WMS 详解(二)
android·kotlin
游戏开发爱好者88 小时前
App Store 上传 IPA 自动化,.p8 密钥认证与 CI/CD 接入实战
android·运维·ci/cd·小程序·uni-app·自动化·iphone
游戏开发爱好者89 小时前
iOS 推送怎么配置,APNs 推送证书、设备库与群发
android·ios·小程序·https·uni-app·iphone·webview
阿pin10 小时前
Android随笔-kotlin 高阶函数
android·kotlin·高阶函数
wxson72821 天前
【Android视频监控系统技术总结】
android·音视频
hunterandroid1 天前
[Android 从零到一] LiveData 粘性事件与单次消费:从重复触发到可靠事件总线
android