1. 帧动画基础为什么选择AnimationDrawable在Android应用开发中给静态界面添加动态元素是提升用户体验的常见手段。帧动画Frame Animation作为最基础的动画实现方式之一特别适合需要循环播放一组图片的场景。比如加载中的旋转图标、简单的进度提示、游戏中的角色动作等。AnimationDrawable是Android官方提供的帧动画解决方案它的工作原理就像老式的电影放映机——通过快速切换一系列静态图片帧来产生动态效果。我曾在多个项目中使用过这个技术实测下来它的性能表现相当稳定尤其在处理少量图片建议不超过20张时几乎不会造成卡顿。与属性动画Property Animation或补间动画Tween Animation相比帧动画最大的特点是直观可控每帧画面都是预先设计好的图片实现简单不需要复杂的数学计算兼容性好从Android 1.0开始就支持!-- 典型帧动画定义示例 -- animation-list xmlns:androidhttp://schemas.android.com/apk/res/android item android:drawabledrawable/frame1 android:duration100/ item android:drawabledrawable/frame2 android:duration100/ item android:drawabledrawable/frame3 android:duration100/ /animation-list注意帧动画不适合处理大量高分辨率图片否则可能导致内存溢出OOM。对于复杂动画场景建议考虑Lottie或GIF方案。2. 完整实现流程从XML到代码控制2.1 准备动画资源第一步是在res/drawable目录下创建动画描述文件。我习惯用anim_作为前缀命名这类文件比如anim_loading.xml。这个文件定义了帧序列和每帧持续时间毫秒。!-- res/drawable/anim_loading.xml -- animation-list xmlns:androidhttp://schemas.android.com/apk/res/android android:oneshotfalse item android:drawabledrawable/loading_1 android:duration150/ item android:drawabledrawable/loading_2 android:duration150/ item android:drawabledrawable/loading_3 android:duration150/ item android:drawabledrawable/loading_4 android:duration150/ /animation-list几个关键参数说明android:oneshot设为true时动画只播放一次false则循环播放android:duration控制每帧显示时间建议保持相同以获得流畅效果android:drawable引用具体的图片资源2.2 在布局文件中引用帧动画可以通过ImageView或ProgressBar展示。根据我的经验ProgressBar更适合加载场景而ImageView则更灵活。ProgressBar实现方案ProgressBar android:layout_width40dp android:layout_height40dp android:indeterminateDrawabledrawable/anim_loading/ImageView实现方案ImageView android:idid/iv_animation android:layout_width50dp android:layout_height50dp android:srcdrawable/anim_loading/2.3 代码控制动画在Activity中控制动画启停是实际开发中的常见需求。这里有个坑要注意动画不能在onCreate()中立即启动因为Drawable还未完全附加到窗口。class MainActivity : AppCompatActivity() { private lateinit var animDrawable: AnimationDrawable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val imageView findViewByIdImageView(R.id.iv_animation) animDrawable imageView.drawable as AnimationDrawable // 正确做法在onWindowFocusChanged中启动 imageView.post { animDrawable.start() } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { animDrawable.start() } else { animDrawable.stop() } } }3. 高级技巧与性能优化3.1 动态修改帧动画有时候我们需要根据运行时条件调整动画。比如在游戏开发中角色受伤时切换为红色闪烁效果。通过代码动态修改AnimationDrawable完全可行fun setupDamageAnimation() { val anim AnimationDrawable() anim.addFrame(ContextCompat.getDrawable(this, R.drawable.player_normal)!!, 200) anim.addFrame(ContextCompat.getDrawable(this, R.drawable.player_damage)!!, 200) anim.isOneShot false findViewByIdImageView(R.id.iv_character).setImageDrawable(anim) anim.start() }3.2 内存优化策略帧动画最常遇到的就是内存问题。我在一个相机项目中就踩过坑——直接使用1080P的图片序列导致应用崩溃。解决方案包括图片压缩使用WebP格式替代PNG通常能减少30%体积尺寸适配确保图片尺寸不超过显示区域的2倍按需加载对于长动画可以动态加载部分帧及时回收在Activity的onDestroy中调用animDrawable.stop()override fun onDestroy() { animDrawable.stop() animDrawable.callback null super.onDestroy() }3.3 与Lottie的配合使用对于复杂矢量动画我推荐使用Airbnb的Lottie库。但帧动画仍有其优势比如在需要精确控制每帧内容时。实际项目中我经常混合使用两种技术技术方案适用场景优点缺点AnimationDrawable简单图标动画、进度指示实现简单、性能好不支持矢量、内存占用高Lottie复杂交互动画、品牌动效矢量缩放、AE导出学习成本较高4. 实战案例GPS定位状态动画让我们通过一个完整的定位状态案例展示帧动画的实际应用。这个案例包含三种状态搜索中旋转动画定位成功静态图标定位失败闪烁警告步骤1准备动画资源!-- res/drawable/anim_location_searching.xml -- animation-list xmlns:androidhttp://schemas.android.com/apk/res/android item android:drawabledrawable/loc_searching_1 android:duration200/ item android:drawabledrawable/loc_searching_2 android:duration200/ item android:drawabledrawable/loc_searching_3 android:duration200/ /animation-list !-- res/drawable/anim_location_failed.xml -- animation-list xmlns:androidhttp://schemas.android.com/apk/res/android android:oneshotfalse item android:drawabledrawable/loc_failed android:duration500/ item android:drawableandroid:color/transparent android:duration500/ /animation-list步骤2实现状态管理类class LocationAnimHelper(private val imageView: ImageView) { private var currentAnim: AnimationDrawable? null fun setSearching() { imageView.setImageResource(R.drawable.anim_location_searching) (imageView.drawable as? AnimationDrawable)?.apply { currentAnim?.stop() currentAnim this start() } } fun setSuccess() { currentAnim?.stop() imageView.setImageResource(R.drawable.ic_location_success) } fun setFailed() { imageView.setImageResource(R.drawable.anim_location_failed) (imageView.drawable as? AnimationDrawable)?.apply { currentAnim?.stop() currentAnim this start() } } }步骤3在Activity中使用class LocationActivity : AppCompatActivity() { private lateinit var animHelper: LocationAnimHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_location) animHelper LocationAnimHelper(findViewById(R.id.iv_location_status)) // 模拟定位状态变化 lifecycleScope.launch { delay(1000) animHelper.setSearching() delay(3000) if (Random.nextBoolean()) { animHelper.setSuccess() } else { animHelper.setFailed() } } } }这个案例展示了帧动画在实际业务中的典型应用。关键点在于状态切换时要正确处理前一个动画的停止避免内存泄漏和显示异常。