View的layout过程
下面是表达其大概思路的伪码
public void layout(int l, int t, int r, int b){
setFrame(l ,t , r, b);
onLayout(changed ,l , t, r, b);
}
上面的思路也很清晰,就是首先传入了l ,t, r, b四个参数,然后调用setFrame(l, t, r, b)
方法,通过setFrame()
方法来设定View的四个顶点的位置,这样就设定了View在父容器中的位置。接着就调用onLayout()
方法,这个方法的用途就是父容器确定其中所有子View的位置。有个地方需要注意:layout(int l, int t, int r, int b)
中这四个参数是谁传入的?其实,就父View的onLayout()
方法中调用子view的layout()
方法时传入的。
同样,由于onLayout()
的具体实现和具体的布局有关,所以View和ViewGroup均没有真正地实现onLayout()
方法,总体来说,伪码实现如下:
protected void onLayout(boolean changed, int l, int t, int r, int b){
for (int i = 0; i < count; i ++){
View child = getVirtualChildAt(i);
...... // 获取对应child的childLeft, childTop , childRight, childBottom数据
child.layout(childLeft, childTop , childRight, childBottom); //注解一
}
}
注解一:
注意这里的childLeft, childTop , childRight, childBottomd都是需要根据具体情况计算而来的。
View的draw过程
View的draw过程比较简单,下面使用伪码来表明整个过程
public void draw(Canvas canvas){
...
//step 1 绘制背景
drawBackground(canvas);
//step 2 绘制自己
onDraw(canvas);
//step 3 绘制子View
dispatchDraw(canvas); //注解一
//step 4 绘制装饰
onDrawScrollBars(canvas);
...
return;
}
//注解一
View绘制过程的传递是通过dispatchDraw()
来实现的,dispatchDraw()
会遍历所有的子元素的draw()
方法,如此draw事件就一层层地传递下去了。
还有一个地方需要注意,View有一个特殊的方法setWillNotDraw()
:
public void setWillNotDraw(boolean willNotDraw){
setFlags(willNotDraw ? WILL_NOT_DRAW : 0 , DRAW_MASK);
}
如果一个View不需要绘制任何内容,那么设置这个标志位为true以后,系统就会进行相应的优化。默认情况下,View没有启用这个标志位,但是ViewGroup会默认启用这个优化标志位。这个标志位的实际开发意义:当我们自定义控件继承ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。如果知道一个ViewGroup需要通过onDraw()
来绘制自身内容时,我们需要显式地关闭WILL_NOT_DRAW这个标记位。