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

Android逐帧展示绘图形图片仅显示最后一张的问题求助

问题原因拆解

兄弟,你的代码里有几个核心问题导致最终只显示最后一张图片,我给你捋清楚:

  • UI线程被阻塞:onDraw方法是在UI线程执行的,你在循环里加了Thread.sleep(500),这会直接卡住UI线程。阻塞期间所有的绘制操作都不会实时刷新到屏幕上,等循环结束后,只有最后一次绘制的内容会被显示出来。
  • 绘制逻辑错误:循环里每次调用canvas.drawBitmap(bm,0,0,paint)都会把新的图片直接覆盖在画布上,就算没有sleep,也会因为绘制速度太快,肉眼完全看不到前面的帧,直接跳到最后一张。
  • IO操作放在UI线程:在onDraw里调用getAssets().open(file)和BitmapFactory.decodeStream(in)属于IO操作,会进一步拖慢UI线程,甚至引发ANR(应用无响应)。
修复方案

我们需要把帧切换逻辑和绘制逻辑分开,用异步的方式逐帧触发重绘,同时把耗时的IO操作放到子线程里。下面是修改后的代码示例:

第一步:重构自定义View,分离加载与绘制逻辑

public class MainActivity extends AppCompatActivity {
    final String TAG = "TEST";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }

    class MyView extends View {
        private Paint paintCircle;
        private Paint paintRect;
        private Paint paint;
        private List<Bitmap> frameBitmaps = new ArrayList<>();
        private int currentFrameIndex = 0;
        private Handler frameHandler = new Handler(Looper.getMainLooper());
        private static final long FRAME_INTERVAL = 500; // 每帧间隔500ms

        public MyView(Context context) {
            super(context);
            initPaints();
            loadFramesInBackground(); // 子线程加载图片
        }

        private void initPaints() {
            paintCircle = new Paint();
            paintCircle.setColor(Color.YELLOW);
            paintCircle.setStyle(Paint.Style.FILL);
            paintCircle.setAntiAlias(true);

            paintRect = new Paint();
            paintRect.setColor(Color.GREEN);

            paint = new Paint();
        }

        // 子线程加载所有帧图片
        private void loadFramesInBackground() {
            new Thread(() -> {
                String path = "car4/img";
                DecimalFormat d = new DecimalFormat("0000");
                try {
                    String[] files = getAssets().list(path);
                    if (files != null) {
                        // 这里加载前5帧,和你原来的逻辑一致
                        for (int i = 1; i <= 5; i++) {
                            String file = path + "/" + d.format(i) + ".jpg";
                            InputStream in = getAssets().open(file);
                            Bitmap bm = BitmapFactory.decodeStream(in);
                            frameBitmaps.add(bm);
                            in.close();
                        }
                        // 加载完成后开始逐帧切换
                        startFrameAnimation();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

        // 定时切换帧并触发重绘
        private void startFrameAnimation() {
            frameHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (currentFrameIndex >= frameBitmaps.size() - 1) {
                        currentFrameIndex = 0; // 循环播放,也可以停止
                    } else {
                        currentFrameIndex++;
                    }
                    invalidate(); // 触发onDraw重绘
                    frameHandler.postDelayed(this, FRAME_INTERVAL);
                }
            }, FRAME_INTERVAL);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (frameBitmaps.isEmpty() || currentFrameIndex >= frameBitmaps.size()) {
                return;
            }
            // 绘制当前帧的图片
            Bitmap currentBitmap = frameBitmaps.get(currentFrameIndex);
            canvas.drawBitmap(currentBitmap, 0, 0, paint);
            // 绘制你需要的图形(这里是黄色圆形)
            canvas.drawCircle(10, 10, 50, paintCircle);
        }

        // 记得在View销毁时释放资源,避免内存泄漏
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            frameHandler.removeCallbacksAndMessages(null);
            for (Bitmap bitmap : frameBitmaps) {
                if (!bitmap.isRecycled()) {
                    bitmap.recycle();
                }
            }
            frameBitmaps.clear();
        }
    }
}

关键改进点说明

  • 子线程加载图片:把读取Assets和解码Bitmap的操作放到子线程,避免阻塞UI线程。
  • Handler定时切换帧:用Handler.postDelayed来定时更新当前帧索引,然后调用invalidate()触发View重绘,这样每一次重绘都会显示当前帧的内容。
  • 资源回收:在View销毁时回收Bitmap并移除Handler的回调,避免内存泄漏和OOM。
  • 绘制逻辑简化:onDraw只负责绘制当前帧的内容,不再做循环和阻塞操作,保证UI流畅。
额外注意事项
  • 如果图片数量较多,建议不要一次性加载所有Bitmap,而是采用按需加载+缓存的方式,避免内存溢出。
  • 也可以使用Android自带的AnimationDrawable或者ValueAnimator来实现帧动画,逻辑会更简洁,但上面的方式灵活性更高,方便你在每一帧添加自定义的绘制逻辑(比如你需要的特征图形)。

内容的提问来源于stack exchange,提问作者Xwa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:29:31