您的位置:首页 > 其它

View工作原理(三)视图大小计算过程(measure过程)

2016-01-13 17:17 483 查看
一、android中view的measure过程总概
视图大小计算的过程是从根视图measure()方法开始,接着该方法会调用根视图的onMeasure()方法,onMeasure()方法会对所包含的子视图逐一执行measure()方法,如果子视图是ViewGroup子类对象(LinearLayout、FrameLayout、RelativeLayout等布局),则继续调用子视图的measure()方法,重复这一过程。如果子视图是View子类对象(Button、EditText、TextView、ImageView等),则在子视图重载的onMeasure方法内部不需要进行对子视图进行measure操作,从而一次measure过程完成。过程如下图所示:




二、measure详细过程
View中的measure()方法源码(ViewGroup类继承了View类,measure过程先从ViewGroup子类开始):

[java]
view plaincopy





public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
       if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
               widthMeasureSpec != mOldWidthMeasureSpec ||  
               heightMeasureSpec != mOldHeightMeasureSpec) {  
  
           // first clears the measured dimension flag  
           mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  
           if (ViewDebug.TRACE_HIERARCHY) {  
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
           }  
  
           // measure ourselves, this should set the measured dimension flag back  
           onMeasure(widthMeasureSpec, heightMeasureSpec);  
  
           // flag not set, setMeasuredDimension() was not invoked, we raise  
           // an exception to warn the developer  
           if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
               throw new IllegalStateException("onMeasure() did not set the"  
                       + " measured dimension by calling"  
                       + " setMeasuredDimension()");  
           }  
  
           mPrivateFlags |= LAYOUT_REQUIRED;  
       }  
  
       mOldWidthMeasureSpec = widthMeasureSpec;  
       mOldHeightMeasureSpec = heightMeasureSpec;  
   }  

注:通过源码,我们看到该方法的定义中用了final关键字,说明该方法是不能被重写的,即View系统定义的这个measure框架不能被修改。参数widthMeasureSpec和heightMeasureSpec分别对应宽和高的measureSpec,当父视图对子视图进行measure操作时,会调用子视图的measure()方法,该参数得意思是父视图所提供的measure的“规格”,因为父视图为子视图提供的窗口尺寸是由父视图和子视图共同决定。该参数有两部分组成,第一部分:高16位表示specMode,定义在MeasureSpec类中,有三种类型:MeasureSpec.EXACTLY:表示明确的尺寸大小, MeasureSpec.AT_MOST:表示最大大小, MeasureSpec.UNSPECIFIED:不确定大小。第二部分:低16位表示size,即父view的大小,这就是为什么我们在重写onmeasure方法是需要:int specMode = MeasureSpec.getMod(spec); int specSize = MeasureSpec.getSize(spec)这样调用。specMode一般都为MeasureSpec.EXACTLY ,而size分别对应屏幕宽,高。也就是Window第一次掉用的view,一般都是这个值,而对于子view来说,这个值就是你在xml定义的属性  android:layout_width和android:layout_height的值。

下面我们看看源码执行过程,看注释就能很明白,首先清 除测量尺寸的标识。接着将重新测量自己的尺寸,即调用onMeasure()方法。最后是判断测量尺寸大小的标识是否已经重新赋值,如果没有则不执行setMeasuredDimension()方法。方法结束。这个方法里面主要就是调用自己的onMeasure()方法,对自己的大小尺寸进行测量。下面来介绍onMeasure()方法。

 

ViewGroup中的onMeasure方法介绍

其实在ViewGroup类中并没有重写该方法,一般在他的子类中进行重写,比如LinearLayout、RelativeLayout,下面我们以Linearlayout来分析。LinearLayout中onMeasure方法源码如下:

[java] view plaincopy



@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    if (mOrientation == VERTICAL) {  
        measureVertical(widthMeasureSpec, heightMeasureSpec);  
    } else {  
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);  
    }  
}  

 

注:通过源码我们可以知道,首先onMeasure会判断这个布局是纵向布局还是横向布局,即对应android:orientation=""属性。下面以纵向布局来分析,源码如下,有点长:

[java]
view plaincopy





/** 
   * Measures the children when the orientation of this LinearLayout is set 
   * to {@link #VERTICAL}. 
   * 
   * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. 
   * @param heightMeasureSpec Vertical space requirements as imposed by the parent. 
   * 
   * @see #getOrientation() 
   * @see #setOrientation(int) 
   * @see #onMeasure(int, int) 
   */  
  void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
      mTotalLength = 0;  
      int maxWidth = 0;  
      int alternativeMaxWidth = 0;  
      int weightedMaxWidth = 0;  
      boolean allFillParent = true;  
      float totalWeight = 0;  
  
      final int count = getVirtualChildCount();  
       
      final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
      final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
      boolean matchWidth = false;  
      final int baselineChildIndex = mBaselineAlignedChildIndex;          
      final boolean useLargestChild = mUseLargestChild;  
      int largestChildHeight = Integer.MIN_VALUE;  
      // See how tall everyone is. Also remember max width.  
      for (int i = 0; i < count; ++i) {  
          final View child = getVirtualChildAt(i);  
  
          if (child == null) {  
              mTotalLength += measureNullChild(i);  
              continue;  
          }  
          if (child.getVisibility() == View.GONE) {  
             i += getChildrenSkipCount(child, i);  
             continue;  
          }  
          LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
          totalWeight += lp.weight;              
          if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
              // Optimization: don't bother measuring children who are going to use  
              // leftover space. These views will get measured again down below if  
              // there is any leftover space.  
              final int totalLength = mTotalLength;  
              mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);  
          } else {  
              int oldHeight = Integer.MIN_VALUE;  
              if (lp.height == 0 && lp.weight > 0) {  
                  // heightMode is either UNSPECIFIED or AT_MOST, and this  
                  // child wanted to stretch to fill available space.  
                  // Translate that to WRAP_CONTENT so that it does not end up  
                  // with a height of 0  
                  oldHeight = 0;  
                  lp.height = LayoutParams.WRAP_CONTENT;  
              }  
              // Determine how big this child would like to be. If this or  
              // previous children have given a weight, then we allow it to  
              // use all available space (and we will shrink things later  
              // if needed).  
              measureChildBeforeLayout(  
                     child, i, widthMeasureSpec, 0, heightMeasureSpec,  
                     totalWeight == 0 ? mTotalLength : 0);  
  
              if (oldHeight != Integer.MIN_VALUE) {  
                 lp.height = oldHeight;  
              }  
              final int childHeight = child.getMeasuredHeight();  
              final int totalLength = mTotalLength;  
              mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +  
                     lp.bottomMargin + getNextLocationOffset(child));  
  
              if (useLargestChild) {  
                  largestChildHeight = Math.max(childHeight, largestChildHeight);  
              }  
          }  
          /** 
           * If applicable, compute the additional offset to the child's baseline 
           * we'll need later when asked {@link #getBaseline}. 
           */  
          if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {  
             mBaselineChildTop = mTotalLength;  
          }  
          // if we are trying to use a child index for our baseline, the above  
          // book keeping only works if there are no children above it with  
          // weight.  fail fast to aid the developer.  
          if (i < baselineChildIndex && lp.weight > 0) {  
              throw new RuntimeException("A child of LinearLayout with index "  
                      + "less than mBaselineAlignedChildIndex has weight > 0, which "  
                      + "won't work.  Either remove the weight, or don't set "  
                      + "mBaselineAlignedChildIndex.");  
          }  
          boolean matchWidthLocally = false;  
          if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {  
              // The width of the linear layout will scale, and at least one  
              // child said it wanted to match our width. Set a flag  
              // indicating that we need to remeasure at least that view when  
              // we know our width.  
              matchWidth = true;  
              matchWidthLocally = true;  
          }  
          final int margin = lp.leftMargin + lp.rightMargin;  
          final int measuredWidth = child.getMeasuredWidth() + margin;  
          maxWidth = Math.max(maxWidth, measuredWidth);  
          allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;  
          if (lp.weight > 0) {  
              /* 
               * Widths of weighted Views are bogus if we end up 
               * remeasuring, so keep them separate. 
               */  
              weightedMaxWidth = Math.max(weightedMaxWidth,  
                      matchWidthLocally ? margin : measuredWidth);  
          } else {  
              alternativeMaxWidth = Math.max(alternativeMaxWidth,  
                      matchWidthLocally ? margin : measuredWidth);  
          }  
          i += getChildrenSkipCount(child, i);  
      }  
      if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {  
          mTotalLength = 0;  
          for (int i = 0; i < count; ++i) {  
              final View child = getVirtualChildAt(i);  
              if (child == null) {  
                  mTotalLength += measureNullChild(i);  
                  continue;  
              }  
              if (child.getVisibility() == GONE) {  
                  i += getChildrenSkipCount(child, i);  
                  continue;  
              }  
              final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)  
                      child.getLayoutParams();  
              // Account for negative margins  
              final int totalLength = mTotalLength;  
              mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +  
                      lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));  
          }  
      }  
      // Add in our padding  
      mTotalLength += mPaddingTop + mPaddingBottom;  
      int heightSize = mTotalLength;  
      // Check against our minimum height  
      heightSize = Math.max(heightSize, getSuggestedMinimumHeight());          
      // Reconcile our calculated size with the heightMeasureSpec  
      heightSize = resolveSize(heightSize, heightMeasureSpec);          
      // Either expand children with weight to take up available space or  
      // shrink them if they extend beyond our current bounds  
      int delta = heightSize - mTotalLength;  
      if (delta != 0 && totalWeight > 0.0f) {  
          float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;  
          mTotalLength = 0;  
          for (int i = 0; i < count; ++i) {  
              final View child = getVirtualChildAt(i);  
              if (child.getVisibility() == View.GONE) {  
                  continue;  
              }                  
              LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();                  
              float childExtra = lp.weight;  
              if (childExtra > 0) {  
                  // Child said it could absorb extra space -- give him his share  
                  int share = (int) (childExtra * delta / weightSum);  
                  weightSum -= childExtra;  
                  delta -= share;  
                  final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,  
                          mPaddingLeft + mPaddingRight +  
                                  lp.leftMargin + lp.rightMargin, lp.width);  
  
                  // TODO: Use a field like lp.isMeasured to figure out if this  
                  // child has been previously measured  
                  if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {  
                      // child was measured once already above...  
                      // base new measurement on stored values  
                      int childHeight = child.getMeasuredHeight() + share;  
                      if (childHeight < 0) {  
                          childHeight = 0;  
                      }  
                      child.measure(childWidthMeasureSpec,  
                              MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));  
                  } else {  
                      // child was skipped in the loop above.  
                      // Measure for this first time here        
                      child.measure(childWidthMeasureSpec,  
                              MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,  
                                      MeasureSpec.EXACTLY));  
                  }  
              }  
              final int margin =  lp.leftMargin + lp.rightMargin;  
              final int measuredWidth = child.getMeasuredWidth() + margin;  
              maxWidth = Math.max(maxWidth, measuredWidth);  
              boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&  
                      lp.width == LayoutParams.MATCH_PARENT;  
              alternativeMaxWidth = Math.max(alternativeMaxWidth,  
                      matchWidthLocally ? margin : measuredWidth);  
              allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;  
              final int totalLength = mTotalLength;  
              mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +  
                      lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));  
          }  
          // Add in our padding  
          mTotalLength += mPaddingTop + mPaddingBottom;  
          // TODO: Should we recompute the heightSpec based on the new total length?  
      } else {  
          alternativeMaxWidth = Math.max(alternativeMaxWidth,  
                                         weightedMaxWidth);  
      }  
      if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {  
          maxWidth = alternativeMaxWidth;  
      }  
      maxWidth += mPaddingLeft + mPaddingRight;  
      // Check against our minimum width  
      maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());  
      setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize);  
      if (matchWidth) {  
          forceUniformWidth(count, heightMeasureSpec);  
      }  
  }  

注:

获取所有的子view数量,对每个子view开始处理,如果子view是GONE的,则直接跳过。
获取子view的LayoutParams,在xml中定义的参数,通过layout_weight定义的值累加到变量totalWeight中,然后判断如果view的height设置为零,但weight设置的大于0,则将height的值设置为LayoutParams.WRAP_CONTENT。
然后调用measureChildWithMargins方法,该方法处理的逻辑:计算子view的measureSpec,即specMode和specSize,调用方法为:getChildMeasureSpec,调用两次,分别 计算宽和高,getChildMeasureSpec内部根据父view的measure和子view的layout_width和layout_height属性计算子view的measure。getChildMeasureSpec计算子view的measure,总结如下:1.如果在xml中指定了子view的具体大小,那么计算结果不管父的measure是什么,结果都是EXACITY+child_size,2.如果子view的height指定的值为FILL_PARENT,则返回的结果为:EXACITY+size,原因很简单:因为FILL_PARENT的意思是充满这个父view,所以返回的子view的measure就是view的大小。3.如果子view的大小为wrap_content,那么返回的结果都为AT_MOST+size,原因是:最大不能超过父view的大小。
子view的measure确定好以后,然后调用子view的measure方法,如果子view是View对象,则该view的大小测量结束,开始下一个子view的循环,如果子view是ViewGroup那么,又开始一个新的递归,处理逻辑和上面一样,直到所有的view对象测量结束。
所有的子view测量结束后,才开始对layout_weight计算,这样我们可能想到,如果父view已经被占满了,那么有可能layout_weight大于0的view对象是不会显示的,而计算layout_weight的方法也很简单,就是用总高度减去上面分析完mTotalLength的值,就是剩下,然后去平分给view对象,注意计算权重时优先去android:android:weightSum(LinearLayout的xml属性)的值,如果不设置该值会计算和,所以该值既然设置了,就一定要子view的weight的总和相等,否则平分可能不能得到预期效果。
 过程分析完毕,这篇文章这里提到了LinearLayout中的layout_weight属性,这个属性对很对人来说是又恨又爱,下篇文章,我们将来总结改属性的详细用法,让大家彻底理解这个属性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: