Android自定义UI开发之——Measure

Android View真正呈现在用户面前需要经历三个步骤:measure(测量)、layout(布局)和draw(绘制)三个过程。而measure则规定了view的大小。

我们自定义view规定view的大小,可在onMeasure()方法中指定当前view,或者用view.measure()的方式来指定子View的大小。
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureHandler(45 * 3, widthMeasureSpec);
        int height = measureHandler(45 * 3, heightMeasureSpec);
        if(mViewWidth != 0 && mViewHeight != 0) {
            setMeasuredDimension(mViewWidth, mViewHeight);
        }
    }
以上的代码段,就是如何指定当前View大小的一个例子。View主要是通过setMeasuredDimension()来指定View的宽度和高度的。那么,View的宽度和高度是怎么来的呢,他们和onMeasure()方法中的两个参数widthMeasureSpec和heightMeasureSpec有什么关系呢?

现在,我们来看看measureHandler()这个方法。
private int measureHandler(int defaultSize, int measureSpec) {
        int result = defaultSize;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if(specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else if(specMode == MeasureSpec.AT_MOST) {
            result = Math.min(defaultSize, specSize);
        } else {
            result = defaultSize;
        }
        return result;
    }
从上面这个代码段中,我们可以很清晰的看到,widthMeasureSpec或者heightMeasureSpec包含了两类数据,一是specMode,二是specSize。他们分别由MeasureSpec的getMode()和getSize()方法得到。

现在,我们来看一下MeasureSpec的源码:
/**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;
我们可以看到Measure有三种模式,分别是UNSPECIFIED、EXACTLY、AT_MOST。我们通过英文注释,可以很清楚的明白:

UNSPECIFIED:当前组件,可以随便用空间,不受限制。
EXACTLY:尺寸的值是多少,那么这个组件的长或宽就是多少。
AT_MOST:父组件,能够给出的最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。

那么,问题来了,MODE_SHIFT是多少?通过源码我们可以发现MODE_SHIFT是30,那为什么要左移30呢?一个int型整数怎么可以表示两个东西(大小模式和大小的值)?

一个int类型我们知道有32位。而模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图:

这里写图片描述

最高两位是00的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED
最高两位是01的时候表示"'精确模式"。即MeasureSpec.EXACTLY
最高两位是11的时候表示"最大模式"。即MeasureSpec.AT_MOST

现在大家都懂了吧,就是这样子滴。也就是说specSize最多30位。
继续看源码,我们可以发现一个重要方法:
public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
这个方法是干嘛用的呢?在这里,我告诉大家:这个方法是由我们给出的尺寸大小和模式生成一个包含这两个信息的int变量。即onMeasure中的两个参数widthMeasureSpec和heightMeasureSpec。

问题延伸:

1、我们可以通过makeMeasureSpec这个方法生成我们自定义的大小来规定View的大小。也就是说可以用view.measure()来指定子View的大小了。为什么要在父View中指定子View的大小呢?比如说在视差滚动时候的应用,当我们父View滚动到某一位置的时候让子View变小等等。
2、我们以后也可以利用一个int类型来表示两个int值啦。当然,拓展一下,除了利用<<左移,我们还可以利用|,&和~。

题外话:
我们来看看View的这个方法:

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {  
   int result = size;  
   int specMode = MeasureSpec.getMode(measureSpec);  
   int specSize =  MeasureSpec.getSize(measureSpec);  
   switch (specMode) {  
   case MeasureSpec.UNSPECIFIED:  
       result = size;  
       break;  
   case MeasureSpec.AT_MOST:  
       if (specSize < size) {  
           result = specSize | MEASURED_STATE_TOO_SMALL;  
       } else {  
           result = size;  
       }  
       break;  
   case MeasureSpec.EXACTLY:  
       result = specSize;  
       break;  
   }  
   return result | (childMeasuredState&MEASURED_STATE_MASK);  

}这里写代码片

这个方法的主要作用就是根据你提供的大小和模式,返回你想要的大小值,这个里面根据传入模式的不同来做相应的处理。我们可以在onMeasure()方法中很方便的调用。

我们再来看看这个方法:
private void measureItem(View child) {  
      ViewGroup.LayoutParams p = child.getLayoutParams();  
      if (p == null) {  
          p = new ViewGroup.LayoutParams(  
                  ViewGroup.LayoutParams.MATCH_PARENT,  
                  ViewGroup.LayoutParams.WRAP_CONTENT);  
      }  

      int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
              mListPadding.left + mListPadding.right, p.width);  
      int lpHeight = p.height;  
      int childHeightSpec;  
      if (lpHeight > 0) {  
          childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
      } else {  
          childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
      }  
      child.measure(childWidthSpec, childHeightSpec);  
  }  

这个方法主要是ListView.measureItem(View child)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值