Android View 源码解析(四) - LinearLay

作者: Android架构 | 来源:发表于2019-01-30 20:49 被阅读8次

    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被二次测量

    实例如下

    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为负的相关解析

    相关代码及效果如下

    负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);
      }
    

    以上
    【附录】

    资料图

    需要资料的朋友可以加入Android架构交流QQ群聊:513088520

    点击链接加入群聊【Android移动架构总群】:加入群聊

    获取免费学习视频,学习大纲另外还有像高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等Android高阶开发资料免费分享。

    相关文章

      网友评论

        本文标题:Android View 源码解析(四) - LinearLay

        本文链接:https://www.haomeiwen.com/subject/emthsqtx.html