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

简介: 版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/52150950 前言本文参考《Android开发艺术与探索》第四章内容及网上几篇博客,里面融入笔者的个人理解。
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/52150950

前言

本文参考《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月前
|
前端开发 Android开发 容器
自定义View之View的工作原理
自定义View之View的工作原理
20 0
|
Android开发
View事件分发相关结论的源码解析
View事件分发的三个核心方法有三个,分别是`dispatchTouchEvent`方法,`onInterceptTouchEvent`方法和`onInterceptTouchEvent`方法。 dispatchTouchEvent方法主要用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。 onInterceptTouchEvent方法在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,返回结果表示是否拦截
|
Android开发 容器
View工作原理分析1 - 初识ViewRoot和 DecorView
以下相关资料均来自 Android艺术探索,部分内容加入了一些我个人的理解。
107 0
|
缓存 安全 Android开发
Android | 带你探究 LayoutInflater 布局解析原理
Android | 带你探究 LayoutInflater 布局解析原理
171 0
Android | 带你探究 LayoutInflater 布局解析原理
|
XML 缓存 Java
ViewBinding 的本质
ViewBinding 的本质
130 0
ViewBinding 的本质
|
XML 缓存 Android开发
LayoutInflater 布局渲染工具原理分析
LayoutInflater 布局渲染工具原理分析
88 0
LayoutInflater 布局渲染工具原理分析
ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别
ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别
ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别
|
Android开发
从源码角度分析Activity、Window和DecorView的关系
前言 最近想出一篇Android事件分发机制的文章,但是根据很多小伙伴反馈在理解Android事件分发机制之前都不是很明白Activity、Window和DecorView之间的关系,导致在学习Android事件分发机制上理解很费劲,本文将从源码角度带你分析Activity、Window和DecorView之间的关系,让你彻彻底底搞明白。
1387 0
|
前端开发 Java Android开发
自定义控件View之onMeasure调用时机源码分析
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 先上测试代码: MainActivity.java import android.
1174 0
|
Android开发 数据安全/隐私保护 数据格式
Activity的基础知识
前言 长风破浪会有时,直挂云帆济沧海。 关于Activity的启动和关闭 一个Android应用程序通常都包含多个Activity,但只有一个Activity会作为程序的入口——当该Android应用运行时将会自动启动并执行该Activity。
804 0