View的绘制流程学习记录

View的工作流程

1. DecorView被加载到Window中

当DecorView被创建后,要加载到Window中,会调用ActivityThread中的handleLaunchActivity()方法

handleLaunchActivity()方法

public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
···
        final Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            if (!r.activity.mFinished && pendingActions != null) {
                pendingActions.setOldState(r.state);
                pendingActions.setRestoreInstanceState(true);
                pendingActions.setCallOnPostCreate(true);
            }
        }
···

上面代码第1行调用performLaunchActivity()方法来创建Activity,这里会调用Ac的onCreate()方法,从而完成DecorView的创建

handleResumeActivity()方法

由于Android更新后这个方法不再由handleLaunchActivity()方法调用,但这个方法仍然很重要

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
···    
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
···
    if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
···

上面代码第1行调用performResumneActivity()方法,这个方法里会调用Ac的onResume()方法

第一个if块中第2行getDecorView()方法得到DecorView,第4行getWindowManager()得到WindowManager

WindowManager的实现类是WindowManagerImpl,所以倒数第1个if块中实际调用的是WIndowManagerImpl的addView()方法

WIndowManagerImpl的addView()方法

public final class WindowManagerImpl implements WindowManager {
    @UnsupportedAppUsage
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
···

在WindowManagerImpl的addView()方法中,又调用了WIndowManagerGlobal的addView()方法,如下

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
···      
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
···            

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

上面代码同步块中第1行创建了ViewRootImpl实例,try/catch中调用了ViewRootImpl的setView()方法,并将DecorView作为参数传进去,这样就把DecorView加载到了window中

但此时的界面什么都不会显示,因为view的工作流程还没有开始


2. ViewRootImpl的performTraversals()方法

ViewRootImpl通过setView()将DecorView加载到Window,ViewRootImpl还有一个performTraversals()方法,这个方法使得ViewTree开始View的工作流程

private void performTraversals() {
···
        if (baseSize != 0 && desiredWindowWidth > baseSize) {
              childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
              childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
              performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
···
        if (didLayout) {
              performLayout(lp, mWidth, mHeight);
···
        if (!cancelDraw) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            performDraw();
        }

三个if块中主要执行了3个方法,分别是performMeasure()、performLayout()、performDraw()。其内部又会调用View的measure()、layout()、和draw()方法。我们可以发现performMeasure()方法需要传进两个参数childWidthMeasureSpec,childHeightMeasureSpec。了解这两个参数需要了解MeasureSpec


3. 理解MeasureSpec

MeasureSpec是View的内部类,封装了View的规格尺寸,包括宽和高的信息,它的作用是在Measure的流程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再onMeasure()方法中根据这个MeasureSpec来确定View的宽和高。MeasureSpec的代码如下

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

从MeasureSpec的常量可以看出,它代表32位int值,其中高2位代表了SpecMode,低30位则代表SpecSize。SpecMode指的是测量模式,SpecSize指的是测量大小,SpecMode有三种模式,如下

UNSPECIFIED:未指定模式,View任意大小,父容器不做限制,一般用于系统测量

AT_MOST:最大模式,对应于wrap_content属性,子View的最终大小是父View指定的SpecSize值,并且子view的大小不能大于这个值

EXACTLY:精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是SpecSize值、

对于每一个View,都持有一个MeasureSpec,而MeasureSpec保存了该View的尺寸规格,在View的测量流程中,通过makeMeasureSpec来保存宽和高信息,通过getMode或getSize得到模式的宽和高。MeasureSpec是受自身LayoutParams和父容器MeasureSpec共同影响的,作为顶层View的DecorView是如何确定自己的MeasureSpec的呢?我们回到的ViewRootImpl的performTraversals()方法,查看getRootMeasureSpec()方法做了什么

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的第一个参数是窗口尺寸,所以对于DecorView来说,它的MeasureSpec由自身的LayoutParams和窗口的尺寸决定,这一点和普通View是不同的。往下就会看到根据自身的LayoutParams来得到不同的MeasureSpec

了解了DecorView,让我们回到performTraversals()方法,查看第1个重要方法performMeasure()

performMeasure()方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

代码很简单,我们知道performMeasure里调用的是mView的measure()方法,接下来学习一下measure()方法的工作流程


4. View的measure流程

View的measure()方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
···    
            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;
            }
···            
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
···

上面代码第1个if块中很明显调用了onMeasure()方法,那么我们就来看看onMeasure()做了什么

onMeasure()方法

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

很明显的两个方法setMeasuredDimension()和getDefaultSize()。接下来查看setMeasuredDimension()和getDefaultSize()

setMeasuredDimension()方法

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

measureWidth和measureHeight用来设置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;
    }

上面代码第2行SpecMode是View的测量模式,SpecSize是View的测量大小,这里根据不同模式返回不同result值,也就是SpecSize。

AT_MOST和EXACTLY模式下都返回SpecSize,即View在这两种模式下的测量宽和高直接取决于SpecSize。也就是说对于一个直接继承自View的自定义View来说,它的wrap_content和match_parent属性的效果是一样的。因此如果要实现自定义View的wrap_content,就要重写onMeasure()方法,并对自定义View的wrap_content值进行处理。

而在UNSPECIFIED模式下返回的是getDefaultSize方法的第一个参数size值,size值从onMeasure()方法来看是getSuggestedMinimumWidth()方法或getSuggestedMinimumHeight()方法得到的,我们来看看getSuggestedMinimumWidth()方法做了什么,这两个方法原理是一样的

getSuggestedMinimumWidth()方法

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

如果没有设置View的背景,则取值为mMinWidth,这个值是可以设置的,对应于Android:minWidth这个属性设置的值或者View的setMinimunWidth()方法,如果不指定的话默认为0。setMinimumWidth()方法如下

setMinimumWidth()方法

public void setMinimumWidth(int minWidth) {
        mMinWidth = minWidth;
        requestLayout();

    }

如果View设置了背景,则取值为max(mMinWidth,mBackground.getMinimumWidth()),也就是取mMinWidth和mBackground.getMinimumWidth()的最大值,这个mBackground是Drawable类型的,刚才说了mMinWidth,现在看看getMinimumWidth()方法

getMinimumWidth()方法

public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

intrinsicWidth得到的是这个Drawable的固有宽度,如果固有宽度大于0则返回固有宽度,否则返回0。

总结一下,getSuggestedMinimumWidth()方法:如果View没有设置背景,则返回mMinWidth。如果设置了背景,就返回mMinWidth和Drawable的最小宽度之间的最大值

View的measure流程到此结束


5. ViewGroup的measure流程

接下来看看ViewGroup的measure流程。

对于ViewGroup,它不止要测量自身,还要遍历地调用子元素的measure()方法。ViewGroup中没有定义onMeasure()方法,却定义了measureChildren()方法

measureChildren()方法

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍历子元素调用measureChild()方法,如下

measureChild()方法

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

上面代码第1行获取子元素的LayoutParams属性。下面两行获取子元素的MeasureSpec并最后调用measure()方法进行测量,getChildMeasureSpec()方法做了什么呢?如下

getChildMeasureSpec()方法

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) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                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.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                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.
                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.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 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
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                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
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

很显然,每一个大的case下都会有child的LayoutParams属性来得出子元素的MeasureSpec属性

有一点需要注意的是,如果父容器的MeasureSpec属性为AT_MOST,子元素的LayoutParams属性为wrap_content,那根据上面第2个case最后一个else if块处的代码,我们会发现子元素的MeasureSpec属性也为AT_MOST,他的SpecSize值为父容器的SpecSize减去padding值。换句话说,这和子元素设置LayoutParams属性为match_parent效果是一样的。为了解决这个问题,需要在LayoutParams属性为wrap_content时指定一下默认的宽和高。ViewGroup并没有提供onMeasure()方法,而是让其子类各自实现测量的方法,究其原因就是ViewGroup有不同的布局需要,很难统一

接下来我们简单分析一下ViewGroup的子类LinearLayout的measure流程。先看看他的onMeasure()方法

LinearLayout的onMeasure()方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

这个方法逻辑很简单,如果是垂直方向就调用measureVertical()方法,否则就调用measureHorizontal()方法,我们来看看measureVertical()方法

LinearLayout的measureVertical()方法

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        float totalWeight = 0;

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
···
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
···
        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;
···
    }

这段代码很长,简单说一下,开始定义了mTotalLength用来存储LinearLayout在垂直方向上的高度,然后for循环遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度。如果是wrap_content,则将每个子元素的高度和margin垂直高度等值相加并赋给mTotalLength。当然最后还要加上垂直方向的padding值。如果布局高度设置为match_parent或者具体数值,则和view的测量方法是一样的

ViewGroup的measure流程到此结束


6. View的layout流程

layout()方法的作用是确定元素的位置。ViewGroup中的layout()方法用来确定子元素的位置。View中的layout()方法用来确定自身位置。

来看看View的layout()方法

View的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);
···
            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);
                }
            }
        }

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
···
    }

layout()方法四个参数l、t、r、b分别代表是View从左、上、右、下相对于其父容器的距离,接着来看上面代码中间部分调用的setFrame()方法做了什么

setFrame()方法

protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        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);
···
        }
        return changed;
    }

setFrame()方法用传进来的l、t、r、b四个参数分别初始化mLeft,mTop,mRight,mBottom四个值,就确定了该View在父容器中的位置

调用了setFrame()方法后,View的layout()方法会接着调用onLayout()方法

View的onLayout()方法

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

onLayout()方法是一个空方法,确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()的方法,既然如此,我们看看LinearLayout的onLayout()方法

LinearLayout的onLayout()方法

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

与onMeasure()方法类似,根据方向调用不同方法,仍然查看垂直方向的layoutVertical()方法

LinearLayout的layoutVertical()方法
void layoutVertical(int left, int top, int right, int bottom) {
···       
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

这个方法会遍历子元素并在上面代码末尾部分调用setChildFrame()方法,其中childTop值是不断累加的,这样子元素才会依次按照垂直方向一个接一个排列下去而不是重叠,接着看setChildFrame()方法

setChildFrame()方法

private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

在这个方法中调用子元素的layout()方法确定自己的位置


7. View的draw流程

官方清楚的说明了draw的每一步流程

  1. 如果需要,则绘制背景
  2. 保存当前canvas层
  3. 绘制View的内容
  4. 绘制子View
  5. 如果需要,则绘制View的褪色边缘,类似于阴影效果
  6. 绘制装饰,比如滚动条

这里第2步和第5步可以跳过,重点分析其他步骤


7.1 绘制背景

绘制背景调用了View的drawBackground()方法

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

可以看到if语句块中考虑了背景偏移参数scrollX和scrollY,如果有偏移值不为0,则会在偏移后的canvas绘制背景


7.3 绘制View的内容

这里调用了View的onDraw方法,这个方法是个空实现,因为不同的View有不同的内容,需要我们自己去实现,在自定义View里重写该方法

onDraw()方法

protected void onDraw(Canvas canvas) {
    }

7.4 绘制子View

调用了dispatchDraw()方法,这个方法也是一个空实现

protected void dispatchDraw(Canvas canvas) {
    }

ViewGroup重写了这个方法,紧接着看ViewGroup的dispatchDraw()方法

ViewGroup的dispatchDraw()方法

protected void dispatchDraw(Canvas canvas) {
··· 
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
···

截取关键部分,在dispatchDraw()方法中对子类View进行遍历,并在第一个if块中调用drawChild()方法

drawChild()方法

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

child是View的对象,说明这里调用了View的draw()方法

View的boolean draw()方法

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
···
        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((RecordingCanvas) 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 {
                    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);
                }
            }
        }

我们看重点分析,上面代码第1个if块中判断是否有缓存,如果没有则正常绘制,如果有则利用缓存显示

中间部分,第二个else块中的draw(canvas)则是绘制自身,调用下面同名draw()方法

View的void draw()方法

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        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)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        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
            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 (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

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

7.6 绘制装饰

绘制装饰的方法为View的onDrawForeground()方法

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

很明显这个方法用于绘制ScrollBar以及其他装饰,并将他们绘制在视图内容的上层

到这里View的绘制流程结束了


同样,我们简单总结串联一下使用的方法有哪些吧

加载DecorView到window:
handleLaunchActivity()——handleResumeActivity()——WIndowManagerImpl的addView()——WIndowManagerGlobal的addView

理解MeasureSpec的流程:
ViewRootImpl的performTraversals()——getRootMeasureSpec()——MeasureSpec

View绘制的measure流程:
ViewRootImpl的performTraversals()——performMeasure()——View的measure()——onMeasure()——getSuggestedMinimumWidth()——setMinimumWidth() ||
getMinimumWidth()

onMeasure()——setMeasuredDimension()
onMeasure()——getDefaultSize()

View的layout流程:
View的layout()——setFrame()
View的layout()——View的onLayout()

View的draw流程:
View的drawBackground()——onDraw()——dispatchDraw()——ViewGroup的dispatchDraw()——drawChild()——View的boolean draw()——View的void draw()——View的onDrawForeground()

ViewGroup绘制的measure流程:
ViewGroup的measure()——measureChild()——getChildMeasureSpec()

LinearLayout的measure流程:
LinearLayout的onMeasure()——measureVertical() || measureHorizontal()

LinearLayout的layout流程
LinearLayout的onLayout()——LinearLayout的layoutVertical() || layoutHorizontal()——setChildFrame()


本文摘抄自《Android进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。


欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容