图片 7

深入解析 Android 中 View 的工作原理

Android中的任何二个搭架子、任何一个控件其实都以直接或直接接轨自View实现的,当然也囊括大家在平时支付中所写的种种璀璨的自定义控件了,所以读书View的干活原理对于我们来说显得极度首要,本篇博客,大家将一同深刻学习Android中View的做事原理。

4.1 初始ViewRoot和DecorView
View的绘图流程是从ViewRoot的performTraversal的始发的。经过measure,layout,draw八个进度。
performTraversal会依次调用performMeasure,performLayout,performDraw来形成拔尖View的measure,layout和draw进程。performMeasure方法中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对富有的子成分实行measure进度,此时measure流程就从父容器传递到子成分了,那样就做到了二回measure进程,layout和draw的进程看似。
(1卡塔尔(قطر‎measure进度决定了View的宽高,大致在颇负景况下,那一个宽高都相当于View最后的宽高。getMeasuredHeight(卡塔尔,getMeasuredWidth(卡塔尔国来获得衡量宽高。
(2卡塔尔(قطر‎layout进程决定了View八个顶峰之处,和View实际的宽高。通过getHeight(卡塔尔国和getWidth(卡塔尔(قطر‎来收获实际的宽高。
(3State of Qatardraw进程决定view的显得。
DecorView是叁个FrameLayout,蕴含了二个竖直方向的LinearLayout,上边是标题栏,上面是内容。
4.1 理解MeasureSpec
(1State of QatarMeasureSpec和LayoutParams的照料关系。
在View度量的时候,系统会将LayoutParams在父容器的节制下调换来MeasureSpec,在依靠MeasureSpec来决定View衡量后的宽高。
MeasureSpec不是当世无双由LayoutParams决定的,而是和父容器一同决定的。对于DecorView,它的MeasureSpec是由窗口的轻重和小编的LayoutParams分明的。
(2卡塔尔普通view的MeasureSpec的创建法则
当View选用固定宽高时,不管父容器采取什么MeasureSpec,View的MeasureSpec都以精确方式,大小等于LayoutParams中的大小。
当View采用match_parent时,倘诺父容器是纯粹格局,那么view也是可信情势,大小是父容器剩余的深浅。要是父容器是最大形式,那么view也是最大方式,大小不超越父容器的盈余空间。
当View采用wrap_content时,不管父容器是什么形式,View都以最大格局,大小是不超过父容器剩余的深浅。
4.3 View的办事流程
(1)measure过程
view的度量进度由measure方法(final)实现,在measure方法中又会调用onMeasure方法,onMeasure会调用setMeasuredDimension方法,setMeasuredDimension会设置View宽高的衡量值。当View的SpecMode是AT_MOST和EXACTLY时,getDefaultSize再次回到的是measureSpec中的specSize的大大小小,而当View的SpecMode是UNSPECIFIED的时候
回去的是size,是getSuggestedMinimumWidth的再次回到值,假使View未有设置背景,那么重临的是android:midWidth这几个特性,若是设置了背景,再次来到的是android:minWidth和背景最小宽度中双方的的最大值。

  • 初识 ViewRoot 和 DecorView;
  • 理解 MeasureSpec;
  • View 的做事流程:measure、layout、draw。

ViewRoot和DecorView

1.ViewRoot对应于ViewRootImpl类,是接连WindowManager和DecorView的纽带,View的三大流程均是经过ViewRoot来产生的。在ActivityThread中,当Activity对象被创建达成后,会将DecorView增加到Window中,同期会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建构关联。

2.View的绘图流程从ViewRoot的performTraversals初始,经过measure、layout和draw四个经过才可以把一个View绘制出来,当中measure用来度量View的宽高,layout用来分明View在父容器中的放置地方,而draw则承当将View绘制到显示屏上。

3.performTraversals会依次调用performMeasure、performLayout和performDraw多个点子,那四个主意分别成功一流View的measure、layout和draw那三大流程。个中performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对具备子成分实行measure进度,那样就完事了叁次measure进程;子成分会重新父容器的measure进程,如此一再完毕了全方位View数的遍历。

图片 1

measure进度决定了View的宽/高,实现后可因而getMeasuredWidth/getMeasureHeight方法来获取View度量后的宽/高。Layout进程决定了View的八个极点的坐标和实际View的宽高,实现后可透过getTop、getBotton、getLeft和getRight获得View的八个定位坐标。Draw进程决定了View的呈现,实现后View的原委手艺显现到显示器上。

DecorView作为甲级View,经常景色下它此中含有了一个竖直方向的LinearLayout,里面分为多少个部分(具体意况和Android版本和核心有关),上面是标题栏,上边是内容栏。在Activity通过setContextView所设置的布局文件其实正是被加载到内容栏之中的。

//获取内容栏
ViewGroup content = findViewById(R.android.id.content);
//获取我们设置的Viewcontext.getChildAt(0);
DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传给我们的View。

图片 2

图片 3

图片 4hello,夏天

MeasureSpec

1.MeasureSpec十分的大程度上决定四个View的尺码规格,衡量进程中,系统会将View的layoutParams依照父容器所施加的规行矩步转换来对应的MeasureSpec,再依赖那个measureSpec来测量出View的宽/高。

2.MeasureSpec意味叁个三十三位的int值,高2位为SpecMode,低33人为SpecSize,SpecMode是指度量形式,SpecSize是指在某种度量方式下的条件大小。

MpecMode有三类;

1.UNSPECIFIED 父容器不对View举行任何约束,要多大给多大,经常用于系统里头

2.EXACTLY
父容器检查实验到View所须求的标准大小,那个时候View的最终大小正是SpecSize所钦赐的值,对应LayoutParams中的match_parent和求实数值那三种情势。

3.AT_MOST
父容器内定了叁个可用大小即SpecSize,View的大小无法超过这么些值,区别View达成差别,对应LayoutParams中的wrap_content。

当View选取固定宽/高的时候,不管父容器的MeasureSpec的是怎么,View的MeasureSpec都是纯正格局兵其尺寸信守Layoutparams的高低。
当View的宽/高是match_parent时,就算她的父容器的格局是标准格局,那View也是纯正形式还要大小是父容器的结余空间;要是父容器是最大形式,那么View也是最大形式还要起大小不会超越父容器的剩余空间。
当View的宽/高是wrap_content时,不管父容器的格局是纯粹依然最大化,View的格局总是最大化并且不能够超越父容器的剩余空间。

对于DecorView,它的MeasureSpec由Window的尺码和其自己的LayoutParams来七只鲜明,对于见惯司空的View,其MeasureSpec由父容器的MeasureSpec和本身的Layoutparams来合作明确。

对于 DecorView,在ViewRootImpl源码中的measureHierarchy犹如下一段代码:

.........
if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
.........

我们查阅一下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是如何产生的,rootDimension便是DecorView本身的LayoutParams,然后会依附那一个值实行推断
LayoutParams.MATCH_PARENT:DecorView的MeasureSpec被赋值为标准方式,DecorView的高低正是Window的高低

ViewGroup.LayoutParams.WRAP_CONTENT:DecorView的MeasureSpec被赋值为最大形式,DecorView的分寸不定,可是不能够赶上Window的尺寸

暗许意况:DecorView的MeasureSpec被赋值为标准形式,DecorView的大小为自己LayoutParams设置的值,也正是rootDimension

继之是对此常常的View,也正是结构中的View,它的Measure进度由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);
    }

在对子view举行measure早前会先调用getChildMeasureSpec方法来获取子view的MeasureSpec,从这段代码就能够看见来子view的MeasureSpec的规定与父容器的MeasureSpec(parentWidthMeasureSpec)还应该有作者的LayoutParams(lp.height和lp.width),还会有View自己的Margin和Padding有关

接下去翻看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;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

此间参数中的padding是指父容器的padding,这里是父容器所占用的空中,所以子view能使用的空中要减去那个padding的值。同有时间这一个艺术内部其实正是基于父容器的MeasureSpec结合子view的LayoutParams来规定子view的MeasureSpec

Paste_Image.png

为更加好的知道 View
的三大流程(measurelayoutdraw),先精通部分大旨的定义。

View的绘图流程

measure的过程

假设只是三个View,那么通过measure方法就马到功成了其衡量的进度,如若是多少个ViewGroup,除了衡量本身外,还有只怕会调用子孩子的measure方法

1.View的measure过程

View的measure进程由其measure方法成功,在那之中有上面一段内容

.........
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            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;
            }
.........

能够领略View的measure方法内,其实调用了本身的onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//里面有一个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;
    }

雷同大家只需求看MeasureSpec.AT_MOST和MeasureSpec.EXACTLY三种状态,那三种状态重临的result其实都以measureSpec中拿走的specSize,那些specSize就是View衡量后的大大小小,这里之所以是View衡量后的分寸,是因为View的末梢大小是在layout阶段鲜明的,所以要加已分别,日常景况下View衡量大小和终非常的大小是均等的。

UNSPECIFIED情形下,result的值正是getSuggestedMinimumWidth(卡塔尔方法和getSuggested迷你mumHeight(卡塔尔(قطر‎再次回到的值,查看那五个办法

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

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

}

从getSuggestedMinimumWidth代码能够见见,如若View没有安装背景,那么宽度就为mMinWidth,那一个值对应android:minWidth这么些特性所设定的值,纵然View设置了背景,则为max(mMinWidth,
mBackground.getMinimumWidth(卡塔尔(قطر‎卡塔尔(قطر‎

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

查看mBackground.getMinimumWidth(卡塔尔国方法,它事实上是Drawable的章程,固然intrinsicHeight也便是原来的肥瘦不为0,就赶回它,假设为0,就再次来到0。

从View的getDefaultSize方法能够得出结论:View的宽高由specSize决定,借使大家经过持续View来自定义控件须要重写onMeasure方法,并安装WRAP_CONTENT时的尺寸,不然在构造中应用WRAP_CONTENT约等于选用MATCH_PARENT

由来:因为View在构造中动用WRAP_CONTENT就也便是specMode为AT_MOST,而这种情景下,result
= specSize,那几个specSize的大小为parentSize,
parentSize就是父容器如今可用的轻重缓急,也正是父容器当前结余空间的大大小小,那当时和在构造中使用MATCH_PARENT效果是均等的

所以在AT_MOST格局下,大家日常都会给View设定暗许的里边宽高,并在WRAP_CONTENT时设置此宽高就可以。
能够因此翻看TextView、ImageView的源码,能够得到消息在WRAP_CONTENT下,onMeasure方法均做了奇特的拍卖,上边是TextView的onMeasure中的一段内容

if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(widthSize, width);
            }

2.ViewGroup的measure流程

ViewGroup是多少个抽象类,它未有重写View的onMeasure方法,而是本人提供了三个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方法去衡量每三个子成分的宽高

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

在对子view进行measure以前会先调用getChildMeasureSpec方法来获取子孩子的MeasureSpec,从这段代码就足以看见来子view的MeasureSpec的规定与父容器的MeasureSpec(parentWidthMeasureSpec和parentHeightMeasureSpec)还恐怕有小编的LayoutParams(lp.height和lp.width),还应该有View本人的Margin和Padding有关,最终正是调用子view的measure方法

ViewGroup并未有去定义度量的现实经过,那是因为ViewGroup是四个抽象类,其onMeasure方法必要种种子类去贯彻,因为各样ViewGroup的贯彻类,比方LinearLayout,RelativeLayout等的结构格局都以莫衷一是的,所以不只怕因人而宜的来写onMeasure方法。

接下去深入分析LinearLayout的onMeasure方法:

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

查看measureVertical方法

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

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

            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // Optimization: don't bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                if (lp.height == 0 && lp.weight > 0) {
                    // heightMode is either UNSPECIFIED or AT_MOST, and this
                    // child wanted to stretch to fill available space.
                    // Translate that to WRAP_CONTENT so that it does not end up
                    // with a height of 0
                    oldHeight = 0;
                    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).
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
                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);
        }

遍历子成分,调用他们的measureChildBeforeLayout方法,这几个艺术内会衡量子孩子的宽高,何况有三个mTotalLength来记录LinearLayout
在竖直方向的开端高度,每衡量一遍子成分,mTotalLength都会加多,扩张一些包蕴子成分的万丈甚至子成分竖直方向的margin

void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
里面调用了child.measure方法,也就是子孩子的measure方法
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);
}

当子成分度量完成后,LinearLayout会衡量本人的分寸,对于竖直的LinearLayout,它在档案的次序方向上的度量进度,遵守View的度量进程,在竖直方向上,借使利用的是match_parent或许具体的数值,那么它的度量进度和View的同等,即中度为specSize;就算它的布局中中度选取wrap_content,那么惊人是子成分所吞吃的莫斯中国科学技术大学学总和,但那些和不可能超越父容器的剩余空间,当然还要思量padding,竖直方向的结论能够从上边代码得悉:

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

不时候onMeasure中得到的衡量宽高只怕是不精确的,相比较好的习贯是在onLayout中去赢得View的度量宽高和终极宽高

在Activity中,在onCreate,onStart,onResume中均无法正确获得View的宽高新闻,这是因为measure和Activity的生命周期是不联合的,所以很也许View未有度量完成,得到的宽高是0.

measure总结

1.measure历程主要正是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的历程。具体measure宗旨首要犹如下几点:

2.MeasureSpec(View的中间类)衡量规格为int型,值由高2位规格格局specMode和低三十五人具体尺寸specSize组成。在那之中specMode独有二种值:

MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;

3.View的measure方法是final的,区别意重载,View子类只好重载onMeasure来实现本身的衡量逻辑。

4.最顶层DecorView衡量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法分明的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为大意显示器尺寸)。

5.ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了老爹和儿子View的尺码总结。

6.只倘使ViewGroup的子类就必须须要LayoutParams世襲子MarginLayoutParams,不然无法使用layout_margin参数。

7.View的结构大小由父View和子View协同决定。

8.运用View的getMeasuredWidth(卡塔尔(قطر‎和getMeasuredHeight(State of Qatar方法来得到View度量的宽高,必得确认保障这多个艺术在onMeasure流程之后被调用本事回去有效值。

layout的过程

ViewGroup的岗位明确后,它在onLayout中会遍历全部的子成分并调用子成分layout方法,子成分layout方法中又会调用onLayout方法,View的layout方法鲜明本身的地点,而onLayout方法方式显明子孩子之处

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);
            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方法的大概流程如下:首先会透过setFrame方法来规定mLeft;mTop;mBottom;

mRight;只要那多个点一旦分明,那么View在父容器中的地点就规定了,接着会调用onLayout方法,该方法指标是父容器来显明子成分的地点,无论是View依旧ViewGroup都未曾兑现onLayout方法,大家查阅LinearLayout的onLayout方法

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

翻开layoutVertical中重点代码

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的数值会一再的增大,那象征前面包车型地铁子成分还坐落靠下的岗位,适逢其会符合竖直的LinearLayout的特点,setChildFrame方法中唯独是调用了子成分的Layout方法而已

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

还要,会发觉setChildFrame中的width和height实际上就是子成分的度量宽高

final int childWidth = child.getMeasuredWidth();
             final int childHeight = child.getMeasuredHeight();

View的layout方法中会通过setFrame方法去设置子成分多个尖峰的职位,那样子成分之处就足以明确

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

接下去是View的getWidth和getHeight方法,结合在这之中的兑现,能够窥见他们分别重回的正是View衡量的大幅和冲天

@ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

    /**
     * Return the height of your view.
     *
     * @return The height of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getHeight() {
        return mBottom - mTop;
}

layout总结

1.layout也是从顶层父View向子View的递归调用view.layout方法的长河,即父View依据上一步measure子View所获得的结构大小和布局参数,将子View放在合适之处上。

2.View.layout办法可被重载,ViewGroup.layout为final的不行重载,ViewGroup.onLayout为abstract的,子类必须重载实现自个儿的职务逻辑。

3.measure操作完结后得到的是对各样View经度量过的measuredWidth和measuredHeight,layout操作完结现在得到的是对各类View进行岗位分配后的mLeft、mTop、mRight、mBottom,这一个值都是周旋于父View来讲的。

4.凡是layout_XXX的布局属性基本都对准的是包括子View的ViewGroup的,当对三个不曾父容器的View设置相关layout_XXX属性是未有任何意义的。

5.使用View的getWidth(卡塔尔和getHeight(卡塔尔国方法来赢得View度量的宽高,必需确认保障那七个法子在onLayout流程之后被调用技能回去有效值。

draw的过程

View的绘图进程信守以下几步:

1卡塔尔国绘制背景background.draw(canvas卡塔尔国
2卡塔尔(قطر‎绘制本人(onDraw)
3)绘制 children(dispatchDraw)
4卡塔尔绘制装饰(onDrawScrollBars)

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事件就一层一层的传递下去。

draw总结

1.假若该View是一个ViewGroup,则必要递归绘制其所蕴藏的装有子View。

2.View默许不会绘制任何内容,真正的绘图都急需本人在子类中达成。

3.View的绘图是依附onDraw方法传入的Canvas类来张开的。

4.在赢得画布剪切区(各类View的draw中传来的Canvas)时会自动管理掉padding,子View得到Canvas不用关爱那么些逻辑,只用关爱什么绘制就能够。

5.暗中认可意况下子View的ViewGroup.drawChild绘制顺序和子View被加多的一一一致,可是你也能够重载ViewGroup.getChildDrawingOrder(卡塔尔(قطر‎方法提供分裂顺序。

仿效资料
《Android开辟情势研究》

(2)ViewGroup的measure过程
viewGroup除了要达成自身的measure以外,还有可能会遍历去调用全部子成分的measure方法,各样子成分在递归完结此进程。ViewGroup是一个抽象类,因而它从不重写onMeasure方法。在onMeasure方法中取得的度量宽高是不规范的,在onLayout中获取衡量宽高也许最后的宽高。view的measure进程和Activity的生命周期方法不是协同施行的,因而不能保障Activity推行了onCreate、onStart、onResume时某些view已经度量完结了。要是view还不曾测量完毕,那么得到的宽高就都以0。上边是八种减轻该难题的艺术:
一:onWindowFocusChanged(卡塔尔:View已经伊始化实现,宽高已经计划好了。
二:view.post(runnable卡塔尔:通过post可以将叁个runnable投射到新闻队列的尾巴部分,然后等待Looper调用此runnable的时候,View就曾经初叶化好了。
三:ViewTreeObserver:使用ViewTreeObserver的众多回调方法能够产生这么些效率,举个例子动用onGlobalLayoutListener接口,当view树的情况产生转移依然view树内部的view的可以预知性爆发改换时,onGlobalLayout方法将被回调。伴随着view树的情事改动,这些办法也会被一再调用。
在view的暗中同意达成中,view的衡量宽高和尾声宽高是特别的,只不过度量宽高产生于measure进程,而最后宽高形成于layout进程。
(3)layout过程
ViewGroup的任务被鲜明未来,它在onLayout中会遍历全部的子成分并调用其layout方法,在layout方法中OnLayout方法又会被调用。layout方法的流水生产线:首先会透过setFrame方法来设定View的三个终端,View一旦鲜明,View在父容器中之处也被鲜明了,接着会调用onLayout方法,那几个措施的用处是显明子成分的地点。onLayout的切切实实落实和具体的构造有关。
(4)draw过程
1.制图背景:background.draw(canvas卡塔尔国;
2.制图本身:onDraw(卡塔尔国;
3.绘制children:dispatchDraw;
4.绘制装饰:onDrawScrollBars。
4.4 自定义view
(1卡塔尔(قطر‎世袭view重写onDraw方法供给团结援救wrap_content,并且padding也要协和解和管理理。世襲特定的View举个例子TextView无需思虑。
一而再自ViewGroup要在onMeasure和onLayout初级中学毕业生升学考试虑padding和子成分的margin对其引致的影响。
(2卡塔尔(قطر‎尽量不要在View中动用Handler,因为view内部自己已经提供了post连串的不二等秘书籍,完全能够替代Handler的作用。
(3卡塔尔view中一经无线程只怕动画片,必要在onDetachedFromWindow方法中马上止住。
(4State of Qatar管理好view的滑行冲突境况。
怎么处理wrap_content,原因?
举例在View在构造中运用wrap_content,那么它的specMode是AT_MOST,在此种方式下,它的宽高档于specSize,而specSize等于parentSize。parentSize是父容器这几天结余空间的轻重。和match_parent一致。

ViewRoot 对应于 ViewRootImpl 类,是连接 WindowManager
DecorView 的关节,View 的三大流程都以因此 ViewRoot 来达成的。

View 的绘图流程从 ViewRootperformTraversals 方法开首,它经过
measure(测量 View 的宽高),layout(鲜明 View 在父容器的任务卡塔尔 和
draw(负担将 View 绘制在显示器上卡塔尔(قطر‎ 多个经过技艺将叁个 View
绘制出来,如下:

图片 5performTraversals
的行事流程

DecorView 是一个 FrameLayout,View 层的风云都先经过
DecorView,再传递给 View。

DecorView 作为头号 View,日常它个中会含有五个竖直方向的
LinearLayout,上面是题目栏,上面是内容栏。在 Activity 中通过
setContentView 设置的布局文件正是被加到内容栏中,而内容栏的 id 为
content,可经过 ViewGroup content = findviewbyid(android.R.id.content)
得到 content,通过 content.getChildAt 得到设置的 View。其协会如下:

图片 6顶级
View:DecorView 的结构

MeasureSpec 十分的大程度上主宰了多少个 View 的尺寸规格。在 View
的衡量进程中,系统会将 View 的 LayoutParams
根据父容器所施加的法则转变来对应的 MeasureSpec,再依据那几个
measureSpec 来衡量出 View 的宽高(衡量宽高不自然等于 View
的终极宽高
)。

MeasureSpec 代表一个31位 int 值,高两位表示 SpecMode,低三十五位代表
SpecSize(某些度量形式下的法则大小),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;// MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); }}// 解包:获取其原始的 SpecMode@MeasureSpecModepublic static int getMode(int measureSpec) { return (measureSpec & MODE_MASK);}// 解包:获取其原始的 SpecSizepublic static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK);}

SpecMode 有三类,其含义分别如下:

  • UNSPECIFIED父容器不对 View 有任何的范围,表示一种衡量的情景

  • EXACTLY父容器检查测试出 View 的精度大小,那个时候 View 的末尾大小正是SpecSize 所钦定的值。它对应于 LayoutParams 中的 match_parent
    和切实的数值那三种情势

  • AT_MOST父容器钦命三个可用大小即SpecSize,View
    的大大小小不可能超出那些值。它对应于 LayoutParams 中的 wrap_content

Layoutparams 要求和父容器一同本事调整 View 的 MeasureSpec,一旦确定MeasureSpec 后,onMeasure 中就能够规定 View 的度量宽高。

一流 View(DecorView),其 MeasureSpec 由窗口的尺寸和自身的 Layoutparams
来一只决定;普通 View,其 MeasureSpec 由父容器的 MeasureSpec 和本身的
Layoutparams 来决定。

对于 DecorView,在 ViewRootImpl 中的 measureHierarchy
方法中的一段代码显示了其 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: // 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 的 MesourSpec 的产生进程,根据其 Layoutparams
的宽高的参数来划分,服从如下法规:

  • LayoutParams.MATCH_PARENT:准确情势,大小就是窗口的朗朗上口

  • LayoutParams.WRAP_CONTENT:最大方式,大小不定,不过不可能超出荧屏的尺寸

  • 恒定大小:正确方式,大小为 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); // 调用子元素的 measure 方法前会通过上面的 getChildMeasureSpec 方法得到子元素的 MesureSpec child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

上述对子成分进行 measure,鲜明,子成分的 MesureSpec 的创办和父容器的
MesureSpec 、子成分的 LayoutParams 有关和 View 的 margin 有关,此中
getChildMeasureSpec 方法如下:

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode; int specSize = MeasureSpec.getSize; // 参数中的 pading 是指父容器中已占有的控件大小 // 因此子元素可以用的大小为父容器的尺寸减去 pading int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch  { // 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 来规定子成分的 MesureSpec。

上面getChildMeasureSpec 显示了平时 View 的 MeasureSpec
创设法规,也可参谋下表(表中的 parentSize 指父容器中近日可应用的高低):

图片 7习感觉常
View 的 MeasureSpec 的创制法则

当 View 采纳固定宽/高时,不管父容器的 MeasureSpec 是哪些,View 的
MeasureSpec 都以标准格局同一时候其尺寸依据 LayoutParams 中的大小。

当 View 的宽/高是 match_parent 时,若父容器是精准方式,那么 View
也是精准格局同一时间其尺寸是父容器的剩余空间;若父容器是最大方式,那么 View
也是最大格局还要其尺寸不会超越父容器的结余空间。

当 View 的宽/高是 wrap_content
时,不管父容器的格局是精准依然最大化,View
的形式总是最大化,况且大小不可能抢先父容器的结余空间。

注:UNSPECIFIED 形式首要用来系统里面频仍 Measure
的情景,日常不需关切此方式。

综上,只要提供父容器的 MeasureSpec 和子成分的
LayoutParams,就足以急速地明确出子成分的 MeasureSpec 了,有了
MeasureSpec 就能够更进一层鲜明出子元素度量后的大小了。

View 的专门的学问流程首假如指 measure(衡量,显著 View
的衡量宽/高)、layout(布局,分明 View
的终极宽/高和两极分化的职分)、draw(绘制,将 View
绘制到显示屏上)那三大流程。

若只是两个原来的 View,那么通过 measure 方法就做到了其度量进度,要是一个ViewGroup,除了完毕本身的衡量进度外,还大概会遍历去调用全数子成分的 measure
方法,各种子成分再递归去施行那几个流程。

4.3.1.1 View 的 measure 过程

View 的 measure 过程由其 measure 方法来产生,measure 方法中会去调用 View
onMesure 方法如下:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 设置 View 宽/高的测量值 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }

其中 getDefaultSize 方法如下:

 public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch  { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }

上面的 AT_MOSTEXACTLY 那三种景况,可明白为 getDefaultSize
重临的尺寸正是 mesourSpec 中的 specSize,而以此 specSize 就是 View
衡量后的深浅(衡量大小不自然等于 View 的尾声大小)。

至于 UNSPECIFIED 这种气象,平日用来系统之中的度量进程,View 的大大小小为
getDefaultSize的首先个参数是 size,其宽/高获取情势如下:

protected int getSuggestedMinimumWidth() { // 1. 若 View 没有设置背景,View 的宽度为 mMinwidth, // 而 mMinwidth 对应于 android:minwidth 这个属性所指定的值, // 因此 View 的宽度即为 android:minwidth 属性所指定的值, // 若这个属性不指定,那么 mMinWidth 则默认为0; // 2. 若 View 指定了背景,则View的宽度为max(mMinwidth,mbackground().getMininumwidth) return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth;}protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight;}

地方注释深入分析了 getSuggestedMinimumWidth
方法的落实,getSuggestedMinimumHeight和它的法则同样。注释中未表达的
mBackground.getMinimumWidth() 方法(即 Drawable 的
getMinimumWidth方法)如下:

public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); // 返回 Drawable的原始宽度,否则就返回0 return intrinsicWidth > 0 ? intrinsicWidth : 0;}

总结 getSuggestedMinimumWidth 的逻辑:若 View 没设背景,那么再次回到
android:minwidth所钦赐的值;若 View 设了背景,则赶回
android:minwidth和背景的细小宽度这两个中的最大值。View 在
UNSPECIFIED 情形下的度量宽/高即为
getSuggestedMinimumWidthgetSuggestedMinimumHeight的归来值 。

结论:直白接轨 View 的自定义控件必要重写 onMeasure 方法并安装
wrap_content 时的自己大小,不然在布局中选择 wrap_content
就一定于选取 match_parent

从上述代码中级知识分子道,若 View 在布局中运用 wrap_content,那么它的 specMode
AT_MOST 格局,它的宽/高档于 specSize;此情景下 View 的 specSize
是 parentSize,而 parentSize
是父容器中如今能够运用的大小,即父容器当前剩下的上台湾空中大学小。明显,View
的宽/高就等于父容器当前剩余的半空中山大学小,这种作用和在构造中使用
match_parent 别无二样。

竭泽而渔上述难题代码如下:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec); // 给 View 指定一个默认的内部宽/高(mWidth, mHeight),并在 wrap_content 时设置此宽/高即可 // 对于非 wrap_content 情形,沿用系统的测量值即可 //(注:TextView、ImageView 等针对 wrap_content 情形,它们的 onMeasure 方法做了特殊处理) if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } }

4.3.1.2 ViewGroup 的 measure 过程

和 View 分裂的是,ViewGroup 是叁个抽象类,它未有重写 View 的 onMeasure
方法,但它提供了三个 measureChildren 方法:

 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; // ViewGroup 在 measure 时,会对每一个子元素进行 measure for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }

上述代码中的 measureChild 方法如下:

 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { // 1. 取出子元素的 LayoutParams final LayoutParams lp = child.getLayoutParams(); // 2. 通过 getChidMeasureSpec 来创建子元素的 MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); // 3. 将 MeasureSpec 直接传递给 View 的 measure 方法来进行测量 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

地点代码注释表达了 measurechild 的思想。

鉴于 ViewGroup 是一个抽象类,其衡量进度的 onMeasure
方法须要各种子类去具体完结;差异的 ViewGroup
子类有分裂的布局个性,它们的度量细节各不相符,如 LinearLayout 和
RelativeLayout 这两边的结构特性不相同,由此 ViewGroup 无法对其 onMeasure
方法做统一完成。

下边通过 LinearLayout 的 onMeasure 方法来分析 ViewGroup 的 measure
进程,先来看一下 LinearLayout 的 onMeasure 方法:

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

此处选用查看竖直方向的 LinearLayout 度量进度,即 measureVertical
方法(其源码相比较长就不贴了),这里只描述其大意逻辑:系统会遍历子成分并对各种子成分实施
measureChildBeforeLayout 方法,此方法内部会调用子元素的 measure
方法,当子元素衡量完成之后,LinearLayout
会依照子成分的景观来衡量本人的大大小小。

View 的 measure 进程做到后,通过 getMeasureWidth/Height
能够正确地获取到 View 的度量宽/高。但在系统要再三 measure
技能鲜明最后的衡量宽/高的情景下,在 onMeasure
方法中取得的衡量宽/高恐怕是不规范的。由此建议在 onLayout 方法中去获取
View 的度量宽/高恐怕最后宽/高。

问题:怎么着在 Activity 已运维的时候获得有些 View 的宽/高?

注:由于 View 的 measure 进程和 Activity
的生命周期方法不是合营实践的,不能保障 Activiy 推行了
onCreate、onStart、onResume 时有些 View 已经度量实现了,进而在
onCreate、onStart、onResume 中均不可能准确得View的宽/高新闻(若 View
尚未度量实现,那么得到的宽/高正是0)。

此处给出多种格局:

Activity/View#onWindowFocusChanged

onWindowFocusChanged主意是指:View
已开始化达成,宽/高已酌量好,那时去得到宽/高是没难题的(注:当 Activity
继续实践和间断推行时,onWindowFocusChanged 均会被调用,若频繁地进行
onResumeonPause,那么 onWindowFocusChanged
也会被频仍地调用)。标准代码如下:

 public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged; if (!hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }

view.post

透过 post 可将贰个 runnable 投递到音讯队列的尾巴部分,然后等待 Lopper 调用此
runnable 时,View 就开首化好了。标准代码如下:

 protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = mTextView.getMeasuredWidth(); int height = mTextView.getMeasuredHeight; }

ViewTreeObserver

使用 ViewTreeObserver 的不在少数回调可做到这些功用,标准代码如下:

 protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener; int width = mTextView.getMeasuredWidth(); int height = mTextView.getMeasuredHeight; }

view.measure(int widthMeasureSpec , int heightMeasureSpec)

由此手动衡量 View 的宽高,此格局较复杂,依据 View 的LayoutParams
来分情形来处理:

  • match_parent:不能衡量出切实可行的宽高

  • 切切实实的数值:如宽高都以100dp,如下 measure:

 int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec);
  • wrap_content:如下measure:

 // View 的尺寸使用30位的二进制表示,即最大是30个1,也就是 (1<<30)-1 int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); view.measure(widthMeasureSpec, heightMeasureSpec);

关于 View 的
measure,互联网上有三个错误的用法。为何说是大错特错的,首先其违反了系统的里边得以完成正式(因为不或许透过荒诞的
MeasureSpec 去得出客观的 SpecMode,进而招致 measure
进程出错),其次没办法作保 measure 出科学的结果。

  • 首先种错误的艺术:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(-1, View.MeasureSpec.UNSPECIFIED);int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(-1, View.MeasureSpec.UNSPECIFIED);view.measure(widthMeasureSpec, heightMeasureSpec);
  • 其次种错误的形式:

view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

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; // 1. 通过 setFrame 方法来设定 View 的四个顶点的位置, // 即初始化 mLeft,mTop,mRight,mBottom 这四个值 boolean changed = isLayoutModeOptical ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // 2. View 的四个顶点一旦确定,那么 View 在父容器的位置也就确定了, // 接下来会调用onLayout方法(用途:父容器确定子元素的位置) onLayout(changed, l, t, r, b); 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.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }

和 onMeasure 近似,onLayout 的具体地方完毕均等和现实结构有关,全体 View
和 ViewGroup 均未有真的的落实 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); } }

LinearLayout 的 onLayout 和 onMeasure 的贯彻逻辑相通,就 layoutVertical
来讲,其首要代码如下:

 void layoutVertical(int left, int top, int right, int bottom) { . . . final int count = getVirtualChildCount(); // 遍历所有子元素 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt; if (child == null) { childTop += measureNullChild; } 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 { childTop += mDividerHeight; } childTop += lp.topMargin; // 调用 setChildFrame 为子元素指定对应的位置 setChildFrame(child, childLeft, childTop + getLocationOffset, childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset; i += getChildrenSkipCount; } } }

上述措施中的 setChildFrame 方法,仅仅是调用子成分的 layout
方法而已,如下:

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

那般父成分在 layout 方法中做到自身的定点后,就透过 onLayout
方法去调用子成分的 layout 方法,子成分又会由此和谐的 layout
方法来明确本身的职位,那样一层一层传递下去实现全体 View 树的 layout
过程。

问题:View 的度量宽/高和尾声宽/高有怎么样界别?(即:View 的
getMeasureWidthgetWidth 那多个章程有啥样界别?)

为了酬答这些标题,先看下 getWidthgetHeight 方法的落到实处:

 public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; }

能够见到,getWidthgetHeight 再次来到的适逢其会是 View 的度量宽度、中度。

对于地点的标题:在 View 的暗中同意完成中,View
的度量宽/高和最终宽/高是相等的,只可是度量宽/高产生于 View 的 measure
进程,一个是 layout 进度,而结尾宽/高形成于 View 的 layout
进程,即双方的赋值机会比不上,度量宽/高的赋值时机微微早一些。

平凡支付中可用认为 View 的度量宽/高 =
最终宽/高,但有些特殊情状下,如重写 View 的 layout 方法如下:

 public void layout(int l,int t,int r, int b){ super.layout(l, t, r + 100, b + 100); }

上述代码会以致在此外景况下 View 的终极宽/高总是比度量宽/高大 100px。

Draw 进程其功能是将 View 绘制到荧屏方面。View 的绘图进程据守如下几步:

绘制背景 background.draw

绘图自身

绘制 children (dispatchDraw)

制图装饰 (onDrawSrcollBars卡塔尔国

那或多或少由此 draw 方法的源码可看出来:

 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; } // 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; // Step 4, draw the children dispatchDraw; // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty { mOverlay.getOverlayView().dispatchDraw; } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground; // we're done... return; } . . . }

View 绘制进度的传递是通过 dispatchDraw 来兑现的,dispatchDraw
会遍历全部子成分的 draw 方法,如此 draw 事件就一偶发地传递下去。View
有二个非常的办法 setwilINotDraw

public void setwilINotDraw(boolean willNotDraw){ // 若一个 View 不需要绘制任何内容,那么设置这个标记位为 true 以后,系统会进行相应的优化。 // 默认情况下,View 没有启用这个校化标记位,但 ViewGroup 会默认启用这个优化标记位。 setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

事实上开拓中,自定义控件世袭于 ViewGroup
并且本人不具备绘制功用时,就足以敞开这几个标识位进而便利系统开展两次三番的优化。若显明驾驭一个ViewGroup 须求经过 onDraw 来绘制内容时,供给显式地关闭 WILL_NOT_DRAW
那几个标志位。

发表评论

电子邮件地址不会被公开。 必填项已用*标注