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
相关产品推荐
相关产品推荐

