瞧一瞧,LinearLayout的源码

先看看美女吧
容颜易老,真情不老,可真情却不可求...
  1. 核心成员标记
  2. 内部类
  3. 核心方法分解
1. 核心成员,标记
  • orientation: 横向或者竖向排列,默认是0, 0是横向的, 1是竖向的。

  • gravity属性:控制子view整体的摆放,如left-左顶边, right-右顶边, bottom-下顶边,center-horizontal-水平居中。

  • weightSum:横向或者纵向的权重和,默认是-1哦也就是没有权重。一般是不计算该数值,而是计算子view集设置的权重和,与该值不同呢。

  • divider, showDividers, dividerPadding:内置分割线,以及他的位置和padding距离。注意padding距离是不会拉开子元素的间距的,而只是设定分割线本身的宽度呢。

2. 内部类
  • LinearLayout.LayoutParams: 提供给LinearLayout的子元素布局定位使用。
    • weight: 宽或高的权重,不设置默认是0。
    • gravity: 其实这个和linearlayout本身的gravity属性是很相似的,只不过前者是应用到所有的item,这个只是给当前的子child使用, 不设置默认是-1,代表啥都不是。
3. 重要方法分解
  • onDraw: 该方法是来绘制view内容的,但LinearLayout的内容只有divider-分割线, 所以该方法只是根据横向还是纵向规则来绘制divider哦,别无其他的内容。

  • drawDividersVertical:绘制纵向排列的分割线,分割线其实就是横着的一条线啦。

    1. 遍历所有的child, 然后根据showDividers是middle, start, end来给child前面绘制分割线。比如:如果是middle,那么则在每个子child的前面都绘制一个分割线(除了第一个chld外), 如是start,则在第一个child前绘制分割线;如是end, 那么在最后一个child后面绘制分割线。
    
    2. drawHorizontalDivider,这个是绘制纵排列的分割线divider的核心方法, 这里很简单就是先设置drawable的上,下,左右边界,然后将该drawable绘制到view的canvas上。即drawable.draw(canvas)。
    
    
  • drawDividersHorizontal: 绘制横向排列的分割,就是一条垂线啦。

    1. 逻辑和上面的基本上一样的。有一个地方不同是,横向绘制,需要考虑是从左到右还是从右边到左边。
    
    
  • onMeasure: 测量---横向测量与纵向测量。

  • measureVertical: 纵向测量

  • 1. 第一次测量,测出大部分的子view, 并得出LinearLayout的高度。要知道,这里的子view如果是wrap的或者带有权重的测量的都不是最终的。
    2. 第二次测量,补充测量前面没有测过的子view, 如果确定了LinearLayout的高度后,前面测量的子view并未有填充满linearlayout的高(因为带权重的view第一次测量的高度不是最终的),这里会通过他们的权重比去计算出最终的各个带wrap, 或者带有权重的view的高度。
    3. 设置LinearLayout的高度,并纠正那些子view的宽度是match的,测量出这些子view的宽度。

  • 第一次测量:("废话一堆,show me the code",好的, 仔细地看注释哦~)

    for (int i = 0; i < count; ++i) {//
             
                .......
                totalWeight += lp.weight;
                //情景一, 如果LinearLayout是精确模式, child的height=0, weight>0,那么该child第一次测量的时候不测量。
                if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                    final int totalLength = mTotalLength;
                    //但是margin空间累计起来,算是LinearLayout占用的空间
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    //除了情况一之外, 所有的子view都是要测量的呢!
                    
                    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
                        // 这里的情景二,是LinearLayout不是精确模式, 要先测量该子view, 要用wrap_conet,去计算我们的view占据的空间。就是说如果view(0dp+weight),测量的实际结果是由view本身的wrap给出的限度值。
                        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).
                    
                    // 测量我们的子view,如果没有weight那就是除开已经占据的mTotalLength空间,去进行测量啦;如果有weight这里测量的view就不考虑已经用过的空间。
                    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;
                    // 将所有测量过的view都累计起来,计算已经占据了多少的空间,为后面计算LinearLayout的高度而用。
                    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                           lp.bottomMargin + getNextLocationOffset(child));
    
                    if (useLargestChild) {//如果xml设置了measureWithLargestChild,这里会记录最大高度的view, 以备后面使用。
                     
                        largestChildHeight = Math.max(childHeight, largestChildHeight);
                    }
                }
    
               ......
                   
                //如果LinearLayout的width是wrap,并且子view是match的情景,后面需要将该子view的宽度来一次校准。充满linearlayout的宽度。
                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;
                }
    
              ......
                  //累计divider的高度
                  if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
                mTotalLength += mDividerHeight;
                }
        
                //这是一种特殊情况,如果xml使用了measureWithLargestChild,并且LinearLayout不是Exactly,一般也就是wrap啦, 那么我们LinearLayout的高度就是n*largestChildHeight啦,当然要加上对应的margin,不信你试试。
                 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();
                    // 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());
            
            // 通过LinearLayout使用的高度heightSize,和LinearLayout的测量规格来一次最终测量,
        //从而得出我们的LinearLayout的最终高度。如果测量规格是Exactyly ,那么我们的高度就是测量规格里给的高度,如果是At_most(linearlayout是wrap), 那么我们的高度就是上面的heightSize啦。
            int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        //linearlayout最终的高度
            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.
        }
    }
    
    
  • 第二次测量:show me the code

      ......
         
          //看看还有没有剩余空间。哪些情况下有剩余空间呢?比如我们的LinearLayout是match的时候呀,且有子view有weight,高度是0或者是wrap 这个时候我们第一次测量后可能有剩余的空间,我们要把这个剩余的空间平分给带weight的兄弟们。
        int delta = heightSize - mTotalLength;
            if (skippedMeasure || 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) {
                        //这里就是weight平分的逻辑啦。
                        // 公式 = 剩余高度*(子控件的weight/weightSum),也就是子控件的weight占比*剩余高度
                        // 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
                        //走这里的时候,view之前有高度,这里再加上平分一份就是view的最终高度啦。好好想想这里,view之前有高度就是测量过了啦,这里又测量,这就是linearlayout子view耗性能的地方。所以linearLayout如果设置了weight不要轻易设置wrap或者dimense,这是不好滴操作的。
                        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    
                            //如果子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 {//这里其实是对xml里配置了measureWithLargestChild的情景,且上面的条件不满足,主要是没了剩余空间的,将所有的子view带weight都设为高largestChildHeight。比如我们的LinearLayout是wrap, 然后子view都是wrap又有weight, 第一次测量完之后其实是没有剩余空间的,就走到这里来了,会用最大view的高度给其他的带weight的view设高,不管weight是多少都是一样的。不信你试试呢。。。。
                
                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));
                        }
                    }
                }
            }
    
    
    
    
  • 设置LinearLayout的高度,并纠正那些子view的宽度是match的,测量出这些子view的宽度。

       maxWidth += mPaddingLeft + mPaddingRight;
    
            // Check against our minimum width
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
            
    //哈哈,这里就设置了linearLayout的宽,高啦。差不多结束啦。
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
    
    //纠正那些子view的宽度是match的,测量出这些子view的宽度
            if (matchWidth) {
                //这个方法很简单,看看就好了。
                forceUniformWidth(count, heightMeasureSpec);
            }
    
    //结束啦。
    
  • measureHorizontal: 逻辑和纵向测量差不多,就不说啦。

  • onLayout:纵向横向布局子view呀, layoutVertical,layoutHorizontal。

    • layoutVertical:

       void layoutVertical(int left, int top, int right, int bottom) {
              final int paddingLeft = mPaddingLeft;
      
              int childTop;
              int childLeft;
              
              // 计算出LinearLayout的宽度
              final int width = right - left;
              int childRight = width - mPaddingRight;
              
              // Space available for child
              int childSpace = width - paddingLeft - mPaddingRight;
              
              final int count = getVirtualChildCount();
      
              //垂直向的重心规则计算,计算出第一个child的top位置
              final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
              final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
      
              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;
              }
      
              //计算完child的top位置之后, 现在要计算每个child的left和top位置呢。
              for (int i = 0; i < count; i++) {
                  final View child = getVirtualChildAt(i);
                  if (child == null) {
                      childTop += measureNullChild(i);
                  } else if (child.getVisibility() != GONE) {
                      final int childWidth = child.getMeasuredWidth();
                      final int childHeight = child.getMeasuredHeight();
                      
                      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);
                      //读取水平方向的重心
                      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;
                      }
                      //
                      childTop += lp.topMargin;
                      //计算完child的left, top,就可以在这里设置他的位置啦。
                      setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                              childWidth, childHeight);
                      //计算下个child的top啦。
                  childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
      
                    i += getChildrenSkipCount(child, i);
                  }
              }
          }
      
      
      

4. 疑问解惑

  • 当你在布局时候,LinearLayout的子控件中写上了weight, ide有时候会提醒你注意效率问题, linearLayout哪些布局情况会导致效率低下?

    • 从上面可以看出,linearlayout的测量有两次。如果子view两次都测量了,那么效率自然是受到影响的啦。哪些情况会测量两次呢:
      1. view的布局是带weight的,而且高度不为0, LInearLayout是Exactly, 这种第一次会测量,第二次一般也会测量,所以就有两次测量啦。(之所以说一般是要求第二次测量之前是有剩余空间去分配权重的哦)
      2. view带权重,LinearLayout不是Exactly模式,第一次会测量,第二次一般也会测量,也是要求有剩余空间的。
      3. view带权重,linearayout不是Exactly模式,设定了useLargestChild = true(这里一般是没有剩余空间的), 依然也会再测试一次。
    • 总结:所以呀安全策略, 就是view如果带权重呢,LinearLayout设定为Exactly模式, 子view的待测宽或者高设置为0就好了,那么就不会有测量效率问题啦。

如果有帮助到你认识系统控件,点个赞再走呗......

你可能感兴趣的:(瞧一瞧,LinearLayout的源码)