咨询:Android项目中Sprite Image Animation便捷实现方案
我太懂这种困扰了!之前在Android项目里折腾精灵图动画时,那些现成的复杂方案要么配置一堆东西,要么在RecyclerView Adapter里适配起来特别拧巴,简直浪费时间——自定义View确实是个更清爽的解决思路,灵活还容易复用!
用自定义View实现精灵图动画的方案
核心思路
精灵图动画的本质就是按时间间隔切换绘制图片的不同区域,自定义View可以直接在onDraw方法里精准控制要绘制的帧区域,再配合动画驱动类(比如ValueAnimator)来自动更新帧索引,完全不需要依赖复杂的第三方库。
关键代码实现
这里以你提到的一行多帧跑步精灵图为例,写个简单的SpriteView:
- 先定义基础属性和初始化逻辑
class SpriteView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private var spriteBitmap: Bitmap? = null private var totalFrames = 8 // 对应示例图的8帧 private var frameDuration = 100 // 每帧间隔毫秒 private var currentFrame = 0 private var animator: ValueAnimator? = null // 初始化时计算单帧尺寸 private var frameWidth = 0 private var frameHeight = 0 fun setSpriteBitmap(bitmap: Bitmap) { this.spriteBitmap = bitmap frameWidth = bitmap.width / totalFrames frameHeight = bitmap.height invalidate() }
- 实现动画启停逻辑
fun startAnimation() { animator?.cancel() animator = ValueAnimator.ofInt(0, totalFrames - 1).apply { duration = totalFrames * frameDuration.toLong() repeatCount = ValueAnimator.INFINITE addUpdateListener { currentFrame = it.animatedValue as Int invalidate() // 触发重绘 } start() } } fun stopAnimation() { animator?.cancel() animator = null }
- 在
onDraw里绘制当前帧
override fun onDraw(canvas: Canvas) { super.onDraw(canvas) spriteBitmap?.let { bmp -> // 计算当前帧在原图中的区域 val srcRect = Rect(currentFrame * frameWidth, 0, (currentFrame + 1) * frameWidth, frameHeight) // 绘制到View的整个区域(可根据需求调整dstRect实现缩放) val dstRect = Rect(0, 0, width, height) canvas.drawBitmap(bmp, srcRect, dstRect, null) } }
- 适配RecyclerView的生命周期
为了避免列表滑动时浪费资源,在View附着/脱离窗口时自动启停动画:
override fun onAttachedToWindow() { super.onAttachedToWindow() startAnimation() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() stopAnimation() } }
在RecyclerView Adapter里的使用方式
这种自定义View在Adapter里用起来和普通View完全一样:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpriteViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sprite, parent, false) return SpriteViewHolder(view) } override fun onBindViewHolder(holder: SpriteViewHolder, position: Int) { // 用Glide/Coil加载精灵图(记得处理内存缓存) Glide.with(holder.spriteView.context) .asBitmap() .load(R.drawable.your_sprite_sheet) .into(object : CustomTarget<Bitmap>() { override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) { holder.spriteView.setSpriteBitmap(resource) } override fun onLoadCleared(placeholder: Drawable?) {} }) } class SpriteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val spriteView: SpriteView = itemView.findViewById(R.id.sprite_view) }
额外优化点
- 可以给
SpriteView添加自定义XML属性(比如app:totalFrames、app:frameDuration),不用在代码里硬编码参数 - 如果是多行多列的精灵图,只需要调整帧索引的计算逻辑(比如
currentFrame = row * columns + column) - 记得处理Bitmap的内存释放,避免OOM
这种方案既轻量又可控,完美解决了复杂方案在RecyclerView里适配困难的问题,亲测好用!
内容的提问来源于stack exchange,提问作者OMArikan
相关产品推荐
相关产品推荐

