一.布局
前一篇文章已经详细研究了View的测量,现在接着往下看看View的layout过程吧。先回顾一下大致的流程:ViewRootImpl#performLayout()->layout()->onLayout()
Layout的作用是确定ViewGroup的位置,接着ViewGroup会在onLayout()中遍历子View并调用其layout()方法,在layout()中又会调用onLayout来确定子View的位置。
在ViewRootImpl#performLayout()中通过 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()) 调用顶层View的layout()方法,这个host就是DeCorView。
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 listenersCopy =
(ArrayList)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;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
layout()的实现比较简单,通过setFrame(l, t, r, b)确定左上角和右下角坐标(mLeft,mTop),(mRight,mBottom),这样就确定了ViewGroup(这里是DeCorView)的位置,接着就是调用onLayout()来完成子元素的布局了。子元素的layout过程跟具体的布局有关,所以在View中onLayout是空实现,那就看看一些具体的ViewGroup,还是选择比较简单的FrameLayout来分析,它的onLayout()又调用了layoutChildren()来完成子View的布局工组,具体实现如下:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
......
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
......
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
子View的layout又会递归这个过程,递归的出口就是到了具体的View。值得注意的一点是,在测量完成后,mMeasuredWidth,mMeasuredHeight被赋值,即我们调用getMeasuredWidth()、getMeasuredHeight()可得到的值。而layout完成后,mLeft、mTop、mRight、mBottom这四个成员变量被赋值,这将影响getWidth()、 getHeight()得到值,下面是它们的实现:
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
measure的结果与最终View的宽高绝大部分是情况下相等的,只是它们被赋值的时机不同,这点也告诉我们,在自定义View要通过getWidth()、 getHeight()获取控件的宽高,就要等layout完成。
二.绘制
前面已经分析了View的测量、布局的过程,剩下的绘制过程就相对简单了。先回顾一下它的大致流程:ViewRootImpl#performDraw()->ViewGroup#draw()->dispatchDraw()->View#draw()->onDraw(),ViewGroup没用重写draw()方法,具体的实现在View中。
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
从方法的注释就可以看出来它遵循下面几个步骤:
(1)绘制背景;
(2)画自己,即调用onDraw
(3)画子View,通过dispatchDraw
(4)画装饰,如scrollbars。
ViewGroup通过dispatchDraw()来绘制子View,在dispatchDraw()中子View的draw()会被调用,draw()中会调用onDraw,这样我们写在onDraw中的绘制代码就得到了执行。大致的流程总结为:
(1)ViewGroup的draw()方法,开始于DecorView的draw,这是个FrameLayout;
(2)DecorView的draw方法绘制背景、onDraw()画它自己、装饰,并且调用dispatchDraw()来绘制子View;
(3)在dispatchDraw()中子View的draw()被调用,如此反复到画完所有该画的View。