美文网首页
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