View三大流程的发起点

当Activity对象被创建完毕后,会将DecorView添加到Window中(Window是对窗口的抽象,DecorView是一个窗口的顶级容器View,其本质是一个FrameLayout,同时会创建ViewRootImpl(ViewRoot的实现类)对象,并将ViewRootImpl与DecorView建立关联。View的绘制流程从ViewRoot的performTraversals方法开始,经过measure、layout、draw三大过程完成对一个View的绘制工作。peformTraversal方法内部会调用measure、layout、draw这三个方法,这三个方法内部又分别调用onMeasure、onLayout、onDraw方法。

View的绘制流程图

Android View 判断是否在屏幕上显示 判断view是否可见_Layout

/**
     * ViewRootImpl.performTraversals 方法
     */
    private void performTraversals() {
        final View host = mView;
        if (host == null || !mAdded)
            return;
        // 更新标记位, 正在 Traversal
        mIsInTraversal = true;

        // ... 这里省略了数百行代码
        WindowManager.LayoutParams lp = mWindowAttributes;
        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                // 这个 host 为 Window 下的第一个 View, 它的宽高一般就是制定的 Window 的宽高
                // ViewRootImpl, 其实就是用于处理 Window 下最直接的 View, 用 ViewRootImpl 来命名就显而易见了
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                
                // 1. 调用 performMeasure 开启 View 树的测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                // Implementation of weights from WindowManager.LayoutParams
                // We just grow the dimensions as needed and re-measure if
                // needs be
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;
                // 判断是否需要重新 performMeasure
                // 保证宽度权重大于0
                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                // 保证高度权重大于0
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (measureAgain) {
                    // 重新测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
                layoutRequested = true;
            }
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        if (didLayout) {
            // 2. 调用 performLayout 确定 View 的位置
            performLayout(lp, mWidth, mHeight);
        }
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
        if (!cancelDraw && !newSurface) {
            // 3. 调用 performDraw 开启 View 的绘制
            performDraw();
        } else {
            if (isViewVisible) {
                // Try again
                scheduleTraversals();
            }
        }
        mIsInTraversal = false;
    }

1、ViewRootImpl.performMeasure 测量 View 的大小;若宽高的权重不大于0, 则需要重新测量 View 大小。
2、ViewRootImpl.performLayout 确定 View 摆放的位置。
3、ViewRootImpl.performDraw 开启 View 的绘制。
4、MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大小。

其中,Mode模式共分为三类

  • UNSPECIFIED:不对View进行任何限制,要多大给多大,一般用于系统内部;
  • EXACTLY:对应LayoutParams中的match_parent和具体数值这两种模式。检测到View所需要的精确大小,这时候 View的最终大小就是SpecSize所指定的值;
  • AT_MOST:对应LayoutParams中的wrap_content。View的大小不能大于父容器的大小。

对于DecorView,其确定是通过屏幕的大小,和自身的布局参数LayoutParams。

这部分很简单,根据LayoutParams的布局格式(match_parent,wrap_content或指定大小),将自身大小,和屏幕大小相比,设置一个不超过屏幕大小的宽高,以及对应模式。

对于其他View(包括ViewGroup),其确定是通过父布局的MeasureSpec和自身的布局参数LayoutParams。

parentSpeMode

/

subView Size

UNSPECIFIED

EXACTLY

AT_MOST

MATCH_PARENT

0

UNSPECIFIED

parentLeftSize

EXACTLY

parentLeftSize

AT_MOST

WRAP_CONTENT

0

UNSPECIFIED

parentLeftSize

AT_MOST

parentLeftSize

AT_MOST

具体大小

childSize

EXACTLY

childSize

EXACTLY

childSize

EXACTLY

当子View的LayoutParams的布局格式是wrap_content,可以看到子View的大小是父View的剩余尺寸,和设置成match_parent时,子View的大小没有区别。为了显示区别,一般在自定义View时,需要重写onMeasure方法,处理wrap_content时的情况,进行特别指定。

Measure流程
/**
     * ViewRootImpl.performMeasure
     */
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        try {
            // 很简单直接调用了 mView 的 measure 方法
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    
    /**
     * View.measure
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
        // 1 判断是否开启了强制 Layout
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        // 2 与缓存的 mOldMeasureSpec 进行一系列的比较判断得到最终的Flag needsLayout
        // 2.1 判断 MeasureSpec 是否与缓存的一致
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        // 2.2.1 判断 MeasureSpec 的测量模式是否为 MeasureSpec.EXACTLY 
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        // 2.2.2 判断 MeasureSpec 的测量值是否与当前View测量值一致
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        // 2.3 得出最终 Flag 的值
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        // 若添加了强制测量的 Flag 或者 needsLayout = true 才会走下面的代码
        if (forceLayout || needsLayout) {
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // 3.1 无缓存: 这里回调用我们最最熟悉的 onMeasure 
                onMeasure(widthMeasureSpec, heightMeasureSpec);
            } else {
                // 3.2 有缓存: 直接调用 setMeasuredDimensionRaw
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            }
        }
        // 更新缓存的 MesureSpec
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL);
    }

可以看到 measure 方法做了如下的事情:

1、获取 forceLayout 和 needLayout 这两个 Flag 的值
2、可以看到 needLayout 这里做了很多的判断, 可以根据其判断流程做一些优化, 防止过度 Measure

  • 判断 XXXMeasureSpec 是否与缓存的 mOldXXXMeasureSpec 一致
  • 判断 MeasureSpec 的测量模式是否为 MeasureSpec.EXACTLY
  • 判断 MeasureSpec 的测量值是否与当前View测量值一致

3、根据这个两个 Flag 来决定是否需要启动 View 的测量

  • 无缓存: onMeasure
  • 有缓存: setMeasuredDimensionRaw

4、ViewGroup extends View, ViewGroup 中默认是没有重写 onMeasure 的

所以当我们继承 ViewGroup 写自定义 View 的时候必须要重写 onMeasure 方法

一般步骤如下

/**
     * 重写 View 的 onMeasure 方法
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 1. 获取从上层传递下来的 Mode 和 Size
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        // 2. 遍历测量子 View 大小
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            // 调用了这个 measureChildWithMargins 
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        }
        // 3. 根据子 View 大小来设置自己的大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    }
    
    /**
     * ViewGroup.measureChildWithMargins
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // 测量 View 的大小, 连同其设置的 margin 参数
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        // 可以看到最终又回调了 View 的 measure 方法, 这样就一层一层的将 View 的  measure 传递下去了
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到 ViewGroup 中的 Measure 主要做了两件事情:

1、遍历子 View, 将 measure 操作向下分发

2、子 View 全部测量好之后, 根据自身布局的特性, 设置自身大小

接下来看看 View.onMeasure 方法

/**
     * View.onMeasure
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
            getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
        );
    }
    
    /**
     * View.getSuggestedMinimumWidth
     */
    protected int getSuggestedMinimumWidth() {
        // 获取建议的最小宽度, 即背景的宽度
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    
    /**
     * 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:// 未指定的
            // 当未指定的时候, View 的大小默认为 size, 通过上面得知为其 Background 的宽高
            result = size;
            break;
        // 可见如果不重写 View 的 onMeasure 方法的话, 其 wrap_content 的作用于 match_parent 是一样的
        case MeasureSpec.AT_MOST:// 最大的: wrap_content
        case MeasureSpec.EXACTLY:// 精确的: match_parent 和 10dp
            result = specSize;
            break;
        }
        return result;
    }
    
    /**
     * View.setMeasureDimension
     */
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        // 调用者这个方法
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    
    /**
     * View.setMeasuredDimensionRaw
     */
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        // 给成员变量赋值
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

总结:
Measure 操作的过程

1、将 MeasureSpec 经过自身处理后, 分发到下层子 View;
2、子 View 确定了大小之后, 再回溯到父容器中;
3、父容器结合子 View 大小和 自身布局特性 来确定自己的大小, 一直回溯到顶层;

可以看到 View 的 onMeasure 中还是有一些细节的, 在我们自定义 View 中起到至关重要的作用:

1、当测量模式为 MeasureSpec.UNSPECIFIED 时, View 的大小默认为 getSuggestedMinimumWidth() 即背景的宽高;
2、View 默认是不支持 wrap_content 属性的, 源码中它直接走到了下面的 MeasureSpec.EXACTLY 才得以 break, 我们需要重写 onMeasure, 让其支持 wrap_content;
3、当 View 的 setMeasureDimension() 执行后, 我们可以通过 getMeasureWidth/Height() 获取其初步的宽高值;

Layout流程
/**
     * ViewRootImpl.performLayout
     */
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mInLayout = true;
        final View host = mView;
        if (host == null) {
            return;
        }
        try {
            // 调用了 View.layout 方法, 将他的边界传入
            // 因为 ViewRootImpl 关联的是 Window 下的直接 View, 所以他的起始位置就是 0, 0
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mInLayout = false;
            // ... 省略了部分代码
        }
    }
    
    /**
     * ViewGroup.layout
     */
    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            // 回调了 View.layout 方法, 可见 ViewGroup 的 layout 方法并没有什么实际作用 
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
    
    /**
     * View.layout
     */
    public void layout(int l, int t, int r, int b) {
        // ... 
        // 1. 很重要的方法 View.setFrame()
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 2. 调用了 onLayout 方法
            onLayout(changed, l, t, r, b);
            // ...
        }
    }
    
    /**
     * View.setFrame
     */
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
            // Invalidate our old position
            invalidate(sizeChanged);
            // 1. 更新当前 View 四个顶点的位置, 更新了顶点位置之后, 便可以通过 getWidth/Height 来获取宽高了
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
        }
    }
    
    /**
     * View.onLayout
     */
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    /**
     * ViewGroup.onLayout
     */
    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

1、performLayout 调用了顶级 View 的 layout 方法
2、View.layout 方法会先调用 View.setFrame() 方法, 更新当前 View 四个顶点的坐标,
3、注意这个时候, 就已经可以通过 getWidth/getHeight 获取自身的宽高了, 但是还不能获取子 View 的宽高
Vew.layout 再调用 View.onLayout() 方法,
多肽性————若当前为 ViewGroup 实例, 则会调用 ViewGroup 的 layout 方法
这也就揭示了为什么 ViewGroup 必须重写 onLayout 的原因
4、调用了 ViewGroup 中的 onLayout 又会遍历子 View, 调用其 layout 方法, 这样每个 View 就确定了自己的位置, 是一个从上往下分发的过程

Draw过程
/**
     * ViewRootImpl.performDraw
     */
    private void performDraw() {
        // 只关注核心部分
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;
        mIsDrawing = true;
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    
    /**
     * ViewRootImpl.draw 
     */
    private void draw(boolean fullRedrawNeeded) {
        // 调用了drawSoftware
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }
    }
    
    /**
     * ViewRootImpl.drawSoftware 
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        mView.draw(cavans)
    }
    
    
    /**
     * View.draw
     */
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        // 这个 dirtyOpaque 非常重要, 它是回调 onDraw 方法的必要条件 
        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)
         */

        int saveCount;
        // 1. 绘制背景, 如果需要的话
        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
            // 2. 若满足条件则调用自身的 View.onDraw 方法
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            // 3. ViewGroup 中实现了该方法, 它会调用 drawChild
            // 从而回调 View.draw, 又回到该方法, 不断的往下分发
            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;
        }

如果 dirtyOpaque = true 的话是不会回调 onDraw 方法的,这也解释了为什么我们常常在 ViewGroup 中的 onDraw() 中绘图但无法得以执行的原因。
通过 setWillNotDraw(false) 来可以开启绘制

public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

View的绘制过程遵循如下几步:
1、绘制背景 background.draw(canvas)

2、绘制自己(onDraw)

3、绘制Children(dispatchDraw)

4、绘制装饰(onDrawScrollBars)

总结

从View的测量、布局和绘制原理来看,要实现自定义View,根据自定义View的种类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:onMeasure()方法,onLayout()方法,onDraw()方法。

onMeasure()方法:单一View,一般重写此方法,针对wrap_content情况,规定View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循环测量子View。

onLayout()方法:单一View,不需要实现该方法。ViewGroup必须实现,该方法是个抽象方法,实现该方法,来对子View进行布局。

onDraw()方法:无论单一View,或者ViewGroup都需要实现该方法,因其是个空方法。