View绘制流程

前面说到过requestLayout()方法,而这个方法会触发performTraversals()方法

这个方法则开始View的绘制流程,这个方法分别触发performMeasure,performLayout与performDraw方法,完成measure(测量),layout(布局定位),draw(绘制)这三大流程

接下来来分析这三个流程

1.measure测量流程

measure()是final方法,故不能被继承重写,而measure方法会去调用onMeasure方法,

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension设置View宽高的测量值

看下getDefaultSize这个方法

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

当MeasureSpec的Model为AT_MOST与EXACTLY时候返回大小值为MeasureSpec的specSize值

当为UNSPECIFIED则使用外面传进来的size

这个外面传进来的size通过getSuggestedMinimumWidth方法获取,具体实现就不看了,主要是View没有设置背景,那么这个值返回android:minWidth这个值所指定的值,如果设置了的话,则返回返回android:minWidth与背景最小宽度中的最大值

上面介绍了View的measure方法,接下来说说关于ViewGroup的测量过程

对于ViewGroup来说除了完成自身的measure,还需要对子View进行measure,而ViewGroup并没有提供onMeasure方法,因为不同的ViewGroup的测量方式不同,但是它提供了measureChildren方法,如下

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } 

这里首先获取子View的layoutParams,通过getChildMeasureSpec获取子元素的MeasureSpec,然后传递给View的measure方法来进行测量

2.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);
        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;
}

layout代码如上,主要是确定View自身的位置,onLayout方法则用来确定子元素的位置,所以如果是自定义ViewGroup则需要重写这个onLayout方法

3.draw过程

View绘制流程遵循如下几部

  • 1.绘制背景
  • 2.绘制自己
  • 3.绘制children
  • 4.绘制装饰

代码如下

    public void draw(Canvas canvas) {
        ...

        // 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);

            // 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);

            // we're done...
            return;
        }

       ...
    }

到这里基本就简单的介绍了关于View的测量过程,但是我们会有一些疑问,

1.MeasureSpec从哪里来的?

2.View触发绘制的流程?

要理解这两个问题那就要从绘制起始地方看起了就是requestLayout方法,requestLayout方法调用了scheduleTraversals方法,这个方法间接调用了performTraversals()方法,我们看下,这个方法,

private void performTraversals() { 
...... 
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
...... 
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...... 
performDraw();
......
}

由于这个方法太长,我仅仅将我们需要的代码展示出来了

可以看到我们通过getRootMeasureSpec方法获取MeasureSpec,而我们传递给getRootMeasureSpec方法的两个参数mWith和mHeight 是屏幕的宽度和高度, lp是WindowManager.LayoutParams,它的lp.width和lp.height的默认值是MATCH_PARENT

我们再看下getRootMeasureSpec方法如下

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

所以通过getRootMeasureSpec 生成的测量规格MeasureSpec 的mode是MATCH_PARENT ,size是屏幕的高宽。

接下来就是调用performMeasure方法,这个方法其实是调用DecorView的measure方法,而DecorView的是继承FrameLayout的,所以会调用到FrameLayout的measure方法,而这个方法的具体实现则在onMeasure方法中

而FrameLayout的onMeasure方法则会调用measureChildWithMargins方法,从而对子View进行View的measure测量

所以通过这样的解读就知道整个界面的绘制流程了,同时也清楚了关于MeasureSpec的由来

你可能感兴趣的:(View绘制流程)