美文网首页
Android View 的工作流程

Android View 的工作流程

作者: SunnyGL | 来源:发表于2020-02-26 11:56 被阅读0次

    View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽/高,layout确定View的最终宽高和四个顶点的位置,而draw则将View绘制到屏幕上,三大流程都由ViewRoot发起调用。在搞清楚View的三大流程之前,我们首先必须要了解一下MeasureSpec,因为MeasureSpec在View的measure阶段至关重要。

    一、理解MeasureSpec

    1、MeasureSpec

    MeasureSpec是一个32位的int值,高两位代表SpecMode(测量模式),低30位代表SpecSize(在某种测量模式下的规格大小)。通过下面的代码,我们可以很清楚的看到MeasureSpec的相关定义。(代码中的sUseBrokenMakeMeasureSpec变量与我们本篇讨论的知识点无关,无需关注。)

    public class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK = 0x3 << MODE_SHIFT;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY = 1 << MODE_SHIFT;
        public static final int AT_MOST = 2 << MODE_SHIFT;
    
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
    
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }
    

    上面代码中,makeMeasureSpec方法将SpecMode和SpecSize打包成一个int值,以此来避免过多的对象内存分配。getMode和getSize方法为MeasureSpec的解包方法,分别可以单独获取到SpecMode和SpecSize。
    SpecMode值有三个,每一个都表示特殊含义,如下所示。

    UNSPECIFIED

    父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一 种测量的状态。

    EXACTLY

    父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是Size所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。

    AT_MOST

    父容器指定了一个可用大小即Size,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

    2、MeasureSpec创建

    View的MeasureSpec创建过程要将DecorView和普通View区分来看。我们知道,DecorView是Activity的顶级View,我们通过setContentView方法设置的布局,就是添加到DecorView下的。DecorView的MeasureSpec由屏幕的尺寸和其自身的LayoutParams共同决定。普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。下面会从源码角度对两者的MeasureSpec创建过程做具体分析。

    View的MeasureSpec创建于measure阶段,同时我们还知道View的三大流程都是由ViewRootImpl驱动,DecoreView作为最顶级View,其measure方法肯定是直接受ViewRootImpl调用。在ViewRootImpl的measureHierarchy方法中有如下一段代码,它展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸:

    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, 
                                               lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, 
                                                lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    

    接着再看一下getRootMeasureSpec方法的实现:

    private static int getRootMeasureSpec(int windowSize,
                                          int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize,
                        MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize,
                        MeasureSpec.AT_MOST);
                break;
            default:
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,
                        MeasureSpec.EXACTLY);
                break;
        }
        return measureSpec;
    }
    

    通过上面代码我们可以看到,DecorView的LayoutParams会影响其MeasureSpec的创建,根据自身设置的LayoutParams类型,MeasureSpec创建总结如下:

    • LayoutParams.MATCH_PARENT:Mode为精确模式,Size为窗口的大小;
    • LayoutParams.WRAP_CONTENT:Mode为最大模式,Size为窗口的大小;
    • 固定大小(比如100dp):Mode为精确模式,Size为LayoutParams中指定的大小。

    对于普通View,View的measure过程由ViewGroup传递而来。下面讨论中我们假设有一个ViewGroup,内部有一个View子控件这种测量场景。先看一下ViewGroup的measureChildWithMargins方法:

    protected void measureChildWithMargins(View child,
                                           int parentWidthMeasureSpec,
                                           int widthUsed,
                                           int parentHeightMeasureSpec,
                                           int heightUsed) {
        final ViewGroup.MarginLayoutParams lp =
                (ViewGroup.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(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    上述方法会首先调用getChildMeasure创建View的MeasureSpec,然后将MeasureSpec传到View的measure方法中对View进行最终测量。很显然,childWidthMeasureSpec和childHeightMeasureSpec的创建会和ViewGroup的MeasureSpec,View的LayoutParams,以及View的margin及padding有关,具体逻辑可以看一下ViewGroup的getChildMeasureSpec方法,如下所示。

    public static int getChildMeasureSpec(int spec, int padding,
                                          int childDimension) {
        int specMode = View.MeasureSpec.getMode(spec);
        int specSize = View.MeasureSpec.getSize(spec);
    
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
    
        switch (specMode) {
            case View.MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension
                        == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension
                        == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;
            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension
                        == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                } else if (childDimension
                        == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;
            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension
                        == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 
                            0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                } else if (childDimension
                        == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 
                            0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    

    上述方法看起来有点长,仔细看看其实不难,无非各种条件判断。首先看三个参数,spec为ViewGroup的MeasureSpec,padding为ViewGroup内已占用的空间大小,childDimension为View的LayoutParams。首先通过MeasureSpec的解包方法获取到ViewGroup的SpecMode和SpecSize,然后下面这行计算出ViewGroup目前剩余可用的空间大小。

    int size = Math.max(0, specSize - padding);
    

    接着定义的两个变量,resultSize和resultMode用来创建View的MeasureSpec,这两个值会在下面的switch方法体内赋值。在switch方法体内,首先判断ViewGroup的SpecMode,然后再判断View的SpecMode,最后给resultSize和resultMode赋值,最终调用makeMeasureSpec方法生成一个MeasureSpec,返回回去。细读switch内具体的判断逻辑,可以总结出下表,其中parentSize对应于上面代码中的size变量,childSize对应于上面代码中的childDimension参数。


    普通View的MeasureSpec的创建规则.png

    这里针对表格简单说一下,当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式 并且其大小遵循Layoutparams中的大小。当View的宽/高是match_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。至于UNSPECIFIED模式,这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。

    二、View的三大流程

    文章开头提到过,View的三大流程即measure,layout,draw,下面会从源码角度对每个流程进行详细分析。

    1、measure过程

    measure过程要将View和ViewGroup分情况来看,如果只是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所 有子元素的measure方法,各个子元素再递归去执行这个流程,下面针对这两种情况分别 讨论。

    1.1、View的measure过程

    View的measure过程由其measure方法来完成, measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法内,会有一系列缓存数据相关的判断,当需要对View进行测量时,会调用onMeasure方法,因此我们只需要看onMeasure方法即可。View的onMeasure方法代码如下。

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

    上述代码很简单,在onMeasure方法内调用了setMeasureDimension方法,此方法接收两个参数,这两个参数都为getDefaultSize方法的返回值,只是getDefaultSize方法的传参不同。setMeasureDimension方法会设置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;
    }
    

    对于我们来说,只需要关心AT_MOST和EXACTLY两种情况,通过上面的代码,我们可以看到,不管是AT_MOST还是EXACTLY模式,此时的返回结果都是specSize,而这个specSize就是父级控件对View测量后的大小。
    至于UNSPECIFIED这种情况,一般用于系统内部的测量过程,在这种情况下,View的大小为getDefaultSize的第一个参数size,即宽/高分别为getSuggestedMinimumWidth和 getSuggestedMinimumHeight这两个方法的返回值,两个方法的源码如下。

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

    上述两个方法逻辑一致,我们只看getSuggestedMinimumHeight方法。在getSuggestedMinimumHeight方法内,如果View没有设置背景,那么就返回mMinHeight,即View的高度为mMinHeight,而mMinHeight对应于android:minWidth这个属性所指定的值。如果View设置了背景,那么就返回max(mMinHeight, mBackground.getMinimumHeight()),Drawable的getMinimumHeight方法源代码如下。

    public int getMinimumHeight() {
        final int intrinsicHeight = getIntrinsicHeight();
        return intrinsicHeight > 0 ? intrinsicHeight : 0;
    }
    

    可以看出,getMinimumHeight返回的就是Drawable的原始高度,前提是这个Drawable有原始高度,否则就返回0。Drawable是否具有原始高度要看具体的Drawable类型,比如ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高(图片的尺寸)。
    1.2、ViewGroup的measure过程

    对于ViewGroup来说,除了需要对自身测量,还需要遍历去调用子View的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,且未重写View的onMeasure方法,因为不同的ViewGroup实现类对内部子View的测量,布局,绘制方法都不一样,所以onMeasure的实现方法在具体的ViewGroup实现类中。ViewGroup提供了一个叫measureChildren的方法,此方法内会调用子View的measure方法。

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

    从上述代码我们可以看到,在measureChildren方法内,会遍历ViewGroup内所有的子View,并对将需要占据屏幕空间的View和ViewGroup的MeasureSpec传递到measureChild方法内,measureChild方法源码如下。

    protected void measureChild(View child, int parentWidthMeasureSpec,
                                int parentHeightMeasureSpec) {
        final ViewGroup.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);
    }
    

    很显然,measureChild方法的逻辑就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的 measure方法来进行测量。getChildMeasureSpec的工作过程已经在上面进行了详细分析。
    前面我们说过,不同的ViewGroup实现类对于子View的测量,布局,绘制都会不同,所以其onMeasure方法具体实现都在其实现类中。这里我们只拿LinearLayout当作案例,做具体分析。LinearLayout的onMeasure方法源码如下。

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

    上述代码很简单,就是判断了一下LinearLayout的布局方向是垂直还是水平,这里我们只看垂直布局的具体测量,水平同理。measureVertical方法的代码很长,我们只看一下大概逻辑。首先在measureVertical方法中,会先对子View进行遍历测量,大概代码如下。

    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        ···
        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));
    }
    

    从上面代码我们可以看到,ViewGroup对子View进行了遍历,并调用了measureChildBeforeLayout方法,这个方法内部最终会调用子元素的measure方法,接着使用mTotalLength类变量存储LinearLayout当前的总高度。当对子View遍历测量结束后,LinearLayout会测量自己的大小,相关源码如下。

    mTotalLength += mPaddingTop + mPaddingBottom;
    int heightSize = mTotalLength;
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
    ···
    setMeasuredDimension(
            resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);
    

    垂直显示的LinearLayout,其横向宽度测量和普通View一致,竖向高度根据其LayoutParams不同分开来看,上述代码中,resolveSizeAndState方法包含这部分的逻辑,源代码如下。

    public static int resolveSizeAndState(int size, int measureSpec,
                                          int childMeasuredState) {
        final int specMode = View.MeasureSpec.getMode(measureSpec);
        final int specSize = View.MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case View.MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case View.MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case View.MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }
    

    上述方法的三个参数,size为遍历子View测量后的累计高度和背景图片高度的最大值,measureSpec为LinearView的heightMeasureSpec,childMeasuredState在高度测量时为0。通过上面代码,我们可以看到,当LinearLayout的高度指定为MATCH_PARENT或者具体数值时,高度为specSize,当LinearLayout的高度指定为WRAP_CONTENT时,高度为specSize和size的最小值。

    2、layout过程

    View的layout过程和measure,draw过程同样,都是先从上一级的ViewGroup开始,然后传递到子View。layout的过程主要涉及两个方法,layout方法确定View自身的位置,onLayout方法确定所有子View的位置。layout方法的代码在View中,部分代码如下。

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

    上述代码中,通过setFrame方法确定了View自身的位置,接着通过onLayout方法来调用子View的layout方法,确定子View的位置。onLayout方法和onMeasure方法一样,实现方法和具体的布局有关,这里我们就拿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);
        }
    }
    

    上面代码中,根据LinearLayout的方向去调用不同的方法,这里我们就看一下layoutVertical方法,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();
                ···
                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会确定子View的位置,在其内部只不过是调用了View的layout方法,setChildFrame的方法源码我会贴在下面。值得注意的是,setChildFrame方法的第三个参数为子View布局时的y轴位置,此参数为childTop变量和getLocationOffset方法的和,getLocationOffset方法默认返回0,此处不必关心,代码中下一行,childTop变量会不断的加上子View的高和margin值,getNextLocationOffset方法默认返回0,此处也不必关心,这正是垂直布局的LinearLayout对子View的layout逻辑。

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

    根据上面的分析,父View先通过layout方法确定自身的位置,然后通过onLayout方法确定子View的位置,这样一层一层的传递下去,就完成了整个View树的layout过程。

    3、draw过程

    View的draw流程比较简单,大体有四个步骤,总结如下:

    1. 绘制背景
    2. 绘制自己
    3. 绘制子View
    4. 绘制装饰

    其实上面的四个流程在源码注释中也给出了很详细的介绍。

    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);
       }
    
       // skip step 2 & 5 if possible (common case)
       final int viewFlags = mViewFlags;
       boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
       boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
       if (!verticalEdges && !horizontalEdges) {
          // Step 3, draw the content
          if (!dirtyOpaque) onDraw(canvas);
    
          // Step 4, draw the children
          dispatchDraw(canvas);
    
          drawAutofilledHighlight(canvas);
    
          // Overlay is part of the content and draws beneath Foreground
          if (mOverlay != null && !mOverlay.isEmpty()) {
             mOverlay.getOverlayView().dispatchDraw(canvas);
          }
    
          // Step 6, draw decorations (foreground, scrollbars)
          onDrawForeground(canvas);
    
          // Step 7, draw the default focus highlight
          drawDefaultFocusHighlight(canvas);
    
          if (debugDraw()) {
             debugDrawFocus(canvas);
          }
    
          // we're done...
          return;
       }
       ...
    }
    

    其中,dispatchDraw的具体实现在ViewGroup中,在其内部会对子View进行遍历,并调用子View的draw方法,从而完成整个绘制流程。

    三、为什么测量过程中使用MeasureSpec,而不是具体的数值?

    MeasureSpec记录在xml中给View设置的宽高属性,比如WARP_CONTENT,MATCH_PARENT,当View设置这些属性时,View最终测量出的宽高需要依赖于父View的宽高信息,所以由一个View的LayoutParams和父View的宽高信息生成MeasureSpec。
    MeasureSpec记录当前View的测量模式和可能取值。

    四、三大流程事件传递过程

    1、ViewRootImpl.PerformTraveals

    此方法中依次调用
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    performDraw();

    2、ViewRootImpl.performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    此方法中调用DecorView的measure方法,DecorView为FrameLayout。

    相关文章

      网友评论

          本文标题:Android View 的工作流程

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