View的工作原理之Measure过程源码学习(三)

        上一篇文章讲解了整个Android应用程序的View视图的顶级节点DecorView的Measure过程,文章最后就讲到了DecorView的onMeasure方法中调用super.onMeasure(widthMeasureSpec, heightMeasureSpec);之后,在FrameLayout的onMeasure方法中通过循环遍历子元素,从而往下进行每一级View的Measure过程。

        在开始本文内容讲解之前,我们需要先理解一些概念。在Android源代码中,我们可以发现,所有的View控件都是直接或间接继承自View这个类,包括ViewGroup也是。所以这里我们有必要进行一下区分,我们知道ViewGroup是可以有子元素的View,那么对于不能有子元素的View,我们称之为普通View,例如TextView等。区分好了这两者,就继续来讲解ViewGroup和普通View的Measure过程。

        回顾第一篇文章中,最后部分说到,DecorView的Measure过程是在performMeasure方法中调用DecorView的measure方法开始,而子元素的Measure过程是在DecorView的父类FrameLayout中的onMeasure方法中调用子元素的measure方法开始的。这就可以知道,不管是哪个ViewMeasure过程,一定会调用到它的measure方法。查看源码发现,这个measure方法是在View类中定义的,并且它是final类型,任何子类都不能重写这个方法,那我们就来看一下这个方法的源码。

//View.java 21961行
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
        //光学边界处理
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        //...

        if (forceLayout || needsLayout) {

          //...

            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

         //...
        }
}

        在View的measure方法代码中,可以看到,前面有部分是光学边界处理的。至于这光学边界是什么来的,我还不是很了解,但是这里还是提一下,防止以后再看代码纠结。代码省略部分大多是判断条件的处理,我认为不需要过多关注,所以注释了。这里其实重点是onMeasuresetMeasuredDimensionRaw这两个方法。整个measure方法中重点就是,根据判断条件的不同,分别调用这两个方法。根据注释及相关代码,我猜测应该是,为了避免重复测量,所以会在测量的时候静得到的测量值缓存下来,然后下一次再需要测量的时候,检查是不是有缓存的测量值。有得话就拿出来调用setMeasuredDimensionRaw方法就行,没有就调用onMeasure方法进行测量,值的一提的是onMeasure方法的参数就是父容器的MeasureSpec值,这个是可以在onMeasure方法注释中了解到。下面来看View的这两个方法。

//View.java 22115行
 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

//View.java 22089行
 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }


//View.java 22072行
 * @param widthMeasureSpec Horizontal space requirements as imposed by the parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the parent

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

       可以看到,setMeasuredDimensionRaw方法很简单,将拿到的测量值,赋给全局变量就完成了。onMeasure方法最终也是调用setMeasuredDimension方法,这里又是光学边界处理,然后调用setMeasuredDimensionRaw方法,也是把处理得到的值给到全局变量。

      到这,可以很直接的说,所有View的measure过程的最终目的就是把测量得到的值赋值给mMeasuredHeight和mMeasuredWidth。很显然View的测量结果,就是为了View的布局(layout)过程和绘画(draw)过程服务的。在这两个过程,都有使用到getMeasuredWidthgetMeasuredHeight方法来获取他们终的SpecSize。代码如下,之所以需要“与运算”,是因为mMeasuredWidth和mMeasuredHeight包含测量模式和测量大小,这里只取大小。

//View.java  13626行
 public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
//View.java  13654行
public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

       View的measure过程到这,其实就可以看出一些东西了。接下来的步骤就是具体的实现View重写onMeasure方法,然后进行相应的处理了。那么当我们自定义View的时候重写的onMeasure方法,到底该做些什么呢?我们在这里是不得而知的。关于这个问题,我们可以参看目前Android已经写好的控件,看看这些控件是如何实现的,就可以知道,当我们自定义View的时候该怎么做了。

           先来看一下,默认情况下View的onMeasure方法是怎么做的,看源代码:

//View.java 22072行
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

//View.java 22232行
 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
 }
//Drawable.java  1052行
 public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
 }

//Drawable.java  1026行
 public int getIntrinsicWidth() {
        return -1;
 }

        在onMeasure方法中,setMeasuredDimension方法传递的参数是getDefaultSize方法返回来的,getDefaultSize方法的参数又是getSuggestedMinimumWidth和父容器的widthMeasureSpec(这里只分析width的情况,height的情况类似)。在getSuggestedMinimumWidth方法中,查看背景是否设置,如果没有设置背景,那么View的宽度是mMinWidth,而mMinWidth对应android:minWidth这个属性的值,如果这个属性没有设置,那么默认0。如果View设置了背景,那么getSuggestedMinimumWidth方法返回的值是max(mMinWidth, mBackground.getMinimumWidth())。通过代码得知,mBackground.getMinimumWidth()返回值为intrinsicWidth,intrinsicWidth的值是Drawable的原始宽度,有些Drawable由原始宽度,有些没有,如果没有则直接返回0。

//View.java 22188行
 public static int getDefaultSize(int size, int measureSpec) {
     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:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
     }
      return result;
 }

           getDefaultSize方法也很简单, View的宽高就是specSize(不考虑MeasureSpec.UNSPECIFIED模式的情况)。所以在默认的onMeasure方法中,View的宽高就是父容器剩余的大小,也就是父容器的MeasureSpec值的SpecSize。由此我们得出一个结论:不管父容器的测量模式是什么,默认情况下,子元素的测量大小,都是父容器剩余的大小。到这了解了默认情况下View的onMeasure方法的工作。下一篇文章,将会学习ViewGroup和普通View的onMeasure方法的工作。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值