Android自定义View高效绘制问题:规避重绘闪烁与优化绘制效率
首先直接给你明确结论:你不能在onDraw()方法之外安全地进行View的绘制操作。Android的View绘制体系是由系统UI线程的绘制管线严格管控的,onDraw()是系统为View提供的唯一合法绘制入口——所有绘制操作都必须在这个方法内完成,否则会引发线程安全问题、画面错乱,甚至和系统的绘制流程冲突(比如系统触发重绘时会直接覆盖你在外部做的所有绘制)。
不过别担心,你的核心痛点(重复全量绘制导致的闪烁和低效率)完全有成熟的优化方案,下面给你一步步拆解:
核心优化方向:减少不必要重绘 + 复用已有绘制结果
1. 用离屏缓存(Offscreen Buffer)复用已绘制内容
你可以创建一个全局的Bitmap和对应的Canvas作为缓存容器,把已经绘制好的图片都存在这个缓存里。每次新增一张图片时,只把新图片画到缓存Canvas上,然后在onDraw()里只需要将整个缓存Bitmap一次性画到View的Canvas上即可。这样每次onDraw()的工作量就从绘制N张图变成了绘制1张大图,效率会大幅提升。
示例代码:
private Bitmap mCacheBitmap; private Canvas mCacheCanvas; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 初始化缓存,尺寸和View保持一致 if (mCacheBitmap != null) { mCacheBitmap.recycle(); } mCacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mCacheCanvas = new Canvas(mCacheBitmap); // 背景只绘制一次到缓存里 drawBackground(mCacheCanvas); } // 新图片下载完成后调用这个方法 private void addNewImage(Bitmap newBitmap, int left, int top) { // 直接把新图片画到缓存Canvas上 mCacheCanvas.drawBitmap(newBitmap, left, top, mPaint); // 只重绘新增图片的区域,而非整个View invalidate(left, top, left + newBitmap.getWidth(), top + newBitmap.getHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 只需要把缓存好的Bitmap画到View上 if (mCacheBitmap != null) { canvas.drawBitmap(mCacheBitmap, 0, 0, null); } } private void drawBackground(Canvas canvas) { // 这里写你的背景绘制逻辑,只执行一次 canvas.drawColor(Color.parseColor("#f5f5f5")); }
2. 局部重绘,避免全量刷新
每次新增图片时,别调用无参的invalidate()(会触发全View重绘),而是调用invalidate(Rect)或者postInvalidate(Rect),传入新增图片所在的矩形区域。这样系统只会重绘View的这个小区域,既能减少绘制工作量,也能避免画面闪烁。
比如上面示例里的invalidate(left, top, left + newBitmap.getWidth(), top + newBitmap.getHeight())就是局部重绘的标准用法。
3. 避免重复绘制静态背景
如果你的View背景是固定不变的,只需要在初始化缓存时绘制一次(比如onSizeChanged()方法里),之后的onDraw()和新增图片操作都不要再重复绘制背景。这样就不会每次重绘都覆盖之前的内容,从根源减少闪烁的可能。
4. 启用硬件加速优化
可以给View开启硬件加速,利用GPU提升绘制效率,同时进一步减少闪烁:
setLayerType(LAYER_TYPE_HARDWARE, null);
注意:硬件加速对部分特殊绘制逻辑有兼容性限制,如果你的自定义绘制有复杂的图层混合或滤镜操作,需要测试确认效果。
额外建议:复用系统成熟组件
如果你的需求只是展示图片网格,其实可以直接用RecyclerView配合GridLayoutManager(推荐),或者传统的GridView。这些系统组件自带视图复用、局部更新的机制,不需要自己处理复杂的绘制缓存逻辑,性能优化也已经做得很完善了。
内容的提问来源于stack exchange,提问作者Surubelnita

