Android View体系(二)理解 MeasureSpec

1 前言

MeasureSpec 翻译成中文就是测量规格,在 View 的测量过程中要使用到 MeasureSpec

2 组成

MeasureSpec 是由 32 位的 int 值来表示,高 2 位 表示测量模式(mode),低 30 位 表示测量大小(size),所以我们可以得出结论:测量规格(MeasureSpec)= 测量模式(mode) + 测量大小(size),我们来看看 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);
          }
     }
     
   public static int getMode(int measureSpec) {
       return (measureSpec & MODE_MASK);
   }

       
   public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
   }
}         

MeasureSpecView 的一个静态内部类,提供了 sizemode 打包和解包成 int 的操作,避免创建额外的变量,减少对象的内存分配。

2.1 测量模式

MeasureSpec 的测量模式 (mode)有 3 种,都不同的含义。

  • UNSPECIFIED
  • EXACTLY
  • AT_MOST
模式描述
UNSPECIFIED父容器不对 View 进行限制,要多大就多大,一般用于系统内部,我们基本用不到
EXACTLY精确模式,父容器测量出 View 所需要的精确大小,View 最终大小就是 size 的大小,对应 LayoutParams 的 match_parent 和具体数值(10dp)这两种模式
AT_MOST父容器指定一个可用的大小,具体的值要看子元素实现,子元素不能大于这个值,对应 LayoutParams 的 wrap_content

2.2 MeasureSpec 的计算

MeasureSpec 计算由两个因素决定,如下所示。

  • View 自身的 LayoutParams
  • 父容器的 MeasureSpec

另外对于顶级的 ViewDecorView)和普通的 View 这两种 View,计算 MeasureSpec 也有所不同,如下所示。

  • 顶级 ViewDecorView):自身的LayoutParams 和窗口的尺寸共同决定
  • 普通 ViewView 自身的 LayoutParams和父容器的 MeasureSpec 共同决定

3 源码分析

接下来我们看看 getChildMeasure() 分析如何计算出 MeasureSpec 的值

/**
 *@param spec 父容器的测量值(MeasureSpec) 
 *@param padding view 当前尺寸的的内边距和外边距
 *@param childDimension 子视图的布局参数(宽/高)
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //父容器的测量模式 
        int specMode = MeasureSpec.getMode(spec);
        //父容器的测量大小
        int specSize = MeasureSpec.getSize(spec);
        //通过父容器计算出子 view 大小,父容器剩余空间,但是不一定用这个值
        int size = Math.max(0, specSize - padding);
        //子 view 实际大小和模式
        int resultSize = 0;
        int resultMode = 0;
        //判断父容器的模式 
        switch (specMode) {
        //1.如果父容器测量模式是 EXACTLY
        case MeasureSpec.EXACTLY:
            //1.1.当子 view 有确切的值
            if (childDimension >= 0) {
                //子 view 大小为自身值,模式为 EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            
            //1.2.子 view 的 LayoutParams 值为 MATCH_PARENT   
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子 view 大小为父容器剩余空间,模式为 EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;

            //1.3.子 view 的 LayoutParams 值为 WRAP_CONTENT
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子 view 大小不能超过父容器剩余空间,模式为AT_MOST  
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

         //2.如果父容器测量模式是 AT_MOST
        case MeasureSpec.AT_MOST:
             //2.1.当子 view 有确切的值 
            if (childDimension >= 0) {
                //子 view 大小为自身值,模式为 EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            //2.2.子 view 的 LayoutParams 值为 MATCH_PARENT 
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                 //子 view 大小不能超过父容器剩余空间,模式为 AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            //2.3.子 view 的 LayoutParams 值为 WRAP_CONTENT
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子 view 大小不能超过父容器剩余空间,模式为 AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //3.如果父容器测量模式是 UNSPECIFIED
        case MeasureSpec.UNSPECIFIED:
            //2.1.当子 view 有确切的值 
            if (childDimension >= 0) {
                //子 view 大小为自身值,模式为 EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            //3.2.子 view 的 LayoutParams 值为 MATCH_PARENT   
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子 view 大小 , 模式 UNSPECIFIED
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
               //子 view 大小 , 模式 UNSPECIFIED   
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

总结如下,这里我们不讨论父容器测量模式是 UNSPECIFIED,一般用不到。

  • 当子 View 为固定宽/高,不管父容器的 MeasureSpec 是什么,子 View 的测量模式都是为 EXACTLY,测量大小为自身大小
  • 当子View 宽/高为 match_parent,子 View 的测量模式都是跟随父容器的测量模式,如果父容器测量模式为 EXACTLY,测量大小为父容器剩余空间,如果父容器测量模式为 AT_MOST,测量大小不能超过父容器剩余空间
  • 当子 View 宽/高为 wrap_content,子 View 的测量模式都是 AT_MOST,测量大小不能超过父容器剩余空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值