View的测量、布局和绘制过程

作者: nick_young | 来源:发表于2017-12-13 20:47 被阅读30次

    写在前面的话

            按照之前写的节奏来的话,这篇改对View的整个测量、布局和绘制过程进行分析了。在之前的Activity显示到Window的过程中了解到performTraversals()这个方法会执行performMeasure()去测量View的大小,performLayout()去将子View放到合适的位置上,performDraw()将View真正绘制出来。

    1. measure的过程

    1.1 在测量前,先看下MeasureSpec

    MeasureSpec理解为测量规格,从源码中可以知道测量规格包括了测量模式(SpecMode)和大小(SpecSize),这个规格通过一个int型来表示。其中int的高2位代表了测量模式,低30位代表了大小。我们都知道两位可以有四种组合情况,而Android中View有三种测量模式,分别是:

    • UNSPECIFIED(0 << 30):子View可以想要任意大小
    • EXACTLY(1 << 30):父容器已经检测出子View所需要的精确大小,View的大小即为SpecSize的大小,他对应于布局参数中的MATCH_PARENT,或者精确值
    • AT_MOST(2 << 30):父容器指定了一个大小,即SpecSize,子View的大小不能超过这个SpecSize的大小
      通过测量规格获取测量模式和大小:
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
    // 获得SpecMode
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    // 获得SpecSize
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
    

    从上面可以看到,获得SpecMode时,需要和MODE_MASK(0x30000000)进行与运算,因为低30位全为0,高2位都为1,所以最终的结果就是高2位<<30的值,也就是我们三个测量模式中的一个。
    同理,在获得SpecSize时,我们需要将SpecMode去除,获得低30位的值,所以这里进行的是与上非MODE_MASK运算,即获取低30位的值(SpecSize)。

    1.2 getRootMeasureSpec方法

    在执行performMeasure()方法前,会执行ViewRootImpl中的getRootMeasureSpec方法,通过这个方法来获得跟布局的测量规格。

    // mWidth和mHeight的值是通过
    //if (mWidth != frame.width() || mHeight != frame.height()) {
    //mWidth = frame.width();
    //mHeight = frame.height();
    //}
    //赋值,这里等于Window窗口的宽高
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        //rootDimension是decorView的params的参数,这里为MATCH_PARENT,所以测量模式是EXACTLY
        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;
    }
    

    1.3 View的measure方法

    在获得了宽高的测量规格后,将会执行performMeasure()方法,performMeasure()方法会调用DecorView的measure()方法,DecorView和其父类并没有重写这个measure方法,最终会调用View的measure方法。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //判断当前View的layoutMode是不是LAYOUT_MODE_OPTICAL_BOUNDS,这种情况很少
        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);
        }
        
        // 作为缓存的key
        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    
        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        
        // 需要布局
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
            resolveRtlPropertiesIfNeeded();
            // 如果是强制布局的话,则需要重新去调用onMeasure方法,否则去缓存中获取
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
    
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        // 放到缓存中
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
    
    // onMeasure中需要去设置测量的结果,View的默认实现是设置默认的大小,这个大小根据测量模式来确定
    // 如果是UNSPECIFIED:未指定的话则大小为建议的最小值
    // 如果是AT_MOST||EXACTLY,那么返回值为SpecSize
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    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;
        }
        // 真正为mMeasuredWidth和mMeasuredHeight赋值
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    // 为mMeasuredWidth和mMeasuredHeight赋值
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
    
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
    

    从代码中可以看到,如果我们需要进行布局的话,首先判断是否为强制布局,如果不是的话获得mMeasureCache中当前测量规格的位置。如果没有这个缓存,则说明需要去进行onMeasure方法去测量真正的宽高,最后将当前宽高的测量规格保存到缓存中。
    在调用View的onMeasure方法时,我们需要调用setMeasuredDimension方法来设置具体的宽高,当调用了这个方法后,会通过setMeasuredDimensionRaw方法来给mMeasuredWidth和mMeasuredHeight赋值,这样我们通过getMeasuredWidthAndState()获取mMeasuredWidth值或者通过getMeasuredWidth()来获取mMeasuredWidth & MEASURED_SIZE_MASK(0x00ffffff)值。
    上面写到的都是View里面关于测量的方法,从这里我们就看出来了,View的测量确定了View的四个点的位置以及测量的宽高。ViewGroup作为View的子类,其并没有重写onMeasure方法,作为ViewGroup的子类基本上都会重写onMeasure方法,通过onMeasure方法来测量子View的大小,通过子View的大小最终来确定自己的大小。
    下面是FrameLayout的测量过程:

    FrameLayout.java
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        // 如果当前的测量模式不是EXACTLY,则需要统计拥有MATCH_PARENT属性的子View
        // 在设置完成当前layout的宽高后,需要重新测量拥有MATCH_PARENT属性的子View
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
    
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            // 如果子View不是隐藏状态,则需要测量
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                // 测量子View
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 设置最大宽度,每个子View和当前的最大宽度进行比较
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
    
        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        // 为当前layout设置宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        // 如果有MATCH_PARENT属性的子View大于1的话,则需要重新去测量这些子View
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
    
                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
    
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    ViewGroup.java
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        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);
        // 两个测量规格都有了,接着通过child.measure去测量自身大小
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    ViewGroup.java
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
    
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
    
        switch (specMode) {
        // EXACTLY如果是精确大小的话,则根据child的大小来计算具体大小
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                // 如果设置有具体值,则结果设置具体值,模式为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                // MATCH_PARENT则设置父View的大小,模式为EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                // 结果设置成父View大小,并且测量模式设置成AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        
        // AT_MOST模式
        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                // 设置具体大小,并且测量模式是EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                // 设置成父View大小,并且模式是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                // 设置成父View大小,并且模式是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // UNSPECIFIED模式
        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                // 设置具体大小,并且测量模式是EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                // sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M
                // 通过targetSdkVersion来判断size设置为0还是父View的size,并且测量模式设置UNSPECIFIED
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                // 通过targetSdkVersion来判断size设置为0还是父View的size,并且测量模式设置UNSPECIFIED
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    View.java
    // 这个我的理解是解析size并使这个size拥有一个状态
    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            // 如果是AT_MOST模式,则需要判断需要的size和测量规格的SpecSize的大小
            // 如果需要的size>SpecSize,那么使用SpecSize,并且设置标志位为MEASURED_STATE_TOO_SMALL = 0x01000000
            // 这个标志为代表了当前测量规格的大小小于所需大小
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            // 如果测量模式是EXACTLY,那么这个结果就是specSize
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            // 其他情况都是所需的size
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        //MEASURED_STATE_MASK = 0xff000000,这里的结果都带有一个状态,这个状态的用处不详。。。
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }
    

    从上面FrameLayout的测量过程我们可以看到,整个流程如下:


    流程图

    测量过程:

    1. FrameLayout调用onMeasure方法,开始测量
    2. FrameLayout的onMeasure方法中调用了其父类的measureChildWithMargins方法去测量子View的大小
    3. measureChildWithMargins通过getChildMeasureSpec方法获得子View的测量规格后调用子View的measure方法
    4. 子View通过调用onMeasure方法最终通过setMeasuredDimension设置具体的测量后的宽高
    5. FrameLayout在获得所有子View的结果后,获取其中的最大值,并且如果有背景图的话,获取子View和背景图的最大值,同样通过setMeasuredDimension设置FrameLayout的大小

    2. layout过程

            measure之后就会进行layout过程。layout其实就是对View的left、top、right、bottom这四个点位置的确定的过程。从源码可以看到,View中实现了layout方法,ViewGroup对其进行了Override,但是ViewGroup会调用super.layout(l, t, r, b),所以最终还是进入View的layout方法。
            在ViewGroup中onLayout是一个抽象方法,这就意味着所有的子类需要实现这个抽象方法。一般来说,每个不同的layout都有不同的实现,这样就构成了我们Android各种布局。当然了,自定义控件中关于onLayout的实现也是很重要的。下面还是关于FrameLayout的layout的实现:

    View.java
    public void layout(int l, int t, int r, int b) {
        // mPrivateFlags3的赋值是在measure方法中,多次测量是在不是强制layout并且有缓存的情况下进行赋值的
        // 这种情况需要重新调用onMeasure方法,对View重新设置大小
        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;
        // 是否使用视觉边界布局效果,这个并不影响这个流程,因为setOpticalFrame也会调用setFrame方法
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        // 如果有改变,则需要重新布局
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 调用onLayout方法进行布局
            onLayout(changed, l, t, r, b);
    
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
    
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            // 调用OnLayoutChangeListener的onLayoutChange方法
            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;
    }
    View.java
    // 返回值是boolean,代表是否位置改变了,如果和以前的位置不同,则说明改变了,需要重新布局
    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;
    
            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;
    
            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);
    
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
            mPrivateFlags |= PFLAG_HAS_BOUNDS;
    
            // 如果尺寸改变了,调用sizeChange方法,这里面会调用onSizeChanged方法
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
            
            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                // If we are visible, force the DRAWN bit to on so that
                // this invalidate will go through (at least to our parent).
                // This is because someone may have invalidated this view
                // before this call to setFrame came in, thereby clearing
                // the DRAWN bit.
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                // parent display list may need to be recreated based on a change in the bounds
                // of any child
                invalidateParentCaches();
            }
    
            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;
    
            mBackgroundSizeChanged = true;
            if (mForegroundInfo != null) {
                mForegroundInfo.mBoundsChanged = true;
            }
    
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        return changed;
    }
    
    FrameLayout.java
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    FrameLayout.java
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        // 获得当前View的四个可布局的点
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();
    
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
    
                int childLeft;
                int childTop;
    
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
                // 获得布局的方向,如果没有设置flag PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL,则为从左到右布局
                // 右到左布局在有些国家会出现这种情况
                final int layoutDirection = getLayoutDirection();
                // 获得当前布局的绝对显示位置,这里会根据布局方向来设置具体是从左边开始还是右边开始
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                // 垂直方法的显示位置
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                // 水平方向显示位置的设置
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    // 水平居中
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    // 从右边开始
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    // 左边和默认的情况都是左边开始布局
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                // 垂直方向的布局
                switch (verticalGravity) {
                    // 顶部开始
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    // 垂直居中
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    // 底部开始
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    // 默认顶部开始
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                // 子View布局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    
    layout流程图
            layout的过程就是确定当前View的left、top、right、bottom这四个点位置,通过这四个点可以确定这个View的位置,从而在绘制的时候正确绘制。layout的过程和measure的过程不同,layout的过程是先确定自己的位置在确定其子View的位置。

    3. draw

    draw过程在之前的Activity显示到Window的过程中有写到过,在整个过程中,会调用View的draw方法:

    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);
        }
        // 通常来算是跳过2和5部分。这里只看下跳过2和5部分时,整个流程
        // 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
            // 调用onDraw方法,去绘制内容
            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;
        }
    
        ......
    }
    

    从代码中我们可以了解到,整个绘制过程一共有6步,但是通常来说第2步和第5步不会调用:

    1. 绘制背景 drawBackground
    2. 保存画布图层
    3. 绘制内容 onDraw
    4. 分发绘制事件(绘制子View) dispatchDraw
    5. 绘制并恢复图层
    6. 绘制装饰 onDrawForeground

    3.1 drawBackground

    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        // 背景为空,直接返回
        if (background == null) {
            return;
        }
        // 设置背景的边界值
        setBackgroundBounds();
    
        // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mHardwareRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
    
            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }
        
        // 滚动的x和y值
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        // 没有滚动,直接绘制
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            // 将canvas移动后再绘制
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
    
    void setBackgroundBounds() {
        // 如果背景尺寸改变并且背景不为空,这设置其边界为0,0,width,height
        if (mBackgroundSizeChanged && mBackground != null) {
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }
    

            drawBackground方法比较简单,总体来说就是绘制View的背景,当然根据背景是否存在,是否页面滚动了来绘制背景。

    3.2 onDraw

             onDraw是没有具体实现的内容,一般来说在自定义View的时候,很多时候会重写onDraw方法来绘制真正要实现的内容。

    3.3 dispatchDraw

            从注释来看,dispatchDraw作为绘制子View的开始,其在View中是空实现。在ViewGroup中有dispatchDraw方法的具体实现:

    ViewGroup.java
    // 我们只关注如何绘制childView,内容省略了一大部分
    // 这里面可以看到会调用drawChild去绘制其子View
    @Override
    protected void dispatchDraw(Canvas canvas) {
        ......
        // 我们只关注如何绘制childView
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            ......
    
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                // 绘制其子View
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ......
    }
    ViewGroup.java
    // 子View会调用其返回值为boolean的draw方法去绘制
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
    View.java
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        // 关于硬件加速模式
        ......
        // 动画相关
        ......
        
        // 硬件加速相关,通过updateDisplayListIfDirty获取显示列表的renderNode最后下面绘制
        if (drawingWithRenderNode) {
            // Delay getting the display list until animation-driven alpha values are
            // set up and possibly passed on to the view
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.isValid()) {
                // Uncommon, but possible. If a view is removed from the hierarchy during the call
                // to getDisplayList(), the display list will be marked invalid and we should not
                // try to use it again.
                renderNode = null;
                drawingWithRenderNode = false;
            }
        }
        ......
        // 如果使用缓存去绘制,则通过cache绘制,否则还是会调用View的draw(canvas)方法绘制
        if (!drawingWithDrawingCache) {
            // 硬件加速的话通过drawRenderNode去绘制,在之前讲过了最后会调用View的draw(canvas)方法绘制
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                // 无背景的快速绘制
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    // 
                    dispatchDraw(canvas);
                } else {
                    // 调用View的draw(canvas)方法绘制
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == null) {
                    cachePaint = new Paint();
                    cachePaint.setDither(false);
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                // use layer paint to draw the bitmap, merging the two alphas, but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                if (alpha < 1) {
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                }
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                if (alpha < 1) {
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
        }
    
        ......
        return more;
    }
    

            绘制子View的过程要相对繁琐一些,通过View的另一个draw方法来绘制子View,并且这个方法包括了动画相关,硬件加速相关。上面的代码有一部分省略了,其中比较重要的是两个地方:

    1. 硬件加速相关过程:在之前的Activity显示到Window的过程中有写到过,主要是通过updateDisplayListIfDirty方法获取显示列表的renderNode最后通过硬件加速绘制。
    2. 软件绘制过程:需要判断是否使用缓存,如果使用缓存的话,直接绘制缓存,否则的话还需要按照上面的绘制流程一步步进行。

    3.4 onDrawForeground

            看这个名称可以认为是绘制前景,其中包括了滚动条、滚动指示器等。当然了,可以通过重写这个方法去绘制任何想要的前景。

    public void onDrawForeground(Canvas canvas) {
        // 滚动指示器绘制
        onDrawScrollIndicators(canvas);
        // 滚动条绘制
        onDrawScrollBars(canvas);
        // 前景
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {
            if (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = false;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
    
                if (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0, 0, getWidth(), getHeight());
                } else {
                    selfBounds.set(getPaddingLeft(), getPaddingTop(),
                            getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                }
    
                final int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                foreground.setBounds(overlayBounds);
            }
            
            // 前景的绘制
            foreground.draw(canvas);
        }
    }
    

    3.5 绘制顺序

    这里还是使用扔物线大神的一张图来表示绘制顺序吧。

    绘制顺序.jpg

    3.6 draw的总结

            整个绘制过程是一个自上向下的过程,在这个过程中先绘制自身的背景(drawBackground)内容(onDraw),接着绘制子View(dispatchDraw)。子View的绘制过程又和上面的过程一样,当所有的子View绘制完成后,会执行装饰的绘制(onDrawForeground)

    写在后面的话

            还是按照计划来的,整个流程已经写到了测量、布局和绘制的过程。总体来说感觉网上有些资料还是不够靠谱,如果自己不去看一遍的话,可能会有许多坑等着你来填。


    Go

    相关文章

      网友评论

        本文标题:View的测量、布局和绘制过程

        本文链接:https://www.haomeiwen.com/subject/fntoixtx.html