View的工作原理(一)初认识ViewRoot、DecorView,理解MeasureSpec

要点

在这里插入图片描述

一、初识RootView、DecorView

为了更好的熟悉view的三大流程(测量、摆放、绘制)我们就先普及下RootView、DecorView基本概念。

1、ViewRoot

1、ViewRoot 对应ViewRootImlp 类,它其实是连接 WindowManger 和 DecorView 的桥梁。View 的三大流程都是通过 ViewRoot 来完成的。
2、在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象与 DecorView 进行关联。
3、整个 View 的绘制流程,是从 ViewRoot 的 performTraversals 方法开始的。 它经过measure、layout、draw 这三个过程才最终将一个 View 绘制出来,其中 measure 是用于测量 View 的宽高的,layout 是用于当继承 ViewGroup 时确定子 View 位置的, draw 则是负责将 View 绘制在屏幕上。如下图(performTraversals 工作流程图)
4、performTraversals 会依次调用 performMeasure、performLayout、performDraw 这三个方法,这三个方法会依次完成顶级 View 的 measure、layout、draw 三大流程。
5、其中 performMeasure 方法会接着调用 measure 方法,在 measure 方法中又会去调用 onMeasure 方法。在 onMeasure 方法中对所有的子元素进行了 measure 过程 ,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次 measure 过程。接着,子元素又重复了一遍 measure 过程,如此反复,直到没有了子元素。这样就完成了整个 View 树的遍历。
6、其中 layout 和 draw 的流程同上,两者同样都有 performLayout 、performDraw 和 layout、draw 方法,唯一不同的是,performDraw 的传递是在 draw 方法中通过 dispatchDraw 来实现的,不过这并没有什么本质上的区别。

在这里插入图片描述

2、三大流程简介
  • measure
    这个过程决定了 View 的宽/高。measure 完成以后,可以通过 getMeasureWidth 和 getMeasureHeight 来获取View 测量后的宽/高。

  • layout
    这个过程决定了 View 四个定点的坐标和实际 View 的宽/高。完成以后,可以通过 getTop、getLeft、getBottom、getRight 来获取 View 的四个顶点的位置,并可以通过 getWidth 和 getHeight 方法获取 View 的最终宽/高。
    ps:其实getTop、getBottom、getRight、getLeft这四个方法是view的方法在view基础知识中有讲解参考:View的事件体系(一)view基础和view的几种滑动方式中的图解

  • draw
    这个过程决定了 View 的显示,只有当 draw 方法完成以后,View 才能够显示到屏幕上。

3、DecorView

在这里插入图片描述

如上图(图片来源网络):
1、DecorView 在一般情况下,内部会包含一个竖直的 LInearLayout,里面有上下两部分(具体情况和安卓版本和主题有关),上面是标题栏,下面是内容,内容布局有一个默认的 id: content。
2、DecorView 继承自 FrameLayout,是一个 ViewGroup。在整个 ViewTree 中, DecorView 是整个 ViewTree 的顶层 View。View 的所有事件,都先经过 DecorView,然后再传递给 View
我们在 Activity 中 通过 setContentView() 方法设置的布局,其实就是添加到了内容部分里。如果我们想获取到内容布局的话,可以通过如下方法获取:

ViewGroup content = (ViewGroup) findViewById(android.R.id.content);

View childAt = content.getChildAt(0);

二、理解MeasureSpec

MeasureSpec 是 View 的一个内部类,代表了一个32位的 int 值。前 2 位代表 SpecMode,后 30 位代表 SpecSize。SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小。

测量模式(SpecMode )有3种:UNSPECIFIED,EXACTLY,AT_MOST。我们只需要使用2个bit就可以做到,因为2个bit取值范围是[0,3]里面可以存放4个数足够我们用了。那么Google是怎么把一个int同时放测量模式和尺寸信息呢?我们知道int型数据占用32个bit,而google实现的是,将int数据的前面2个bit用于区分不同的布局模式,后面30个bit存放的是尺寸的数据。

该类在很大程度上决定了 View 的尺寸规格,除此之外父容器也影响 View 的 MeasureSpec 的创建过程。系统会将 View 的 LayoutParams 根据父容器所施加的规则转换成对应的 MeasureSpec,然后再根据这个 MeasureSpec 来测量出 View 的宽高。需要注意的是,这个的宽高是测量的宽高,并不一定是 View 最终的宽高。后面会详细了解 MeasureSpec 的创建过程,在了解之前,首先来了解下 MeasureSpec 的三种测量模式。
参考下图理解:
布局参数参考:
从LayoutParams说起到代码动态布局
关于LayoutParams的使用

在这里插入图片描述

1、三种测量模式

SpecMode 和 SpecSize 组成了 MeasureSpec,MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来避免过多的对象创建,并提供了对应的打包、解包方法
首先我们看看MeasureSpec源码。

public static class 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;

// 打包操作
 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);
            }
        }
}

// 解包  获得 mode
 public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
   //解包 获得size     
   public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
2、SpecMode 三类
  • UNSPECIFIED:
    父容器不对子元素作任何约束,子 View 想要多大就给多大。这种情况一般用在系统内部,表示一种测量的状态。一般情况下,我们不用关注该测量模式。

  • EXACTLY:
    精准模式。父容器已经解决了子元素所需要的精准大小,这时候子 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值。

  • AT_MOST:
    最大模式。父最大模式容器指定了一个可用大小即 SpecSize,子元素最大不可以超过指定的这个值。它对应于LayoutParams 中的 wrap_content。

3、MeasureSpec和LayoutParams 的关系

我们知道:对于普通 View,MeasureSpec 是由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定的。
看看源码,MeasureSpec 是怎么被创建出来的,由于普通 View 的 measure 过程由 ViewGroup 传递而来,所以首先来看一下 ViewGroup 的:measureChildWithMargins()方法

         
 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
            //获取子元素的布局参数
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 根据父容器WidthMeasureSpec,子view自身布局参数 ,子view的margain、padding来获得 子view的WidthMeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
           // 根据父容器WidthMeasureSpec,子view自身布局参数 ,子view的margain、padding来获得 子view的WidthMeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
       //开始测量子view
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

首先获取到了子元素的 LayoutParams 然后根据当前子元素的 LayoutParams 和父类的MeasureSpec,获取了子元素的 MeasureSpec,最后调用了子元素的 measure。
继续深入getChildMeasureSpec源码如下:

//参数 父容器MeasureSpec,父容器padding,子view大小
  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//父容器mode
        int specSize = MeasureSpec.getSize(spec);//父容器size

        int size = Math.max(0, specSize - padding); //父容器可用size空间==父容器总空间-padding空间

        int resultSize = 0;//测量出的子元素size 先默认为0;
        int resultMode = 0;//测量出的子元素mode先默认为0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:// 父控件EXACTLY模式时
            if (childDimension >= 0) {//子元素采用固定数值时(比如xml中宽高设置了具体dp)
                resultSize = childDimension; // 测量出 子元素size 为childDimension
                resultMode = MeasureSpec.EXACTLY;// 测量出 子元素模式为EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子元素设置了MATCH_PARENT时
                // Child wants to be our size. So be it.
                resultSize = size;// 子元素大小为 size,这个size是父容器大小减去padding的空间
                resultMode = MeasureSpec.EXACTLY;//子元素模式为EXACTLY
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子元素设置为WRAP_CONTENT时
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size; // 子元素大小为 size,这个size是父容器大小减去padding的空间
                resultMode = MeasureSpec.AT_MOST;//子元素模式为AT_MOST
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST://当父容器为AT_MOST模式时
            if (childDimension >= 0) {//子元素设置了固定值时(比如宽高设置:50dp)
                // Child wants a specific size... so be it
                resultSize = childDimension; //大小为子元素大小
                resultMode = MeasureSpec.EXACTLY;//模式为EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子元素设置MATCH_PARENT时
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;//大小为父容器剩余空间(参考上文size解释)
                resultMode = MeasureSpec.AT_MOST;//模式AT_MOST
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子元素设置WRAP_CONTENT时
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;//大小为父容器剩余空间(参考上文size解释)
                resultMode = MeasureSpec.AT_MOST;//模式AT_MOST
            }
            //(可以看出自定义view时父容器的AT_MOST我们不处理时view设置match_parent、wrap_content都是match_parent效果)
            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);
    }
getChildMeasureSpec展示了普通view的MeasureSpec创建规则,根据源码总结出下表:

在这里插入图片描述

总结:(详细的分析请看注释,我已经几乎都注释了
1、当 View 采用固定宽高(确切值)的时候,不管父容器的 MeasureSpec 是什么,View 的测量模式都是 EXACTLY 也就是精准模式。并且其大小遵循 LayoutParams 中的大小。
2、当 View 的宽高是 match_parent 的时候,如果父控件是精准模式,那么 View 也是精准模式,并且大小是父容器的剩余空间。如果父控件是最大模式,那么 View 也是最大模式,并且大小不会超过父容器的剩余空间。
3、当 View 的宽高是 wrap_content 的时候,不管父控件是精准模式还是最大模式,View 的模式最是最大模式,并且大小不能够超过父容器的剩余空间。
ps:UNSPECIFIED 系统使用,我们不考虑。

实际开发中:

实际开发中:
1、UNSPECIFIED我们不常见。几乎不用处理。
2、EXACTLY我们也几乎不用处理,
3、AT_MOST 这种我们要处理。(上面源码中AT_MOST状况下的注释如下)
(可以看出自定义view时父容器的AT_MOST我们不处理时view设置match_parent、wrap_content都是match_parent效果)
栗子如下:

首先自定义view继承view啥也不处理:

/**
 * Create by SunnyDay on 2019/04/12
 */
public class MyLayout extends View {
    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
   
}

直接使用:
在这里插入图片描述
结果:
在这里插入图片描述

如上:
自定义view时,除了第一个view的高度我们设置精确值时显示无误,我们设置为wrap_content时的效果就是match_parent的效果。
ps:中间写了个TextView以做隔离区分
如何处理参考view的三大流程

三、小结

总结了一篇,发现MeasureSpec还真不好理解。。。。。
参考文章:https://blog.csdn.net/Airsaid/article/details/53576087

The end

本文来自<安卓开发艺术探索>笔记总结

View的工作原理(二)Measure过程

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/89230661