瞧一瞧,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閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容