创建位图时自定义视图崩溃

Custom View crashes when creating bitmap

我正在尝试创建一个充满黑屏的自定义视图,然后可以通过在屏幕上绘图以显示下面的图像来将其擦除。我发现了一个旧的 SO 问题,它有一个旧的解决方案,但是将它转换为 Kotlin 会在第 34 行给出关于高度为 0 的错误。下面是正在使用的代码。请注意,宽度已正确设置为 1440,但高度和测量高度也都为 0。我认为将代码添加到 this.doOnLayout 将确保该功能仅在视图展开后 运行,但这似乎不正确。

package com.imdevinc.mapcaster

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.core.view.doOnLayout

class DrawView @JvmOverloads
    constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr), View.OnTouchListener {

    private val bmPaint = Paint()
    private val drawPaint = Paint()
    private val path = Path()
    private var layoutUpdated = false
    private lateinit var cv: Canvas
    private lateinit var bm: Bitmap

    init {
        isFocusable = true
        isFocusableInTouchMode = true
        setOnTouchListener(this)
        this.doOnLayout {
            initCanvas()
        }
    }

    fun initCanvas() {
        Log.d("DrawView", "onDraw(): filling canvas")
        // Create a new bitmap and canvas, fill it with a black mask
        bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        cv = Canvas()
        cv = Canvas()
        cv.setBitmap(bm)
        cv.drawColor(Color.BLACK)
        Log.d("DrawView", "onDraw(): drawing black")

        // Make the painting stroke fatter
        drawPaint.style = Paint.Style.STROKE
        drawPaint.strokeWidth = width / 15f
        Log.d("DrawView", "onDraw(): updating stroke style")

        // Clear pixels instead of painting new ones
        drawPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
        Log.d("DrawView", "onDraw(): setting xfer mode")
        layoutUpdated = true
    }

    override fun onTouch(view: View?, event: MotionEvent): Boolean {
        val xPos = event.x
        val yPos = event.y

        when(event.action) {
            MotionEvent.ACTION_DOWN -> path.moveTo(xPos, yPos)
            MotionEvent.ACTION_MOVE -> path.moveTo(xPos, yPos)
            else -> return false
        }

        invalidate()
        return true
    }

    override fun onDraw(canvas: Canvas) {
        if (layoutUpdated) {
            cv.drawPath(path, drawPaint)
            Log.d("DrawView", "onDraw(): drawPath")
            canvas.drawBitmap(bm, 0f, 0f, bmPaint)
            Log.d("DrawView", "onDraw(): drawBitmap")
        }

        super.onDraw(canvas)
    }
}

以及视图布局以备不时之需

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher"/>

    <com.imdevinc.mapcaster.DrawView
        android:id="@+id/draw"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

堆栈跟踪

2020-01-02 18:50:18.173 10081-10081/com.imdevinc.mapcaster E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.imdevinc.mapcaster, PID: 10081
    java.lang.IllegalArgumentException: width and height must be > 0
        at android.graphics.Bitmap.createBitmap(Bitmap.java:1033)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:1000)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:950)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:911)
        at com.imdevinc.mapcaster.DrawView.initCanvas(DrawView.kt:42)
        at com.imdevinc.mapcaster.DrawView$$special$$inlined$doOnLayout.onLayoutChange(View.kt:339)
        at android.view.View.layout(View.java:20690)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at com.android.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:508)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:753)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2792)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2319)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
        at android.view.Choreographer.doCallbacks(Choreographer.java:761)
        at android.view.Choreographer.doFrame(Choreographer.java:696)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

我认为使用 addOnLayoutChangeListenerdoOnLayout

更好

解决了我的问题。我使用的是 LinearLayout,它将我的自定义视图推到我的 ImageView 下方。切换到 RelativeLayout 允许自定义视图位于 ImageView 之上并解决了问题。