Android自定义View的onDraw方法绘制Bitmap时触发NullPointerException
自定义DrawingView空指针异常排查与解决
问题描述
运行自定义绘图View时触发NullPointerException,堆栈信息如下:
Process: com.mahidev.kidsdrawingapp, PID: 19949 java.lang.NullPointerException at com.mahidev.kidsdrawingapp.DrawingView.onDraw(DrawingView.kt:64) at android.view.View.draw(View.java:22635) at android.view.View.updateDisplayListIfDirty(View.java:21472) at android.view.View.draw(View.java:22335) at android.view.ViewGroup.drawChild(ViewGro
出错代码位于第64行:
canvas.drawBitmap(mCanvasBitmap!!, 0f, 0f, mCanvasPaint)
问题原因
mCanvasBitmap仅在onSizeChanged方法中初始化,而Android View生命周期中,onDraw可能在onSizeChanged之前被调用(比如View首次布局还未确定尺寸时),此时mCanvasBitmap为null,非空断言!!直接触发空指针异常。
同时代码中还存在其他潜在问题:
- 多处使用
!!强制非空,未做null安全检查 - 绘制历史路径时错误使用当前
mDrawPath的颜色,导致所有历史路径颜色被覆盖 onTouchEvent方法末尾存在无效的重复return语句
解决方案
1. 核心空指针修复
在onDraw中对mCanvasBitmap做null安全检查,避免直接使用!!。
2. 其他问题修复
- 对所有可空对象使用Kotlin空安全调用(
?.)或let作用域函数 - 修正历史路径颜色取值逻辑,使用路径自身的color属性
- 移除
onTouchEvent中无效的重复return语句 - 初始化默认画笔大小,避免画笔尺寸为0的情况
修改后的完整代码
package com.mahidev.kidsdrawingapp import android.content.Context import android.graphics.* import android.util.AttributeSet import android.util.Log import android.util.TypedValue import android.view.MotionEvent import android.view.View class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) { private var mDrawPath: CustomPath? = null private var mCanvasBitmap: Bitmap? = null private var mDrawPaint: Paint? = null private var mCanvasPaint: Paint? = null private var mBrushSize: Float = 0.toFloat() private var color = Color.BLACK private var canvas: Canvas? = null private val mPaths = ArrayList<CustomPath>() init { Log.i("Mahi", "inside init") setUpDrawing() } private fun setUpDrawing() { Log.i("Mahi", "inside setUpDrawing") mDrawPaint = Paint().apply { this.color = color style = Paint.Style.STROKE strokeJoin = Paint.Join.ROUND strokeCap = Paint.Cap.ROUND } mDrawPath = CustomPath(color, mBrushSize) mCanvasPaint = Paint(Paint.DITHER_FLAG) // 初始化默认画笔大小 mBrushSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics) mDrawPaint?.strokeWidth = mBrushSize } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { Log.i("Mahi", "inside onSizeChanged") super.onSizeChanged(w, h, oldw, oldh) mCanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) canvas = Canvas(mCanvasBitmap!!) } fun setSizeForBrush(newSize: Float) { mBrushSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, newSize, resources.displayMetrics) mDrawPaint?.strokeWidth = mBrushSize } override fun onDraw(canvas: Canvas) { Log.i("Mahi", "onDraw") super.onDraw(canvas) // 安全绘制Bitmap mCanvasBitmap?.let { bitmap -> canvas.drawBitmap(bitmap, 0f, 0f, mCanvasPaint) } mDrawPaint?.let { paint -> // 绘制历史路径 for (path in mPaths) { paint.strokeWidth = path.brushThickness paint.color = path.color // 使用当前路径的颜色 canvas.drawPath(path, paint) } // 绘制当前路径 mDrawPath?.takeIf { !it.isEmpty }?.let { currentPath -> paint.strokeWidth = currentPath.brushThickness paint.color = currentPath.color canvas.drawPath(currentPath, paint) } } } override fun onTouchEvent(event: MotionEvent?): Boolean { Log.i("Mahi", "inside onTouchEvent") val touchX = event?.x ?: return super.onTouchEvent(event) val touchY = event?.y ?: return super.onTouchEvent(event) when (event.action) { MotionEvent.ACTION_DOWN -> { mDrawPath?.apply { this.color = color this.brushThickness = mBrushSize reset() moveTo(touchX, touchY) } } MotionEvent.ACTION_MOVE -> { mDrawPath?.lineTo(touchX, touchY) } MotionEvent.ACTION_UP -> { mDrawPath?.let { mPaths.add(it) mDrawPath = CustomPath(color, mBrushSize) } } else -> return super.onTouchEvent(event) } invalidate() return true } internal inner class CustomPath(var color: Int, var brushThickness: Float) : Path() }
内容的提问来源于stack exchange,提问作者Man Steel
相关产品推荐
相关产品推荐

