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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 00:30:54