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

killads
发布于 2020-9-24 10:03
浏览
0收藏

前言


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

 

基本概念介绍

 

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

 

1、DecorView

 

DecorView是Windows中的View的最顶层View。我们可以根据下面一副图来认识它:View的工作原理(一)——从ViewRoot和DecorView说起-鸿蒙开发者社区

由这幅图我们可以看到 ,其实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()方法;

View的工作原理(一)——从ViewRoot和DecorView说起-鸿蒙开发者社区View的工作原理(一)——从ViewRoot和DecorView说起-鸿蒙开发者社区

 

什么是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创建规则

View的工作原理(一)——从ViewRoot和DecorView说起-鸿蒙开发者社区

 

作者:紫雾凌寒

来源:CSDN

 

分类
收藏
回复
举报
回复
    相关推荐