You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:Android项目中Sprite Image Animation便捷实现方案

我太懂这种困扰了!之前在Android项目里折腾精灵图动画时,那些现成的复杂方案要么配置一堆东西,要么在RecyclerView Adapter里适配起来特别拧巴,简直浪费时间——自定义View确实是个更清爽的解决思路,灵活还容易复用!

用自定义View实现精灵图动画的方案

核心思路

精灵图动画的本质就是按时间间隔切换绘制图片的不同区域,自定义View可以直接在onDraw方法里精准控制要绘制的帧区域,再配合动画驱动类(比如ValueAnimator)来自动更新帧索引,完全不需要依赖复杂的第三方库。

关键代码实现

这里以你提到的一行多帧跑步精灵图为例,写个简单的SpriteView:

  1. 先定义基础属性和初始化逻辑
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()
    }
  1. 实现动画启停逻辑
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
    }
  1. 在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)
        }
    }
  1. 适配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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:34:50