Android中使用AnimatedImageDrawable在Canvas上正确绘制GIF的方法
我来帮你搞定这个问题!你当前的代码只绘制了GIF的第一帧就结束了,根本没给后续帧刷新画布的机会——AnimatedImageDrawable本身不会自动触发SurfaceView重绘,得我们主动配合它的动画节奏来更新画布。下面是完整的实现方案,Kotlin和Java版本都有:
Kotlin 实现方案
我们需要给AnimatedImageDrawable注册动画回调,让它在每一帧准备好时通知SurfaceView刷新画布,同时遵循SurfaceView的生命周期管理资源:
class GifSurfaceView(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), SurfaceHolder.Callback { private var victoryGif: AnimatedImageDrawable? = null private var holder: SurfaceHolder? = null // 监听GIF帧更新的回调 private val animationCallback = object : AnimatedImageDrawable.AnimationCallback() { override fun onAnimationFrame(drawable: Drawable?) { // 每一帧就绪时,触发画布重绘 drawCurrentFrame() } } init { holder = this.holder holder?.addCallback(this) // 初始化GIF资源 try { val victorySource = ImageDecoder.createSource(context.assets, "victory.gif") victoryGif = ImageDecoder.decodeDrawable(victorySource) as AnimatedImageDrawable victoryGif?.registerAnimationCallback(animationCallback) } catch (e: IOException) { e.printStackTrace() } } override fun surfaceCreated(holder: SurfaceHolder) { // Surface创建完成后启动GIF动画 victoryGif?.start() } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} override fun surfaceDestroyed(holder: SurfaceHolder) { // Surface销毁时清理资源,防止内存泄漏 victoryGif?.stop() victoryGif?.unregisterAnimationCallback(animationCallback) victoryGif = null } private fun drawCurrentFrame() { val canvas = holder?.lockCanvas() ?: return try { // 清空画布(可选,根据你的背景需求调整) canvas.drawColor(Color.BLACK) victoryGif?.let { gif -> // 计算缩放比例,让GIF居中显示 val scale = min(canvas.width.toFloat() / gif.intrinsicWidth, canvas.height.toFloat() / gif.intrinsicHeight) val left = (canvas.width - gif.intrinsicWidth * scale) / 2 val top = (canvas.height - gif.intrinsicHeight * scale) / 2 // 设置GIF的绘制区域 gif.setBounds(left.toInt(), top.toInt(), (left + gif.intrinsicWidth * scale).toInt(), (top + gif.intrinsicHeight * scale).toInt()) // 绘制当前帧 gif.draw(canvas) } } finally { // 必须解锁并提交画布,否则无法显示绘制内容 holder?.unlockCanvasAndPost(canvas) } } }
Java 实现方案
如果习惯用Java开发,对应的实现逻辑完全一致:
public class GifSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private AnimatedImageDrawable victoryGif; private SurfaceHolder holder; private final AnimatedImageDrawable.AnimationCallback animationCallback = new AnimatedImageDrawable.AnimationCallback() { @Override public void onAnimationFrame(@Nullable Drawable drawable) { drawCurrentFrame(); } }; public GifSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); // 初始化GIF资源 try { ImageDecoder.Source victorySource = ImageDecoder.createSource(context.getAssets(), "victory.gif"); victoryGif = (AnimatedImageDrawable) ImageDecoder.decodeDrawable(victorySource); victoryGif.registerAnimationCallback(animationCallback); } catch (IOException e) { e.printStackTrace(); } } @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { if (victoryGif != null) { victoryGif.start(); } } @Override public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { if (victoryGif != null) { victoryGif.stop(); victoryGif.unregisterAnimationCallback(animationCallback); victoryGif = null; } } private void drawCurrentFrame() { Canvas canvas = holder.lockCanvas(); if (canvas == null) return; try { canvas.drawColor(Color.BLACK); if (victoryGif != null) { float scale = Math.min((float) canvas.getWidth() / victoryGif.getIntrinsicWidth(), (float) canvas.getHeight() / victoryGif.getIntrinsicHeight()); int left = (int) ((canvas.getWidth() - victoryGif.getIntrinsicWidth() * scale) / 2); int top = (int) ((canvas.getHeight() - victoryGif.getIntrinsicHeight() * scale) / 2); victoryGif.setBounds(left, top, (int) (left + victoryGif.getIntrinsicWidth() * scale), (int) (top + victoryGif.getIntrinsicHeight() * scale)); victoryGif.draw(canvas); } } finally { holder.unlockCanvasAndPost(canvas); } } }
关键要点解释
- 为什么原代码只显示第一帧?因为你只调用了一次
draw(),后续动画帧没有机会被绘制到画布上。 - 为什么用
AnimationCallback?它能精准匹配GIF每帧的更新时机,比定时任务更可靠,避免卡顿或帧丢失。 - 一定要管理Surface生命周期:在
surfaceCreated启动动画,surfaceDestroyed停止并释放资源,防止内存泄漏。
内容的提问来源于stack exchange,提问作者himmels
相关产品推荐
相关产品推荐

