问题描述

最近在写一个自定义天气折线图的时候遇到一个问题。
在定义一个View的itemCount属性的时候需要用到View的width,我思路是在onLayout()中通过width初始化它的值,因为此时View已经确定了它的宽高了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private var itemCount = 1
set(value) {
field = value
itemWidth = width / value
}

private var itemWidth = width / itemCount
set(value) {
field = value
beginX = value / 2
}

private var beginX = itemWidth / 2

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
itemCount = 5
}

但是这段代码出现了一个意料之外的错误,我在为View应用属性动画的时候出现了问题,itemWidth和beginX等属性在动画一开始的时候属性值都为0,在运行了很短的时间后,它们的值才变成我预期的值。

1
2
3
4
5
6
7
8
//Log信息
D/test: drawPath: itemWidth=0,beginX=0
D/test: drawPath: itemWidth=0,beginX=0
D/test: drawPath: itemWidth=216,beginX=108
D/test: drawPath: itemWidth=216,beginX=108
D/test: drawPath: itemWidth=216,beginX=108
D/test: drawPath: itemWidth=216,beginX=108
D/test: drawPath: itemWidth=216,beginX=108

原因探究

我认为是在动画开始之前,View的onLayout()方法未得到调用,此时View.width属性的值还是0,导致我定义的属性值计算出来也是0。在动画运行了一段时间才对onLayout()进行调用,此时我定义的属性值才恢复正常。

解决办法

我的思路是将使用View.width的代码块暂时阻塞,等到onLayout()方法被调用,View.width的值不为0的时候,再唤醒前面的代码块,这样使得,在使用到View.width时,其必然值不等于0,因此,也能正确计算出itemWidth和beginX等属性的值。
最终,我采用Kotlin的协程async()将代码块暂时阻塞,在isReady=true(代码onLayout()已经被调用)时,唤醒代码块。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//标记width等属性是否已经准备好了
private var isReady = false

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
itemCount = 5
isReady = false
}

private fun drawPath(posX: Float, maxPosY: Float, minPosY: Float) {
MainScope().launch {
val defferd = async {
//使用width属性的代码块
}
if (isReady)
defferd.await()
}
}

近段时间发现这个解决办法存在问题

async在调用之时就会立刻执行它内部的代码块,而不是等到调用await方法后。
因此需要思考其他解决办法了。