View的工作原理(一)——从ViewRoot和DecorView说起

来源:互联网 发布:大学java专业入门课程 编辑:程序博客网 时间:2024/04/30 08:22

前言

本文参考《Android开发艺术与探索》第四章内容及网上几篇博客,里面融入笔者的个人理解。希望能对大家理解View有所帮助。

基本概念介绍

介绍View的工作原理之前我们首先要理解DecorView和ViewRoot两个概念:

1、DecorView

DecorView是Windows中的View的最顶层View。我们可以根据下面一副图来认识它:


由这幅图我们可以看到 ,其实DecorView是一个FrameLayout,里面是一个垂直的线性布局,在线性布局中分上下两部分FrameLayout,上面一部分是TitleBar,下面是android.R.id.content,我们平常的setContentView就是将布局加载在android.R.id.content中。

2、ViewRoot

ViewRoot是连接WindowsManager和DecorView的桥梁对应于ViewRootImpl。

View的绘制流程就是从ViewRootImpl的performTraversala()方法开始的,包含三大流程:

1、Measure():[ 测量流程]

2、Layout():[布局流程]

3、Draw():[绘制流程]

这三大流程也就是View绘制的三大流程。我们可以通过下面两幅图来理解performTrarsala()方法;


                                                                                                                                                              

什么是MeasureSpec

MeasureSpec  从名字上来看看起来是“测量规格“或是”测量说明书“。大致意思就是决定View的Measure过程。我们可以这样来理解:”MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以这样说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。“

1、MeasureSpec

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


SpecMode有三类如下所示:
  • UNSPECIFIED(未指定模式)
        父容器不对View有任何限制,要多大给多大。
  • EXACTLY(确定模式)
        父容器已检测出View所需要的精确大小,这时候View的最终大小就是SpecSize指定的值,它对应于LayoutParams中的match_parent和具体指定的数值这两种模式。
  • AT_MOST(最多模式)
          父容器指定一个可用大小即SpecSize,View的代销不能大于这个值。对应于wrap_content。

2、MeasureSPec 和LayoutParams的对应关系

为什么要说MeasureSpec和LayoutParams对应昵?那是因为View在测量的时候会将我们设置的LayoutParams在父容器的约束条件下转换成MeasureSpec,然后再根据MeasureSpec来确定View测量后的宽和高。
有些读者会问:那么顶级View(DecorView)怎么转换昵?
对于顶级View:MeasureSpec有窗口尺寸和自身的LayoutParams来共同决定。
对于普通View:由父容器的MeasureSpec和自身的LayoutParams共同来决定。
通过下面一段代码我们来理解DecorView的创建MeasureSpec过程,desiredWindowWidth和desiredWindowHeight是屏幕尺寸:

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


我们来再看看getRootMeasureSpec方法:


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

在android.view.ViewRootImpl  中可以看到其对应关系LayoutParams 中这三个值在内部有个对应关系,那就是

LayoutParams.MATCH_PARENT  对应 MeasureSpec.EXACTLY

.LayoutParams.WRAP_CONTENT对应  MeasureSpec.AT_MOST

默认值(也就是具体值) 对应 MeasureSpec.EXACTLY

也就是内部只有两种模式 EXACTLY 精确模式 和 AT_MOST 最大模式!


对于普通View它的measure是由ViewGroup传递而来,我们看一下ViewGroup的measureChildWidthMargins方法:

/**     * Ask one of the children of this view to measure itself, taking into     * account both the MeasureSpec requirements for this view and its padding     * and margins. The child must have MarginLayoutParams The heavy lifting is     * done in getChildMeasureSpec.     *     * @param child The child to measure     * @param parentWidthMeasureSpec The width requirements for this view     * @param widthUsed Extra space that has been used up by the parent     *        horizontally (possibly by other children of the parent)     * @param parentHeightMeasureSpec The height requirements for this view     * @param heightUsed Extra space that has been used up by the parent     *        vertically (possibly by other children of the parent)     */    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 // 考虑上parent的padding和child的margin                        + widthUsed, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                        + heightUsed, lp.height);        // 这里如果child是个ViewGroup类型,则实际会递归下去。。。        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

我们可以清楚的看到子View的MeasureSpec创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和VIew的margin 和 padding 有关,我们来看下ViewGroup的getChildMeasureSpec:
/**     * Does the hard part of measureChildren: figuring out the MeasureSpec to     * pass to a particular child. This method figures out the right MeasureSpec     * for one dimension (height or width) of one child view.     *     * The goal is to combine information from our MeasureSpec with the     * LayoutParams of the child to get the best possible results. For example,     * if the this view knows its size (because its MeasureSpec has a mode of     * EXACTLY), and the child has indicated in its LayoutParams that it wants     * to be the same size as the parent, the parent should ask the child to     * layout given an exact size.     *     * @param spec The requirements for this view     * @param padding The padding of this view for the current dimension and     *        margins, if applicable     * @param childDimension How big the child wants to be in the current     *        dimension     * @return a MeasureSpec integer for the child     */     public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        // 这个方法是协商型的,最终结果既可能直接由spec(parent提供的),也可能由childDimension决定        // 所以我们知道了,一个View的大小不是简单的单方面决定的,而是通过一系列条件协商的结果,        // 有时会尊重parent的spec,有时会坚持自己的dimension要求        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: // parent说child你应该是个确定的大小            if (childDimension >= 0) { // child正好设置了确定的大小                resultSize = childDimension; // 让child是那个确定的大小                resultMode = MeasureSpec.EXACTLY; // 设置mode为EXACTLY            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size; // 其他情况下都是parent spec中的大小,只是mode不同                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; // 不能超过size                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST: // parent说child你应该最大是某个值。。。            if (childDimension >= 0) { // child指定确定值了,则听child的                // 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: // parent没对child的大小有啥要求            if (childDimension >= 0) { // child指定了确定的值,听child的                // 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 = 0;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = 0;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

从上述方法中我们不难理解,子View的MeasureSpec创建与父容器的MeasureSpec和子元素本身的LayoutParams有关。

下面一张表格对MeasureSpec和LayoutParams对应做个总结:
                                                              
                                                                  普通View的MeasureSpec创建规则


                              parentSpecMode

childLayoutParams

       EXACTLY

          AT_MOST

         UNSPECIFIED

               dp/px

        EXACTLY

       chiladSize

         EXACTLY

         chiladSize

        EXACTLY

        childSize

 

          match_parent

        EXACTLY

        parentSize

        AT_MOST

        parentSize

     UNSPECIFIED

                0

          wrap_content

        EXACTLY

        parentSize

        AT_MOST

        parentSize

     UNSPECIFIED

                0




1 0