Android View 相关源码分析之一 从setContentView说起

Android View 相关源码分析之二 继LayoutInflater来说

Android View 相关源码分析之三 View的绘制过程

LinearLayout 源码分析

measure过程

主要过程

  • 根据布局方向选择measure过程分支
  • 初始化相关变量
  • 对View进行第一次测量
  • mTotalLength的再次测量
  • 二次测量部分View和对为测量的子View进行测量
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {      //判断布局方向      if (mOrientation == VERTICAL) {          measureVertical(widthMeasureSpec, heightMeasureSpec);      } else {          measureHorizontal(widthMeasureSpec, heightMeasureSpec);      }  }

measureVertical和measureHorizontal只是布局方向上的区别 以下主要分析measureVertical方法

初始化相关变量

  //mTotalLength是记录内部使用的高度也就是子View的高度和 而不是LinearLayout的高度  mTotalLength = 0;  //子视图的最大宽度(不包括layout_weight>0的子View)  int maxWidth = 0;  int childState = 0;  int alternativeMaxWidth = 0;  //子视图的最大宽度(仅包含layout_weight>0的子View)  int weightedMaxWidth = 0;  //子视图是否均为fillParent 用于判断是否需要重新计算  boolean allFillParent = true;  //权重值的总和  float totalWeight = 0;  //子View的数量(统一级别下)  final int count = getVirtualChildCount();  //高度宽度模式  final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  //子View的宽度是否需要由父View决定  boolean matchWidth = false;  boolean skippedMeasure = false;  //第几个子View的baseLine作为LinearLayout的基准线  final int baselineChildIndex = mBaselineAlignedChildIndex;    //mUseLargestChild为是否使用最大子元素的尺寸作为标准再次测量  final boolean useLargestChild = mUseLargestChild;  //子View中最高高度  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);      // 测量为null的子视图的高度      // measureNullChild() 暂时返回 0 便于扩展      if (child == null) {          mTotalLength += measureNullChild(i);          continue;      }      //Visibility为Gone的时候跳过该View      // getChildrenSkipCount()方法同样返回0 便于扩展      if (child.getVisibility() == View.GONE) {         i += getChildrenSkipCount(child, i);         continue;      }      //根据showDivider的值(通过hasDividerBeforeChildAt()) 来决定当前子View是否需要添加分割线的高度      if (hasDividerBeforeChildAt(i)) {          mTotalLength += mDividerHeight;      }      //会将子view的LayoutParams强转为父View的LayoutParams类型      LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)child.getLayoutParams();      totalWeight += lp.weight;      if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {          // 满足该条件的话 不需要现在计算该子视图的高度 测量工作会在之后进行          // 若子View的height=0 且weight> 0 则说明该View希望使用的是LinearLayout的剩余空间          // LinearLayout是EXACTLY模式的说明LinearLayout高度已经确定 不需要依赖子View的测量结果来计算自己 就无需测量该子View          final int totalLength = mTotalLength;          mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);          skippedMeasure = true;      } else {          //测量子View          int oldHeight = Integer.MIN_VALUE;          //当前View的height=0 且weight> 0 则说明该LinearLayout的高度需要靠子View测量(不需要的在上面分支处理了)          //将子View的高度设为-1 防止子View高度为0          if (lp.height == 0 && lp.weight > 0) {              oldHeight = 0;              lp.height = LayoutParams.WRAP_CONTENT;          }          //调用子View的measureChildWithMargins() 对子View进行测量          //第四个参数表示当前已使用的宽度  因为是竖直模式 所以为0          //最后一个参数表示已使用的高度 如果之前的子View或者当前的View有weight属性 则当前子视图使用 LinearLayout 的所有高度 已使用的高度为0          measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec,                 totalWeight == 0 ? mTotalLength : 0);          if (oldHeight != Integer.MIN_VALUE) {             //测量完成后 重置子View高度             lp.height = oldHeight;          }          final int childHeight = child.getMeasuredHeight();          final int totalLength = mTotalLength;          // 比较child测量前后总高度 取较大值          ///getNextLocationOffset() 返回0 便于扩展          mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));          // 设置最高子视图大小          if (useLargestChild) {              largestChildHeight = Math.max(childHeight, largestChildHeight);          }      }       // mBaselineChildTop 表示指定的 baseline 的子视图的顶部高度      if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {         mBaselineChildTop = mTotalLength;      }      // 设置为 baseline 的子视图的前面不允许设置 weiget 属性      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;      //当LinearLayout非EXACTLY模式 并且自View为MATCH_PARENT时      //设置matchWidth和matchWidthLocally为true      //该子View占据LinearLayout水平方向上所有空间      if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {          matchWidth = true;          matchWidthLocally = true;      }      final int margin = lp.leftMargin + lp.rightMargin;      final int measuredWidth = child.getMeasuredWidth() + margin;      //对一堆变量赋值      maxWidth = Math.max(maxWidth, measuredWidth);      childState = combineMeasuredStates(childState, child.getMeasuredState());      allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;      if (lp.weight > 0) {          weightedMaxWidth = Math.max(weightedMaxWidth,                  matchWidthLocally ? margin : measuredWidth);      } else {          alternativeMaxWidth = Math.max(alternativeMaxWidth,                  matchWidthLocally ? margin : measuredWidth);      }      i += getChildrenSkipCount(child, i);  }

二次测量mTotalLength

  //根据hasDividerBeforeChildAt得到showDivider的值是否为end 来判断是否需要加上divider的高度  if (mTotalLength > 0 && hasDividerBeforeChildAt(count))      mTotalLength += mDividerHeight;  }  //如果高度测量模式为AT_MOST或者UNSPECIFIED 则进行二次测量 且设置了measureWithLargestChild  if (useLargestChild && (heightMode == MeasureSpec.AT_MOST ||      heightMode == MeasureSpec.UNSPECIFIED)) {      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();          // 计算所有子View的高度之和          final int totalLength = mTotalLength;          mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +                  lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));      }  }

就是需要useLargestChild

而 mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);

就是说仅在LinearLayout的measureWithLargestChild属性设置为True时(默认为false)才可能出现某个child被二次测量

实例如下

Android View 相关源码分析之四 LinearLayout源码分析_第1张图片 mTotalLength的二次测量

二次测量部分View和对为测量的子View进行测量

   //加上padding的值   mTotalLength += mPaddingTop + mPaddingBottom;   int heightSize = mTotalLength;   //minHeight和当前使用的高度比较取较大值   heightSize = Math.max(heightSize, getSuggestedMinimumHeight());   //根据heightMeasureSpec协助计算heightSizeAndState的大小   //resolveSizeAndState方法之后会分析   int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);   heightSize = heightSizeAndState & MEASURED_SIZE_MASK;   // Either expand children with weight to take up available space or   // shrink them if they extend beyond our current bounds. If we skipped   // measurement on any children, we need to measure them now.   //delta为额外的空间 及LinearLayout中未被分配的空间(可以为负)   int delta = heightSize - mTotalLength;   if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {       //skippedMeasure为第一次测量下对跳过测量的子View设置的       //weightSum为权重和 如果设置了总权重则使用我们所设置的  如果没有则使用子View的weight和       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               //计算weight属性分配的大小               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)) {                   //子视图已经被测量过                   //非EXACTLY view需要加上share                   int childHeight = child.getMeasuredHeight() + share;                   if (childHeight < 0) {                       childHeight = 0;                   }                   //重新测量View                   child.measure(childWidthMeasureSpec,                           MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));               } else {                   //如果当前是EXACTLY模式 说明没有被测量 需要进行测量                   //子视图首次被测量                   //EXACTLY模式下 将weight占比的高度分配给子View                       child.measure(childWidthMeasureSpec,                           MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,                                   MeasureSpec.EXACTLY));               }               // Child may now not fit in vertical dimension.               childState = combineMeasuredStates(childState, child.getMeasuredState()                       & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));           }          //处理子视图宽度           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);       // We have no limit, so make all weighted views as tall as the largest child.       // Children will have already been measured once.       if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {           for (int i = 0; i < count; i++) {               final View child = getVirtualChildAt(i);               if (child == null || child.getVisibility() == View.GONE) {                   continue;               }               final LinearLayout.LayoutParams lp =                       (LinearLayout.LayoutParams) child.getLayoutParams();               float childExtra = lp.weight;               if (childExtra > 0) {                  //使用最大子视图高度测量                   child.measure(                           MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),                                   MeasureSpec.EXACTLY),                           MeasureSpec.makeMeasureSpec(largestChildHeight,                                   MeasureSpec.EXACTLY));               }           }       }   }   if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {       maxWidth = alternativeMaxWidth;   }   maxWidth += mPaddingLeft + mPaddingRight;   // Check against our minimum width   maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());   setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),           heightSizeAndState);   if (matchWidth) {       forceUniformWidth(count, heightMeasureSpec);   }

resolveSizeAndState方法 定义在View中

/**   * Utility to reconcile a desired size and state, with constraints imposed   * by a MeasureSpec. Will take the desired size, unless a different size   * is imposed by the constraints. The returned value is a compound integer,   * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and   * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the   * resulting size is smaller than the size the view wants to be.   *   * @param size How big the view wants to be.   * @param measureSpec Constraints imposed by the parent.   * @param childMeasuredState Size information bit mask for the view's   *                           children.   * @return Size information bit mask as defined by   *         {@link #MEASURED_SIZE_MASK} and   *         {@link #MEASURED_STATE_TOO_SMALL}.   */  public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {      final int specMode = MeasureSpec.getMode(measureSpec);      final int specSize = MeasureSpec.getSize(measureSpec);      final int result;      switch (specMode) {          case MeasureSpec.AT_MOST:              if (specSize < size) {                  result = specSize | MEASURED_STATE_TOO_SMALL;              } else {                  result = size;              }              break;          case MeasureSpec.EXACTLY:              result = specSize;              break;          case MeasureSpec.UNSPECIFIED:          default:              result = size;      }      return result | (childMeasuredState & MEASURED_STATE_MASK);  }
delta为负的相关解析

相关代码及效果如下

Android View 相关源码分析之四 LinearLayout源码分析_第2张图片 负delta相关解析

根据之前的measure流程分析一下

  • 相关变量初始化
  • 第一次测量 两个子TextView都会被测量 TextView1.height = TextView1.height = 500dp 则mToatalLength为1000dp
  • mToatalLength再次测量跳过
  • 计算delta delta = heightSize - mTotalLength 根据resolveSizeAndState方法 父LinearLayout是EXACTLY模式 所以最终heightSize为500dp delta = -500dp
  • 根据weight分配剩余空间 TextView1.height = 500 + 1 / 5 * (- 500) = 400 dp
    TextView2.height = 500 + 4 / 5 * (- 500) = 100 dp

layout过程

  protected void onLayout(boolean changed, int l, int t, int r, int b) {    if (mOrientation == VERTICAL) {        layoutVertical(l, t, r, b);    } else {        layoutHorizontal(l, t, r, b);    }  }

我们可以看出 同样是分成水平和竖直两个方向的 同样分析竖直 方向下的layout过程

/**    * Position the children during a layout pass if the orientation of this    * LinearLayout is set to {@link #VERTICAL}.    *    * @see #getOrientation()    * @see #setOrientation(int)    * @see #onLayout(boolean, int, int, int, int)    * @param left    * @param top    * @param right    * @param bottom    */   void layoutVertical(int left, int top, int right, int bottom) {       final int paddingLeft = mPaddingLeft;       int childTop;       int childLeft;       //父View默认子View的宽度       final int width = right - left;       //子View的右侧默认位置       int childRight = width - mPaddingRight;       // 子View的可用空间大小       int childSpace = width - paddingLeft - mPaddingRight;       //子View的个数       final int count = getVirtualChildCount();       final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;       final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;       //根据LinearLayout设置的对其方式 设置第一个子View的Top值       switch (majorGravity) {          case Gravity.BOTTOM:              // mTotalLength contains the padding already              childTop = mPaddingTop + bottom - top - mTotalLength;              break;              // mTotalLength contains the padding already          case Gravity.CENTER_VERTICAL:              childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;              break;          case Gravity.TOP:          default:              childTop = mPaddingTop;              break;       }       //遍历各个子View       for (int i = 0; i < count; i++) {           final View child = getVirtualChildAt(i);           if (child == null) {               childTop += measureNullChild(i);           } else if (child.getVisibility() != GONE) {                //LinearLayout中子View的宽和高有measure过程决定               final int childWidth = child.getMeasuredWidth();               final int childHeight = child.getMeasuredHeight();               //获取子View的LayoutParams               final LinearLayout.LayoutParams lp =                       (LinearLayout.LayoutParams) child.getLayoutParams();               int gravity = lp.gravity;               if (gravity < 0) {                   gravity = minorGravity;               }               final int layoutDirection = getLayoutDirection();               final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);               //根据子View的对其方式设置Left值               switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {                   case Gravity.CENTER_HORIZONTAL:                       childLeft = paddingLeft + ((childSpace - childWidth) / 2)                               + lp.leftMargin - lp.rightMargin;                       break;                   case Gravity.RIGHT:                       childLeft = childRight - childWidth - lp.rightMargin;                       break;                   case Gravity.LEFT:                   default:                       childLeft = paddingLeft + lp.leftMargin;                       break;               }               //如果有分割线 添加分割线的高度               if (hasDividerBeforeChildAt(i)) {                   childTop += mDividerHeight;               }               //子View的top修改               childTop += lp.topMargin;               //用setChildFrame()方法设置子控件控件的在父控件上的坐标轴               setChildFrame(child, childLeft, childTop + getLocationOffset(child),                       childWidth, childHeight);               childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);               i += getChildrenSkipCount(child, i);           }       }   }

draw 源码分析

  protected void onDraw(Canvas canvas) {    if (mDivider == null) {        return;    }    if (mOrientation == VERTICAL) {        drawDividersVertical(canvas);    } else {        drawDividersHorizontal(canvas);    }  }

同样主要分析垂直方向的处理

  void drawDividersVertical(Canvas canvas) {      final int count = getVirtualChildCount();      //根据计算好的坐标绘制对应的子View      for (int i = 0; i < count; i++) {          final View child = getVirtualChildAt(i);          if (child != null && child.getVisibility() != GONE) {              if (hasDividerBeforeChildAt(i)) {                  final LayoutParams lp = (LayoutParams) child.getLayoutParams();                  final int top = child.getTop() - lp.topMargin - mDividerHeight;                  drawHorizontalDivider(canvas, top);              }          }      }      //绘制分割线      if (hasDividerBeforeChildAt(count)) {          final View child = getLastNonGoneChild();          int bottom = 0;          if (child == null) {              bottom = getHeight() - getPaddingBottom() - mDividerHeight;          } else {              final LayoutParams lp = (LayoutParams) child.getLayoutParams();              bottom = child.getBottom() + lp.bottomMargin;          }          drawHorizontalDivider(canvas, bottom);      }  }  void drawHorizontalDivider(Canvas canvas, int top) {    mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,            getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);    mDivider.draw(canvas);  }

以上

更多相关文章

  1. 《Android 复杂的列表视图新写法 MultiType》知识点整理
  2. 高级组件之画廊视图
  3. Android 架构组件之 ViewBinding(视图绑定)
  4. Android实现体重测量仪的源码
  5. Android UI控件详解-GridView(网格视图)
  6. Android 简单视图
  7. Android获取字体高度和设置行高
  8. Android获得屏幕高度和状态栏高度

随机推荐

  1. Django dumpdata无法序列化现有列
  2. mysql5.7.23免安装配置说明in win7
  3. MySQL是一个非常流行的小型关系型数据库
  4. mysql5.7 Access denied for user 'root'
  5. 分页检索大型ResultSet
  6. MYSQL SET类型字段的SQL查询某个字段保函
  7. C运行查询显示命令不同步?
  8. MySQL中如何插入blob类型数据
  9. mysql 排序两个字段/列表先根据时间升序
  10. 尝试删除sql中的重复记录,但查询进入无限