瞧一瞧,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 <Linearlayt是match或者dimense>,那么我们的高度就是测量规格里给的高度,如果是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就好了,那么就不会有测量效率问题啦。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容