创建位图时自定义视图崩溃
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)
我认为使用 addOnLayoutChangeListener
比 doOnLayout
更好
解决了我的问题。我使用的是 LinearLayout,它将我的自定义视图推到我的 ImageView 下方。切换到 RelativeLayout 允许自定义视图位于 ImageView 之上并解决了问题。
我正在尝试创建一个充满黑屏的自定义视图,然后可以通过在屏幕上绘图以显示下面的图像来将其擦除。我发现了一个旧的 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)
我认为使用 addOnLayoutChangeListener
比 doOnLayout
解决了我的问题。我使用的是 LinearLayout,它将我的自定义视图推到我的 ImageView 下方。切换到 RelativeLayout 允许自定义视图位于 ImageView 之上并解决了问题。