美文网首页
View的绘制流程学习记录

View的绘制流程学习记录

作者: 打工崽 | 来源:发表于2021-03-23 21:17 被阅读0次

    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进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。


    欢迎指正。

    相关文章

      网友评论

          本文标题:View的绘制流程学习记录

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