这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
layout过程
View
的measure
过程之后就是View
的layout
过程。相对而言layout
过程比起measure
过程会简单一点。layout
过程是从ViewRootImpl
的pergormLayout()
方法开始,performLayout()
会调用layout()
。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
复制代码
layout
方法实现流程,先通过setFrame
方法设定View
的四个顶点位置:mLeft
、mRight
、mTop
、mBottom
。坐标确定好之后就能够得到View
在父容器的位置。接着会调用onLayout
方法,但在View
中onLayout
没有实现,ViewGroup
中同样也是,因此onLayout
方法是在具体实现类中去实现的。可以通过具体实现类LinearLayout
方法了解具体是怎么实现的。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
复制代码
在layoutVertical
方法中会遍历所有子视图调用它们的setChildFrame
方法来确定子视图位置。在这过程中childTop
会逐渐增大,在后续添加的子视图会放置在靠下的位置,符合垂直方向排列布局属性。父容器在layout
方法完成自身定位后还有执行onLayout
方法去调用子视图的layout
方法,然后子视图就能确定自己的位置。View
树通过一层层的layout
方法传递调用完成整课树位置信息定位。
draw过程
View
的draw
步骤过程主要分以下几点:
- 绘制背景(有必要时保存画布层)
- 绘制
View
的内容 - 绘制子View(有必要时保存画布层)
- 绘制边框等装饰类额外属性
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
....
// Step 2, save the canvas' layers
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
}
}
复制代码
略过方法drawBackground(canvas)
重点关注了解onDraw(canvas)
和 dispatchDraw(canvas)
的实现。在View
和ViewGroup
中onDraw(canvas)
方法没有任何实现是空的,dispatchDraw(canvas)
有在ViewGroup
中实现,具体实现在具体实现类当中。但大体上也能了解绘制上先绘制自身再去分发绘制去绘制子视图。因此绘制是通过方法dispatchDraw(canvas)
进行传递一层层向子视图去做绘制的。
此外View
还有一个方法setWillNotDraw
,当View
不需要绘制任何内容时可以标记为true
,系统内部会做绘制优化。View
默认是不启动这个优化功能,但ViewGroup
会默认启动。对于继承ViewGroup
的自定义View
开发时就需要注意这点,当需要使用onDraw
方法时需要去关闭will_not_draw
标记。