为什么 Canvas 在协程后台线程中绘制的位置很奇怪?
Why did Canvas draw in Coroutine background thread draw in weird position?
我有一个简单的 customView,它画了一条线,如下所示。
class ViewCustom @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
private val strokePaint = Paint()
.apply { color = Color.BLACK}
.apply { strokeWidth = 6f }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawLine(
0f, 0f,
width.toFloat(), height.toFloat(),
strokePaint
)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec))
}
}
效果很好,把对角线端到端画出来。
但是,如果我在它周围添加一个协程线程
class ViewCustom @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
private val treeDrawing by lazy {
TreeDrawingCanvasCustom()
}
private val strokePaint = Paint()
.apply { color = Color.BLACK}
.apply { strokeWidth = 6f }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
CoroutineScope(Dispatchers.Default).launch { // <-- add coroutine
canvas.drawLine(
0f, 0f,
width.toFloat(), height.toFloat(),
strokePaint
)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec))
}
}
它仍然在绘制,但是在一个奇怪的位置,如下图所示。我已经检查过 height
和 width
值在有或没有协程的情况下仍然相同。
为什么会这样?
刚刚发现原因是所有绘图中只有一个 canvas 共享。当onDraw在后台线程时,不能保证其他人也在操纵主线程绘制canvas,因此并发绘制会导致绘制坐标与原始坐标不对齐
在下面的图例中,绘图是在Caption区域,是因为Text Caption绘图是同时发生的,所以它是在Text Caption区域绘制的。
我有一个简单的 customView,它画了一条线,如下所示。
class ViewCustom @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
private val strokePaint = Paint()
.apply { color = Color.BLACK}
.apply { strokeWidth = 6f }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawLine(
0f, 0f,
width.toFloat(), height.toFloat(),
strokePaint
)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec))
}
}
效果很好,把对角线端到端画出来。
但是,如果我在它周围添加一个协程线程
class ViewCustom @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
private val treeDrawing by lazy {
TreeDrawingCanvasCustom()
}
private val strokePaint = Paint()
.apply { color = Color.BLACK}
.apply { strokeWidth = 6f }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
CoroutineScope(Dispatchers.Default).launch { // <-- add coroutine
canvas.drawLine(
0f, 0f,
width.toFloat(), height.toFloat(),
strokePaint
)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec))
}
}
它仍然在绘制,但是在一个奇怪的位置,如下图所示。我已经检查过 height
和 width
值在有或没有协程的情况下仍然相同。
为什么会这样?
刚刚发现原因是所有绘图中只有一个 canvas 共享。当onDraw在后台线程时,不能保证其他人也在操纵主线程绘制canvas,因此并发绘制会导致绘制坐标与原始坐标不对齐
在下面的图例中,绘图是在Caption区域,是因为Text Caption绘图是同时发生的,所以它是在Text Caption区域绘制的。