LinearLayout 源碼分析總結(jié)

LinearLayout 主要是水平或者垂直排列子View淳梦,所以在調(diào)用三大方法時(shí)都是分為水平或者垂直方向的此衅。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    @Override
    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);
        }
    }

一般ViewGroup是不會(huì)做繪制操作的金句,LinearLayout因?yàn)橛蟹指罹€ divider 的屬性泪勒,所以會(huì)在 onDraw() 中繪制分割線栅组。

@Override
    protected void onDraw(Canvas canvas) {
        if (mDivider == null) {
            return;
        }

        if (mOrientation == VERTICAL) {
            drawDividersVertical(canvas);
        } else {
            drawDividersHorizontal(canvas);
        }
    }

measureVertical(),水平垂直邏輯類似梅掠。

  1. 通過 mTotalLength 計(jì)算所有childView 占用的高度酌住,(高度為wrap_content時(shí)用于計(jì)算LinearLayout的高度), 最后根據(jù)LinearLayout的高度計(jì)算出剩余空間,用于weight屬性分配剩余空間
  2. 使用 maxWidth 記錄 childView 最大的寬度阎抒,用于計(jì)算LinearLayout的寬度
  3. 第一次循環(huán)赂韵,如果childView的屬性 height=0,weight>0挠蛉;LinearLayout 的 heightMode 為固定大小
···
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
···

則會(huì)跳過childView的第一次measure過程祭示,所以這樣設(shè)置屬性可以提升性能

  1. 第一次循環(huán),除了3這種情況的childView谴古,所有的childView都會(huì)measure 一次
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
  1. 第二次循環(huán)质涛,設(shè)置 measureWithLargestChild="true" 屬性 并且高度為 warp_content ,則會(huì)進(jìn)入第二次循環(huán)掰担,用最大 childView 的高度 * childView 個(gè)數(shù) 再次計(jì)算 mTotalLength,防止LinearLayout高度出現(xiàn)問題汇陆。
if (useLargestChild &&  (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
  mTotalLength = 0;
  for (int i = 0; i < count; ++i) {
    ...
    mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin +
       lp.bottomMargin + getNextLocationOffset(child)); 
    ...
  }
}
  1. 根據(jù) mTotalLength 與 LinearLayout的高度,計(jì)算出剩余空間带饱,第三次循環(huán)會(huì)weight >0 的 childView 會(huì)調(diào)用 measure 計(jì)算大小

  2. 高度為 wrap_content 時(shí)毡代,因?yàn)闆]有剩余空間,進(jìn)入不到第三次循環(huán)勺疼,所有這個(gè)情況 weight 屬性無效教寂。

  3. android:measureWithLargestChild="true" 屬性需要weight屬性配合使用,不然只會(huì)增加 LinearLayout的高度执庐。
    代碼只會(huì)進(jìn)入第二個(gè)循環(huán)酪耕,用最大childView 的大小計(jì)算 LinearLayout的高度,但不會(huì)進(jìn)入第三個(gè)循環(huán)來重新設(shè)置childView的大小轨淌。

float childExtra = lp.weight;
if (childExtra > 0) {
  child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),MeasureSpec.EXACTLY),
             MeasureSpec.makeMeasureSpec(largestChildHeight,MeasureSpec.EXACTLY));
}
  1. 最后迂烁,當(dāng)LinearLayout 寬度為wrap_content, childView 高度為 match_parent, LinearLayout 會(huì)最后再次 measure 寬度為match_parent 的childView,所以開發(fā)中要避免這種情況看尼。

  2. 當(dāng)LinearLayout 寬度為wrap_content,childView 的寬度盟步,一個(gè)為 wrap_content ,一個(gè)為 match_parent,以wrap_content 為準(zhǔn)

if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
  maxWidth = alternativeMaxWidth; //wrap_content view的寬度
}
  1. android:measureWithLargestChild="true" 與 divider 屬性沖突藏斩,因?yàn)榈诙窝h(huán)沒有記錄 divider 高度,在展示時(shí)會(huì)出現(xiàn)問題

layoutVertical()却盘,水平垂直邏輯類似狰域。

onLayout() 主要用于確認(rèn)View展示的位置,一般有父容器確認(rèn)谷炸,所以這個(gè)方法主要用于確認(rèn)childView 展示的位置。

protected void onLayout(boolean changed, int l, int t, int r, int b) {
}

確認(rèn)位置其實(shí)只要知道左上角位置即可,width,height 在measure 過程已經(jīng)獲得禀挫。

child.layout(left, top, left + width, top + height);

所以主要的邏輯是獲取到 top left旬陡。

  1. 根據(jù) LinearLayout 的 gravity屬性 獲取 childTop
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;
}        
  1. 根據(jù) childView 的 layout_garvity 屬性獲得 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;
}                
  1. 就能確定 childView 的位置了
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);

drawDividersVertical(),水平垂直邏輯類似语婴。

void drawDividersVertical(Canvas canvas) {
    final int count = getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child != null && child.getVisibility() != GONE) {
            //判斷當(dāng)前View之前是否要繪制分割線
            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);
    }
}

所有代碼與注釋,可以通過自定義LinearLayout的方式打斷點(diǎn)調(diào)試


void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    mTotalLength = 0;     // 所有 childView 的高度和 + 本身的 padding描孟,注意:它和 LinearLayout 本身的高度是不同的
    int maxWidth = 0;     // 所有 childView 中寬度的最大值
    int childState = 0;
    int alternativeMaxWidth = 0;  // 所有 layout_weight <= 0 的 childView 中寬度的最大值  用于獲取最大款第
    int weightedMaxWidth = 0;     // 所有 layout_weight >0 的 childView 中寬度的最大值     用于獲取最大款第
    boolean allFillParent = true;  //是否 layout_width 全是 matchParent
    float totalWeight = 0;        // 所有 childView 的 weight 之和

    final int count = getVirtualChildCount();  //getCount()

    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    boolean matchWidth = false;       //是否填充 LinearLayout,最后 measure一次child判斷使用
    boolean skippedMeasure = false;   //是否跳過Measure過程

    final int baselineChildIndex = mBaselineAlignedChildIndex;
    final boolean useLargestChild = mUseLargestChild;  //可以通過 xml 屬性 android:measureWithLargestChild 設(shè)置的

    int largestChildHeight = Integer.MIN_VALUE;
    int consumedExcessSpace = 0;    //使用的剩余空間

    int nonSkippedChildCount = 0;   // 沒有跳過的 childView 個(gè)數(shù),用于分割線的邏輯

    // See how tall everyone is. Also remember max width.
    // 看看每個(gè) View 有多高,記錄最大寬度
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);   //getChildAt()
        if (child == null) {
            mTotalLength += measureNullChild(i);   //0
            continue;
        }

        if (child.getVisibility() == View.GONE) {
           i += getChildrenSkipCount(child, i);   //0
           continue;
        }

        nonSkippedChildCount++;     // 沒有跳過的 childView 個(gè)數(shù)
        if (hasDividerBeforeChildAt(i)) {    //是否有分割線
            mTotalLength += mDividerHeight;   //加上分割線高度
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        totalWeight += lp.weight;   //計(jì)算總權(quán)重 totalWeight

        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;  //是否使用剩余空間
        //LinearLayout 的高度是固定的砰左,child 的  lp.height == 0 && lp.weight > 0匿醒,跳過 else
        //步驟 skippedMeasure=true
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            // Optimization: don't bother measuring children who are only
            // laid out using excess space. These views will get measured
            // later if we have space to distribute.
            //優(yōu)化:不要麻煩測量那些只使用多余空間的孩子。如果我們有空間分布缠导,這些視圖將在稍后得到度量廉羔。
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
        } else {
            if (useExcessSpace) {
                // The heightMode is either UNSPECIFIED or AT_MOST, and
                // this child is only laid out using excess space. Measure
                // using WRAP_CONTENT so that we can find out the view's
                // optimal height. We'll restore the original height of 0
                // after measurement.
                //heightMode 是 UNSPECIFIED or AT_MOST, childView 只使用剩余空間
                //使用WRAP_CONTENT進(jìn)行測量僻造,以便找出視圖的最佳高度憋他。測量后恢復(fù)原來高度 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).
            //確定這個(gè)孩子想要多大。如果這個(gè)或前面的孩子給了一個(gè)權(quán)重髓削,那么我們允許它使用所有可用的空間
            //(如果需要竹挡,我們將在稍后縮小內(nèi)容)。
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            //計(jì)算 childView 高度
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);

            final int childHeight = child.getMeasuredHeight();
            if (useExcessSpace) {
                // Restore the original height and record how much space
                // we've allocated to excess-only children so that we can
                // match the behavior of EXACTLY measurement.
                //恢復(fù)原來的高度立膛,并記錄我們分配給 excess-only children 的空間大小揪罕,以便我們能夠準(zhǔn)確地匹配測量的行為。
                lp.height = 0;
                consumedExcessSpace += childHeight;
            }

            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                   lp.bottomMargin + getNextLocationOffset(child));    //getNextLocationOffset() 0

            if (useLargestChild) {
                largestChildHeight = Math.max(childHeight, largestChildHeight); //最大childHeight
            }
        }

        /**
         * If applicable, compute the additional offset to the child's baseline
         * we'll need later when asked {@link #getBaseline}.
         */
        if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
           mBaselineChildTop = mTotalLength;
        }

        // if we are trying to use a child index for our baseline, the above
        // book keeping only works if there are no children above it with
        // weight.  fail fast to aid the developer.
        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;
        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.
            // 線性布局的寬度會(huì)縮放宝泵,并且至少有一個(gè)孩子說它想要匹配我們的寬度好啰。
            // 設(shè)置一個(gè)標(biāo)志,表明當(dāng)我們知道寬度時(shí)儿奶,至少需要重新測量該視圖坎怪。
            //LinearLayout 的寬度是 WRAP_CONTENT
            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) {
            /*
             * Widths of weighted Views are bogus if we end up
             * remeasuring, so keep them separate.
             */
             //如果我們最終重新測量加權(quán)視圖的寬度,那么它就是假的廓握,所以要把它們分開搅窿。
            weightedMaxWidth = Math.max(weightedMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        }

        i += getChildrenSkipCount(child, i);   //0
    }//第一次循環(huán)結(jié)束 獲得 alternative  weightedMaxWidth  maxWidth  matchWidth largestChildHeight

    if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
        mTotalLength += mDividerHeight;
    }

    //useLargestChild 屬性指定
    //所以接下來根據(jù) largestChildHeight 重新計(jì)算高度
    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
            // 負(fù) margins
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
        }
    }//第二個(gè)循環(huán) useLargestChild   使用 largestChildHeight 計(jì)算 mTotalLength

    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Check against our minimum height
    //檢查 最低高度
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());  //(mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    // Reconcile our calculated size with the heightMeasureSpec
    //將我們計(jì)算出的尺寸與高度測量值進(jìn)行比對(duì)
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    // 通過 heightMeasureSpec嘁酿,調(diào)整 heightSize 的大小,具體的過程需要
    // 看一下 resolveSizeAndState() 方法的實(shí)現(xiàn)
    // 經(jīng)過調(diào)整之后就是 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.
    // 重新計(jì)算有 weight 屬性的 childView 大小男应,
    // 如果還有可用的空間闹司,則擴(kuò)展 childView,計(jì)算其大小
    // 如果 childView 超出了 LinearLayout 的邊界沐飘,則收縮 childView
    int remainingExcess = heightSize - mTotalLength
            + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace); //剩余空間
    if (skippedMeasure
            || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
        //// 根據(jù) mWeightSum 計(jì)算得到 remainingWeightSum游桩,mWeightSum 是通過
        // `android:weightSum` 屬性設(shè)置的,totalWeight 是通過第一次 for 循環(huán)計(jì)算得到的
        float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

        mTotalLength = 0;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null || child.getVisibility() == View.GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final float childWeight = lp.weight;

            // 這是設(shè)置了 weight 的情況下耐朴,最重要的一行代碼
            // remainingExcess 剩余高度 * ( childView 的 weight / remainingWeightSum)
            // share 便是此 childView 通過這個(gè)公式計(jì)算得到的高度借卧,
            // 并重新計(jì)算剩余高度 remainingExcess 和剩余權(quán)重總和 remainingWeightSum
            if (childWeight > 0) {
                final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                remainingExcess -= share;
                remainingWeightSum -= childWeight;

                final int childHeight;
                if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                    childHeight = largestChildHeight;
                } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                        || heightMode == MeasureSpec.EXACTLY)) {
                    // This child needs to be laid out from scratch using
                    // only its share of excess space.
                    childHeight = share;
                } else {
                    // This child had some intrinsic height to which we
                    // need to add its share of excess space.
                    childHeight = child.getMeasuredHeight() + share;
                }

                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        Math.max(0, childHeight), MeasureSpec.EXACTLY);
                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                        lp.width);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                // Child may now not fit in vertical dimension.
                childState = combineMeasuredStates(childState, child.getMeasuredState()
                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
            } //child measure

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

/**
 *  再次計(jì)算寬度
 */
private void forceUniformWidth(int count, int heightMeasureSpec) {
    // Pretend that the linear layout has an exact size.
    int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
            MeasureSpec.EXACTLY);
    for (int i = 0; i< count; ++i) {
       final View child = getVirtualChildAt(i);
       if (child != null && child.getVisibility() != GONE) {
           LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());

           if (lp.width == LayoutParams.MATCH_PARENT) {
               // Temporarily force children to reuse their old measured height
               // FIXME: this may not be right for something like wrapping text?
               int oldHeight = lp.height;
               lp.height = child.getMeasuredHeight();

               // Remeasue with new dimensions
               measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
               lp.height = oldHeight;
           }
       }
    }
}


void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();

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

    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;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

void drawDividersVertical(Canvas canvas) {
    final int count = getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child != null && child.getVisibility() != GONE) {
            //判斷當(dāng)前View之前是否要繪制分割線
            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);
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筛峭,隨后出現(xiàn)的幾起案子铐刘,更是在濱河造成了極大的恐慌,老刑警劉巖影晓,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镰吵,死亡現(xiàn)場離奇詭異,居然都是意外死亡挂签,警方通過查閱死者的電腦和手機(jī)疤祭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饵婆,“玉大人勺馆,你說我怎么就攤上這事煞赢∠入” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵聘殖,是天一觀的道長芹关。 經(jīng)常有香客問我续挟,道長,這世上最難降的妖魔是什么侥衬? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任诗祸,我火速辦了婚禮,結(jié)果婚禮上轴总,老公的妹妹穿的比我還像新娘直颅。我一直安慰自己,他們只是感情好怀樟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布功偿。 她就那樣靜靜地躺著,像睡著了一般往堡。 火紅的嫁衣襯著肌膚如雪械荷。 梳的紋絲不亂的頭發(fā)上共耍,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音吨瞎,去河邊找鬼痹兜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颤诀,可吹牛的內(nèi)容都是我干的字旭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼崖叫,長吁一口氣:“原來是場噩夢啊……” “哼遗淳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起心傀,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤屈暗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后剧包,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恐锦,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡往果,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年疆液,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陕贮。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堕油,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肮之,到底是詐尸還是另有隱情掉缺,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布戈擒,位于F島的核電站眶明,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏筐高。R本人自食惡果不足惜搜囱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柑土。 院中可真熱鬧蜀肘,春花似錦、人聲如沸稽屏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狐榔。三九已至坛增,卻和暖如春获雕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轿偎。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工典鸡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坏晦。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓萝玷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昆婿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子球碉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容