View工作原理

来源:互联网 发布:java多线程与高并发 编辑:程序博客网 时间:2024/05/17 08:49

在自定义View的时候,掌握View的底层工作原理,也就是:View的measure、Layout、draw流程,可以帮助我做出比较有意思的自定义View。

因此,本文主要讲解:

View的工作原理,其中包括:测量流程、布局流程、绘制流程。

其中包括如下内容:

  • 基础知识准备:
    • 认识ViewRoot和DecorView
    • 理解MeasureSpec
    • MeasureSpec和LayoutParams的关系
  • View工作流程源码分析:
    • Measure过程源码分析
      • View的Measure过程
      • ViewGroup的Measure过程
    • Layout过程源码分析
    • draw过程源码分析
  • 常用的几种自定义控件方式

1.基础知识准备

1.1 认识ViewRoot和DecorView

1.1.1 认识ViewRoot

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,源码如下:

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

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过Measure、Layout和draw三个过程才能最终将一个View绘制出来

1.1.2 认识DecorView

DecorView作为顶级View,包含一个竖直方向的LinearLayout布局,这个Linearlayout布局包括两部分:上面的标题栏,下面的内容栏。我们在Activity中通过setContentView所设置的布局文件其实就是被加到了内容栏之中的。DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View。

1.2 理解MeasureSpec

MeasureSpec是什么呢?

MeasureSpec简单说是:测量规格。往细的说:MeasureSpec很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换为对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽、高。

MeasureSpec的组成:

MeasureSpec代表一个32位int值。
高2位代表:SpecMode。SpecMode代表测量模式
低30位代表:SpecSize。SpecSize代表在某种测量模式下的规格大小

下面看看MeasureSpec的源码:

public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        /**         * 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;        /**         * Creates a measure specification based on the supplied size and mode.         *         * The mode must always be one of the following:         * <ul>         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>         * </ul>         *         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's         * implementation was such that the order of arguments did not matter         * and overflow in either value could impact the resulting MeasureSpec.         * {@link android.widget.RelativeLayout} was affected by this bug.         * Apps targeting API levels greater than 17 will get the fixed, more strict         * behavior.</p>         *         * @param size the size of the measure specification         * @param mode the mode of the measure specification         * @return the measure specification based on size and mode         */        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,                                          @MeasureSpecMode int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }        /**         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED         * will automatically get a size of 0. Older apps expect this.         *         * @hide internal use only for compatibility with system widgets and older apps         */        public static int makeSafeMeasureSpec(int size, int mode) {            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {                return 0;            }            return makeMeasureSpec(size, mode);        }        /**         * Extracts the mode from the supplied measure specification.         *         * @param measureSpec the measure specification to extract the mode from         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},         *         {@link android.view.View.MeasureSpec#AT_MOST} or         *         {@link android.view.View.MeasureSpec#EXACTLY}         */        @MeasureSpecMode        public static int getMode(int measureSpec) {            //noinspection ResourceType            return (measureSpec & MODE_MASK);        }        /**         * Extracts the size from the supplied measure specification.         *         * @param measureSpec the measure specification to extract the size from         * @return the size in pixels defined in the supplied measure specification         */        public static int getSize(int measureSpec) {            return (measureSpec & ~MODE_MASK);        }        ......}

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,提供了打包和解包方法。

  • makeMeasureSpec可以根据SpecMode和SpecSize打包出对应的MeasureSpec。
  • getModegetSize可以从MeasureSpec解包出对应的SpecMode和SpecSize。

SpecMode的有三类:

  • UNSPECIFIED

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

  • EXACTLY

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

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

1.3 MeasureSpec和LayoutParams的关系

  • 对于顶级View:DecorView来说:

    其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定。

  • 对于普通View来说:

    其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高。

下面看一个源码分析:

对于DecorView来说,在ViewRootImpl的measureHierarchy方法中有一段代码,展示了DecorView的MeasureSpec创建过程:

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

其中的desiredWindowWidth,desiredWindowHeight是屏幕的尺寸。

接着在看下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;}

从上面的代码可以看出:

DecorView的MeasureSpec产生过程遵从如下规则:

  • LayoutParams.MATCH_PARENT:精确模式,大小就是窗口大小。
  • LayoutParams.WRAP_CONENT:最大模式,大小不变,但是不能超过窗口的大小。
  • 固定大小:精确模式,大小为LayoutParams中指定的大小。

对于普通View来说:View的Measure过程由ViewGroup传递而来,先看一下ViewGroup的measureChildWithMargins方法:

protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                    + widthUsed, lp.width);    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                    + heightUsed, lp.height);    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

从上面的代码可以看出,对子元素进行Measure,在调用子元素的Measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。由此可以看出,子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。

那getChildMeasureSpec又是如何来获得子元素的MeasureSpec的呢?上代码:

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

从上面的代码可以看出,它的主要作用是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec,参数中的padding是指父容器中已占用的空间,因此子元素可用的大小为父容器的尺寸减去padding。代码如下:

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

总的来说:

只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。

2 View工作流程源码分析

整体工作流程简介:

View的工作流程主要是指Measure、Layout、draw这三个流程,即:测量、布局、绘制,其中Measure确定view的宽、高,Layout确定View的最终宽高和四个顶点的位置,draw则将View绘制到屏幕上。

2.1 Measure过程源码分析

measure过程要分情况来看,如果是原始View,Measure方法就完成了其测量过程;如果是一个ViewGroup,除了完成自己的测量过程之外,还会遍历去调用所有子元素的Measure方法,各个子元素再递归去执行这个流程。

2.1.1 View的Measure过程分析

View的measure过程由其measure方法来完成,measure方法是一个final方法,在View的measure方法中回去调用View的onMeasure方法,因此需要看看onMeasure的实现,代码如下:

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

setMeasuredDimension方法会设置View宽高的测量值,因此需要看看getDefaultSize方法,代码如下:

/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */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;}

从getDefaultSize的方法可以看出:

  • 在AT_MOST和EXACTLY时,getDefaultSize返回的大小就是measureSpec的SpecSize,而这个SpecSize就是View测量后的大小。
  • UNSPECIFIED是,一般用于系统内部的测量过程,这时View的大小为getDefaultSize的第一个参数size,即宽高分别为getSuggestedMinimumWindth和getSuggestedMinimumHeight这两个方法的返回值。
    源码如下:
protected int getSuggestedMinimumWidth() {    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}protected int getSuggestedMinimumHeight() {    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());}

可以看出getSuggestedMinimumWidth的逻辑是这样的:

如果View没有设置背景,那么返回android:minWidth这个属性所指定的值,这个值可以为0;如果View设置了背景,则返回android:minWidth和背景的最小宽度两者中的最大值,getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽、高。

从getDefaultSize方法的实现看,View的宽高由specSize决定,可以得出如下结论:

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。

2.1.2 ViewGroup的Measure过程分析

对ViewGroup来说,除了完成自己的measure过程之外,还会遍历去调用子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫做measureChildren的方法,如下所示:

/** * Ask all of the children of this view to measure themselves, taking into * account both the MeasureSpec requirements for this view and its padding. * We skip children that are in the GONE state The heavy lifting is done in * getChildMeasureSpec. * * @param widthMeasureSpec The width requirements for this view * @param heightMeasureSpec The height requirements for this view */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);        }    }}

从上面的代码可以看到,ViewGroup在Measure时,会对每一个子元素进行measure,measureChild这个方法的实现也很好理解,代码如下:

/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding. * The heavy lifting is done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param parentHeightMeasureSpec The height requirements for this view */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);}

上述方法的目的就是,取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法来进行测量。

ViewGroup并没有定义其测量的具体过程,因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现。因为不同的布局,测量细节不同,ViewGroup无法统一实现。下面以Linearlayout的onMeasure方法来分析ViewGroup的measure过程。

首先看看LinearLayout的onMeasure方法,代码如下:

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

以measureVertical方法为例,源码如下:

 /**     * Measures the children when the orientation of this LinearLayout is set     * to {@link #VERTICAL}.     *     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.     *     * @see #getOrientation()     * @see #setOrientation(int)     * @see #onMeasure(int, int)     */    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {        mTotalLength = 0;        int maxWidth = 0;        int childState = 0;        int alternativeMaxWidth = 0;        int weightedMaxWidth = 0;        boolean allFillParent = true;        float totalWeight = 0;        final int count = getVirtualChildCount();        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        boolean matchWidth = false;        boolean skippedMeasure = false;        final int baselineChildIndex = mBaselineAlignedChildIndex;                final boolean useLargestChild = mUseLargestChild;        int largestChildHeight = Integer.MIN_VALUE;        int consumedExcessSpace = 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;            }            if (hasDividerBeforeChildAt(i)) {                mTotalLength += mDividerHeight;            }            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            totalWeight += lp.weight;            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;            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();                if (useExcessSpace) {                    // Restore the original height and record how much space                    // we've allocated to excess-only children so that we can                    // match the behavior of EXACTLY measurement.                    lp.height = 0;                    consumedExcessSpace += childHeight;                }                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                       lp.bottomMargin + getNextLocationOffset(child));                if (useLargestChild) {                    largestChildHeight = Math.max(childHeight, largestChildHeight);                }            }            /**             * If applicable, compute the additional offset to the child's baseline             * we'll need later when asked {@link #getBaseline}.             */            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {               mBaselineChildTop = mTotalLength;            }            // if we are trying to use a child index for our baseline, the above            // book keeping only works if there are no children above it with            // weight.  fail fast to aid the developer.            if (i < baselineChildIndex && lp.weight > 0) {                throw new RuntimeException("A child of LinearLayout with index "                        + "less than mBaselineAlignedChildIndex has weight > 0, which "                        + "won't work.  Either remove the weight, or don't set "                        + "mBaselineAlignedChildIndex.");            }            boolean matchWidthLocally = false;            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {                // The width of the linear layout will scale, and at least one                // child said it wanted to match our width. Set a flag                // indicating that we need to remeasure at least that view when                // we know our width.                matchWidth = true;                matchWidthLocally = true;            }            final int margin = lp.leftMargin + lp.rightMargin;            final int measuredWidth = child.getMeasuredWidth() + margin;            maxWidth = Math.max(maxWidth, measuredWidth);            childState = combineMeasuredStates(childState, child.getMeasuredState());            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;            if (lp.weight > 0) {                /*                 * Widths of weighted Views are bogus if we end up                 * remeasuring, so keep them separate.                 */                weightedMaxWidth = Math.max(weightedMaxWidth,                        matchWidthLocally ? margin : measuredWidth);            } else {                alternativeMaxWidth = Math.max(alternativeMaxWidth,                        matchWidthLocally ? margin : measuredWidth);            }            i += getChildrenSkipCount(child, i);        }        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {            mTotalLength += mDividerHeight;        }        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;        // Check against our minimum height        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());        // Reconcile our calculated size with the heightMeasureSpec        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;        // Either expand children with weight to take up available space or        // shrink them if they extend beyond our current bounds. If we skipped        // measurement on any children, we need to measure them now.        int remainingExcess = heightSize - mTotalLength                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;            mTotalLength = 0;            for (int i = 0; i < count; ++i) {                final View child = getVirtualChildAt(i);                if (child == null || child.getVisibility() == View.GONE) {                    continue;                }                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                final float childWeight = lp.weight;                if (childWeight > 0) {                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);                    remainingExcess -= share;                    remainingWeightSum -= childWeight;                    final int childHeight;                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {                        childHeight = largestChildHeight;                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement                            || heightMode == MeasureSpec.EXACTLY)) {                        // This child needs to be laid out from scratch using                        // only its share of excess space.                        childHeight = share;                    } else {                        // This child had some intrinsic height to which we                        // need to add its share of excess space.                        childHeight = child.getMeasuredHeight() + share;                    }                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                            Math.max(0, childHeight), MeasureSpec.EXACTLY);                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,                            lp.width);                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);                    // Child may now not fit in vertical dimension.                    childState = combineMeasuredStates(childState, child.getMeasuredState()                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));                }                final int margin =  lp.leftMargin + lp.rightMargin;                final int measuredWidth = child.getMeasuredWidth() + margin;                maxWidth = Math.max(maxWidth, measuredWidth);                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&                        lp.width == LayoutParams.MATCH_PARENT;                alternativeMaxWidth = Math.max(alternativeMaxWidth,                        matchWidthLocally ? margin : measuredWidth);                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));            }            // Add in our padding            mTotalLength += mPaddingTop + mPaddingBottom;            // TODO: Should we recompute the heightSpec based on the new total length?        } else {            alternativeMaxWidth = Math.max(alternativeMaxWidth,                                           weightedMaxWidth);            // We have no limit, so make all weighted views as tall as the largest child.            // Children will have already been measured once.            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {                for (int i = 0; i < count; i++) {                    final View child = getVirtualChildAt(i);                    if (child == null || child.getVisibility() == View.GONE) {                        continue;                    }                    final LinearLayout.LayoutParams lp =                            (LinearLayout.LayoutParams) child.getLayoutParams();                    float childExtra = lp.weight;                    if (childExtra > 0) {                        child.measure(                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),                                        MeasureSpec.EXACTLY),                                MeasureSpec.makeMeasureSpec(largestChildHeight,                                        MeasureSpec.EXACTLY));                    }                }            }        }        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {            maxWidth = alternativeMaxWidth;        }        maxWidth += mPaddingLeft + mPaddingRight;        // Check against our minimum width        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                heightSizeAndState);        if (matchWidth) {            forceUniformWidth(count, heightMeasureSpec);        }    }

从源码可以看出,系统会遍历子元素并对每个子元素执行measureChildBeoreLayout方法,这个方法内部会调用子元素的measure方法,这样各个子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。

针对竖直的LinearLayout而言,在水平方向的测量过程遵循View的测量过程,在竖直方向的测量过程则和View有所不同。

在View的Measure过程完成以后,通过getMeasuredWidth/Height方法就可以正确的获取到View的测量宽、高。

2.2 Layout过程源码分析

Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

先看看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);            if (shouldDrawRoundScrollbar()) {                if(mRoundScrollbarRenderer == null) {                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);                }            } else {                mRoundScrollbarRenderer = null;            }            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                ArrayList<OnLayoutChangeListener> listenersCopy =                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();                int numListeners = listenersCopy.size();                for (int i = 0; i < numListeners; ++i) {                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);                }            }        }        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;    }

layout方法的大致流程如下:
1. 首先通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop和mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了。
2. 接着调用onLayout方法,这样父容器就可以确定子元素的位置。

接下来看看onLayout方法,代码如下:

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

可以看到方法是个空方法,View和ViewGroup均没有真正实现onLayout方法,需要具体的View和ViewGroup去实现。我们看看Linearlayout的该方法:

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

依然后vertical和horizontal两个分支,我们选择Vertical这个分支来阅读。代码如下:

/**     * Position the children during a layout pass if the orientation of this     * LinearLayout is set to {@link #VERTICAL}.     *     * @see #getOrientation()     * @see #setOrientation(int)     * @see #onLayout(boolean, int, int, int, int)     * @param left     * @param top     * @param right     * @param bottom     */    void layoutVertical(int left, int top, int right, int bottom) {        final int paddingLeft = mPaddingLeft;        int childTop;        int childLeft;        // Where right end of child should go        final int width = right - left;        int childRight = width - mPaddingRight;        // Space available for child        int childSpace = width - paddingLeft - mPaddingRight;        final int count = getVirtualChildCount();        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;        switch (majorGravity) {           case Gravity.BOTTOM:               // mTotalLength contains the padding already               childTop = mPaddingTop + bottom - top - mTotalLength;               break;               // mTotalLength contains the padding already           case Gravity.CENTER_VERTICAL:               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;               break;           case Gravity.TOP:           default:               childTop = mPaddingTop;               break;        }        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,则仅仅是调用子元素的Layout方法而已,这样父元素在Layout方法中完成自己的定位以后,就通过onLayout方法去调用子元素的Layout方法,子元素又会通过自己的Layout方法来确定自己的位置,这样一层层的传递下去就完成了整个View树的layout过程。

setChildFrame方法代码如下:

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

2.3 draw过程源码分析

draw过程就是把View绘制到屏幕上,绘制过程遵循如下几步:
1. 绘制背景,background.draw(canvas)
2. 绘制自己 onDraw
3. 绘制children dispatchDraw
4. 绘制装饰 onDrawScrollBars

 /**     * Manually render this view (and all of its children) to the given Canvas.     * The view must have already done a full layout before this function is     * called.  When implementing a view, implement     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.     * If you do need to override this method, call the superclass version.     *     * @param canvas The Canvas to which the View is rendered.     */    @CallSuper    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);            // Overlay is part of the content and draws beneath Foreground            if (mOverlay != null && !mOverlay.isEmpty()) {                mOverlay.getOverlayView().dispatchDraw(canvas);            }            // Step 6, draw decorations (foreground, scrollbars)            onDrawForeground(canvas);            // we're done...            return;        }        /*         * Here we do the full fledged routine...         * (this is an uncommon case where speed matters less,         * this is why we repeat some of the tests that have been         * done above)         */        boolean drawTop = false;        boolean drawBottom = false;        boolean drawLeft = false;        boolean drawRight = false;        float topFadeStrength = 0.0f;        float bottomFadeStrength = 0.0f;        float leftFadeStrength = 0.0f;        float rightFadeStrength = 0.0f;        // Step 2, save the canvas' layers        int paddingLeft = mPaddingLeft;        final boolean offsetRequired = isPaddingOffsetRequired();        if (offsetRequired) {            paddingLeft += getLeftPaddingOffset();        }        int left = mScrollX + paddingLeft;        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;        int top = mScrollY + getFadeTop(offsetRequired);        int bottom = top + getFadeHeight(offsetRequired);        if (offsetRequired) {            right += getRightPaddingOffset();            bottom += getBottomPaddingOffset();        }        final ScrollabilityCache scrollabilityCache = mScrollCache;        final float fadeHeight = scrollabilityCache.fadingEdgeLength;        int length = (int) fadeHeight;        // clip the fade length if top and bottom fades overlap        // overlapping fades produce odd-looking artifacts        if (verticalEdges && (top + length > bottom - length)) {            length = (bottom - top) / 2;        }        // also clip horizontal fades if necessary        if (horizontalEdges && (left + length > right - length)) {            length = (right - left) / 2;        }        if (verticalEdges) {            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));            drawTop = topFadeStrength * fadeHeight > 1.0f;            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;        }        if (horizontalEdges) {            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));            drawLeft = leftFadeStrength * fadeHeight > 1.0f;            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));            drawRight = rightFadeStrength * fadeHeight > 1.0f;        }        saveCount = canvas.getSaveCount();        int solidColor = getSolidColor();        if (solidColor == 0) {            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;            if (drawTop) {                canvas.saveLayer(left, top, right, top + length, null, flags);            }            if (drawBottom) {                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);            }            if (drawLeft) {                canvas.saveLayer(left, top, left + length, bottom, null, flags);            }            if (drawRight) {                canvas.saveLayer(right - length, top, right, bottom, null, flags);            }        } else {            scrollabilityCache.setFadeColor(solidColor);        }        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children        dispatchDraw(canvas);        // Step 5, draw the fade effect and restore layers        final Paint p = scrollabilityCache.paint;        final Matrix matrix = scrollabilityCache.matrix;        final Shader fade = scrollabilityCache.shader;        if (drawTop) {            matrix.setScale(1, fadeHeight * topFadeStrength);            matrix.postTranslate(left, top);            fade.setLocalMatrix(matrix);            p.setShader(fade);            canvas.drawRect(left, top, right, top + length, p);        }        if (drawBottom) {            matrix.setScale(1, fadeHeight * bottomFadeStrength);            matrix.postRotate(180);            matrix.postTranslate(left, bottom);            fade.setLocalMatrix(matrix);            p.setShader(fade);            canvas.drawRect(left, bottom - length, right, bottom, p);        }        if (drawLeft) {            matrix.setScale(1, fadeHeight * leftFadeStrength);            matrix.postRotate(-90);            matrix.postTranslate(left, top);            fade.setLocalMatrix(matrix);            p.setShader(fade);            canvas.drawRect(left, top, left + length, bottom, p);        }        if (drawRight) {            matrix.setScale(1, fadeHeight * rightFadeStrength);            matrix.postRotate(90);            matrix.postTranslate(right, top);            fade.setLocalMatrix(matrix);            p.setShader(fade);            canvas.drawRect(right - length, top, right, bottom, p);        }        canvas.restoreToCount(saveCount);        // 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);    }

View绘制过程的传递是通过dispatchDraw方法来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递下去。

3. 常用的自定义控件分析

  1. 继承View重写onDraw方法
  2. 继承ViewGroup派生特殊的Layout
  3. 继承特定的View(比如TextView)
  4. 继承特定的ViewGroup(比如LinearLayout)
原创粉丝点击