package org.holoeverywhere.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import org.holoeverywhere.R; import org.holoeverywhere.drawable.DrawableCompat; public class LinearLayout extends android.widget.LinearLayout implements DrawableCompat.IStateOverlay { public static final int HORIZONTAL = 0; public static final int LAYOUT_DIRECTION_LTR = 0; public static final int LAYOUT_DIRECTION_RTL = 1; public static final int SHOW_DIVIDER_ALL = 7; public static final int SHOW_DIVIDER_BEGINNING = 1; public static final int SHOW_DIVIDER_END = 4; public static final int SHOW_DIVIDER_MIDDLE = 2; public static final int SHOW_DIVIDER_NONE = 0; public static final int VERTICAL = 1; private static final int INDEX_BOTTOM = 2; private static final int INDEX_CENTER_VERTICAL = 0; private static final int INDEX_FILL = 3; private static final int INDEX_TOP = 1; private static final int VERTICAL_GRAVITY_COUNT = 4; private final DrawableCompat.StateOverlay mStateOverlay; private Drawable mDivider; private int mDividerHeight; private int mDividerPadding; private int mDividerWidth; private int mShowDividers; private int mGravity; private int[] mMaxAscent, mMaxDescent; private int mTotalLength; private int mBaselineChildTop; private boolean mUseLargestChild; public LinearLayout(Context context) { this(context, null); } public LinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LinearLayout, defStyle, 0); int index = a.getInt(R.styleable.LinearLayout_android_gravity, -1); if (index >= 0) { setGravity(index); } setMeasureWithLargestChildEnabled(a.getBoolean(R.styleable.LinearLayout_android_measureWithLargestChild, false)); setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_android_divider)); TypedValue value = new TypedValue(); a.getValue(R.styleable.LinearLayout_android_showDividers, value); mShowDividers = value.data; a.getValue(R.styleable.LinearLayout_android_dividerPadding, value); mDividerPadding = TypedValue.complexToDimensionPixelSize(value.data, context.getResources().getDisplayMetrics()); a.recycle(); mStateOverlay = new DrawableCompat.StateOverlay(this, context, attrs, defStyle); } public static int getAbsoluteGravity(int gravity, int layoutDirection) { int result = gravity; if ((result & Gravity.RELATIVE_LAYOUT_DIRECTION) > 0) { if ((result & Gravity.START) == Gravity.START) { result &= ~Gravity.START; if (layoutDirection == LAYOUT_DIRECTION_RTL) { result |= Gravity.RIGHT; } else { result |= Gravity.LEFT; } } else if ((result & Gravity.END) == Gravity.END) { result &= ~Gravity.END; if (layoutDirection == LAYOUT_DIRECTION_RTL) { result |= Gravity.LEFT; } else { result |= Gravity.RIGHT; } } result &= ~Gravity.RELATIVE_LAYOUT_DIRECTION; } return result; } public boolean isMeasureWithLargestChildEnabled() { return mUseLargestChild; } public void setMeasureWithLargestChildEnabled(boolean enabled) { mUseLargestChild = enabled; } @Override public boolean isActivated() { return mStateOverlay.isActivated(); } @Override public void setActivated(boolean activated) { mStateOverlay.setActivated(activated); } @Override protected int[] onCreateDrawableState(int extraSpace) { if (mStateOverlay == null) { return super.onCreateDrawableState(extraSpace); } return mStateOverlay.onCreateDrawableState(extraSpace); } @Override public int[] superOnCreateDrawableState(int extraSpace) { return super.onCreateDrawableState(extraSpace); } void drawDividersHorizontal(Canvas canvas) { final int count = getChildCount(); final boolean isLayoutRtl = isLayoutRtl(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child != null && child.getVisibility() != GONE) { if (hasDividerBeforeChildAt(i)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int position; if (isLayoutRtl) { position = child.getRight() + lp.rightMargin; } else { position = child.getLeft() - lp.leftMargin - mDividerWidth; } drawVerticalDivider(canvas, position); } } } if (hasDividerBeforeChildAt(count)) { final View child = getChildAt(count - 1); int position; if (child == null) { if (isLayoutRtl) { position = getPaddingLeft(); } else { position = getWidth() - getPaddingRight() - mDividerWidth; } } else { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (isLayoutRtl) { position = child.getLeft() - lp.leftMargin - mDividerWidth; } else { position = child.getRight() + lp.rightMargin; } } drawVerticalDivider(canvas, position); } } void drawDividersVertical(Canvas canvas) { final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(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 = getChildAt(count - 1); 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); } void drawVerticalDivider(Canvas canvas, int left) { mDivider.setBounds(left, getPaddingTop() + mDividerPadding, left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding); mDivider.draw(canvas); } private void forceUniformHeight(int count, int widthMeasureSpec) { int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (lp.height == android.view.ViewGroup.LayoutParams.MATCH_PARENT) { int oldWidth = lp.width; lp.width = child.getMeasuredWidth(); measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0); lp.width = oldWidth; } } } } private void forceUniformWidth(int count, int heightMeasureSpec) { int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (lp.width == android.view.ViewGroup.LayoutParams.MATCH_PARENT) { int oldHeight = lp.height; lp.height = child.getMeasuredHeight(); measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0); lp.height = oldHeight; } } } } @Override public Drawable getDividerDrawable() { return mDivider; } @Override public void setDividerDrawable(Drawable divider) { if (divider == mDivider) { return; } mDivider = divider; if (divider != null) { mDividerWidth = divider.getIntrinsicWidth(); mDividerHeight = divider.getIntrinsicHeight(); } else { mDividerWidth = 0; mDividerHeight = 0; } setWillNotDraw(divider == null); requestLayout(); } @Override public int getDividerPadding() { return mDividerPadding; } @Override public void setDividerPadding(int padding) { mDividerPadding = padding; } public int getDividerWidth() { return mDividerWidth; } @Override public int getShowDividers() { return mShowDividers; } @Override public void setShowDividers(int showDividers) { if (showDividers != mShowDividers) { requestLayout(); } mShowDividers = showDividers; } protected boolean hasDividerBeforeChildAt(int childIndex) { if (childIndex == 0) { return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0; } else if (childIndex == getChildCount()) { return (mShowDividers & SHOW_DIVIDER_END) != 0; } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) { boolean hasVisibleViewBefore = false; for (int i = childIndex - 1; i >= 0; i--) { if (getChildAt(i).getVisibility() != GONE) { hasVisibleViewBefore = true; break; } } return hasVisibleViewBefore; } return false; } protected boolean isLayoutRtl() { return ViewCompat.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL; } void layoutHorizontal() { final boolean isLayoutRtl = isLayoutRtl(); final int paddingTop = getPaddingTop(); int childTop; int childLeft; final int height = getBottom() - getTop(); int childBottom = height - getPaddingBottom(); int childSpace = height - paddingTop - getPaddingBottom(); final int count = getChildCount(); final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final boolean baselineAligned = isBaselineAligned(); final int[] maxAscent = mMaxAscent; final int[] maxDescent = mMaxDescent; final int layoutDirection = ViewCompat.getLayoutDirection(this); switch (getAbsoluteGravity(majorGravity, layoutDirection)) { case Gravity.RIGHT: childLeft = getPaddingLeft() + getRight() - getLeft() - mTotalLength; break; case Gravity.CENTER_HORIZONTAL: childLeft = getPaddingLeft() + (getRight() - getLeft() - mTotalLength) / 2; break; case Gravity.LEFT: default: childLeft = getPaddingLeft(); break; } int start = 0; int dir = 1; if (isLayoutRtl) { start = count - 1; dir = -1; } for (int i = 0; i < count; i++) { int childIndex = start + dir * i; final View child = getChildAt(childIndex); if (child != null && child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int childBaseline = -1; final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (baselineAligned && lp.height != android.view.ViewGroup.LayoutParams.MATCH_PARENT) { childBaseline = child.getBaseline(); } int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: childTop = paddingTop + lp.topMargin; if (childBaseline != -1) { childTop += maxAscent[INDEX_TOP] - childBaseline; } break; case Gravity.CENTER_VERTICAL: childTop = paddingTop + (childSpace - childHeight) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = childBottom - childHeight - lp.bottomMargin; if (childBaseline != -1) { int descent = child.getMeasuredHeight() - childBaseline; childTop -= maxDescent[INDEX_BOTTOM] - descent; } break; default: childTop = paddingTop; break; } if (hasDividerBeforeChildAt(childIndex)) { childLeft += mDividerWidth; } childLeft += lp.leftMargin; setChildFrame(child, childLeft, childTop, childWidth, childHeight); childLeft += childWidth + lp.rightMargin; } } } void layoutVertical() { final int paddingLeft = getPaddingLeft(); int childTop; int childLeft; final int width = getRight() - getLeft(); int childRight = width - getPaddingRight(); int childSpace = width - paddingLeft - getPaddingRight(); final int count = getChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; switch (majorGravity) { case Gravity.BOTTOM: childTop = getPaddingTop() + getBottom() - getTop() - mTotalLength; break; case Gravity.CENTER_VERTICAL: childTop = getPaddingTop() + (getBottom() - getTop() - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = getPaddingTop(); break; } for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child != null && 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 = ViewCompat.getLayoutDirection(this); final int absoluteGravity = 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, childWidth, childHeight); childTop += childHeight + lp.bottomMargin; } } } void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); } void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; int maxHeight = 0; int childState = 0; int alternativeMaxHeight = 0; int weightedMaxHeight = 0; boolean allFillParent = true; float totalWeight = 0; final int count = getChildCount(); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchHeight = false; if (mMaxAscent == null || mMaxDescent == null) { mMaxAscent = new int[VERTICAL_GRAVITY_COUNT]; mMaxDescent = new int[VERTICAL_GRAVITY_COUNT]; } final int[] maxAscent = mMaxAscent; final int[] maxDescent = mMaxDescent; maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; final boolean baselineAligned = isBaselineAligned(); final boolean useLargestChild = isMeasureWithLargestChildEnabled(); final boolean isExactly = widthMode == MeasureSpec.EXACTLY; int largestChildWidth = Integer.MIN_VALUE; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == GONE) { continue; } if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerWidth; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) { if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); } if (baselineAligned) { final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(freeSpec, freeSpec); } } else { int oldWidth = Integer.MIN_VALUE; if (lp.width == 0 && lp.weight > 0) { oldWidth = 0; lp.width = android.view.ViewGroup.LayoutParams.WRAP_CONTENT; } measureChildBeforeLayout(child, i, widthMeasureSpec, totalWeight == 0 ? mTotalLength : 0, heightMeasureSpec, 0); if (oldWidth != Integer.MIN_VALUE) { lp.width = oldWidth; } final int childWidth = child.getMeasuredWidth(); if (isExactly) { mTotalLength += childWidth + lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin); } if (useLargestChild) { largestChildWidth = Math.max(childWidth, largestChildWidth); } } boolean matchHeightLocally = false; if (heightMode != MeasureSpec.EXACTLY && lp.height == android.view.ViewGroup.LayoutParams.MATCH_PARENT) { matchHeight = true; matchHeightLocally = true; } final int margin = lp.topMargin + lp.bottomMargin; final int childHeight = child.getMeasuredHeight() + margin; if (VERSION.SDK_INT >= 11) { childState |= child.getMeasuredState(); } if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK; final int index = (gravity >> Gravity.AXIS_Y_SHIFT & ~Gravity.AXIS_SPECIFIED) >> 1; maxAscent[index] = Math.max(maxAscent[index], childBaseline); maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); } } maxHeight = Math.max(maxHeight, childHeight); allFillParent = allFillParent && lp.height == android.view.ViewGroup.LayoutParams.MATCH_PARENT; if (lp.weight > 0) { weightedMaxHeight = Math.max(weightedMaxHeight, matchHeightLocally ? margin : childHeight); } else { alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight); } } if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerWidth; } if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) { final int ascent = Math.max(maxAscent[INDEX_FILL], Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); final int descent = Math.max(maxDescent[INDEX_FILL], Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); maxHeight = Math.max(maxHeight, ascent + descent); } if (useLargestChild && (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == GONE) { continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (isExactly) { mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + lp.leftMargin + lp.rightMargin); } } } mTotalLength += getPaddingLeft() + getPaddingRight(); int widthSize = mTotalLength; widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); int widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec, 0); widthSize = widthSizeAndState & MEASURED_SIZE_MASK; int delta = widthSize - mTotalLength; if (delta != 0 && totalWeight > 0.0f) { final float f = getWeightSum(); float weightSum = f > 0.0f ? f : totalWeight; maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; maxHeight = -1; mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == View.GONE) { continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { int share = (int) (childExtra * delta / weightSum); weightSum -= childExtra; delta -= share; final int childHeightMeasureSpec = getChildMeasureSpec( heightMeasureSpec, getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, lp.height); if (lp.width != 0 || widthMode != MeasureSpec.EXACTLY) { int childWidth = child.getMeasuredWidth() + share; if (childWidth < 0) { childWidth = 0; } child.measure( MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), childHeightMeasureSpec); } else { child.measure(MeasureSpec.makeMeasureSpec( share > 0 ? share : 0, MeasureSpec.EXACTLY), childHeightMeasureSpec); } if (VERSION.SDK_INT >= 11) { childState |= child.getMeasuredState() & MEASURED_STATE_MASK; } } if (isExactly) { mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); } boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && lp.height == android.view.ViewGroup.LayoutParams.MATCH_PARENT; final int margin = lp.topMargin + lp.bottomMargin; int childHeight = child.getMeasuredHeight() + margin; maxHeight = Math.max(maxHeight, childHeight); alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight); allFillParent = allFillParent && lp.height == android.view.ViewGroup.LayoutParams.MATCH_PARENT; if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK; final int index = (gravity >> Gravity.AXIS_Y_SHIFT & ~Gravity.AXIS_SPECIFIED) >> 1; maxAscent[index] = Math.max(maxAscent[index], childBaseline); maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); } } } mTotalLength += getPaddingLeft() + getPaddingRight(); if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) { final int ascent = Math.max(maxAscent[INDEX_FILL], Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); final int descent = Math.max(maxDescent[INDEX_FILL], Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); maxHeight = Math.max(maxHeight, ascent + descent); } } else { alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight); if (useLargestChild && widthMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final View child = getChildAt(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(largestChildWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY)); } } } } if (!allFillParent && heightMode != MeasureSpec.EXACTLY) { maxHeight = alternativeMaxHeight; } maxHeight += getPaddingTop() + getPaddingBottom(); maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); setMeasuredDimension(widthSizeAndState | childState & MEASURED_STATE_MASK, ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); if (matchHeight) { forceUniformHeight(count, widthMeasureSpec); } } public int getBaseline() { final int baselineAlignedChildIndex = getBaselineAlignedChildIndex(); if (baselineAlignedChildIndex < 0) { return super.getBaseline(); } if (getChildCount() <= baselineAlignedChildIndex) { throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "set to an index that is out of bounds."); } final View child = getChildAt(baselineAlignedChildIndex); final int childBaseline = child.getBaseline(); if (childBaseline == -1) { if (baselineAlignedChildIndex == 0) { return -1; } throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "points to a View that doesn't know how to get its baseline."); } int childTop = mBaselineChildTop; if (getOrientation() == VERTICAL) { final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; if (majorGravity != Gravity.TOP) { switch (majorGravity) { case Gravity.BOTTOM: childTop = getBottom() - getTop() - getPaddingBottom() - mTotalLength; break; case Gravity.CENTER_VERTICAL: childTop += ((getBottom() - getTop() - getPaddingTop() - getPaddingBottom()) - mTotalLength) / 2; break; } } } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); return childTop + lp.topMargin + childBaseline; } void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; int maxWidth = 0; int childState = 0; int alternativeMaxWidth = 0; int weightedMaxWidth = 0; boolean allFillParent = true; float totalWeight = 0; final int count = getChildCount(); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchWidth = false; final int baselineChildIndex = getBaselineAlignedChildIndex(); final boolean useLargestChild = isMeasureWithLargestChildEnabled(); int largestChildHeight = Integer.MIN_VALUE; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == View.GONE) { continue; } if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerHeight; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); } else { int oldHeight = Integer.MIN_VALUE; if (lp.height == 0 && lp.weight > 0) { oldHeight = 0; lp.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT; } 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; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } if (baselineChildIndex >= 0 && baselineChildIndex == i + 1) { mBaselineChildTop = mTotalLength; } 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 == android.view.ViewGroup.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); if (VERSION.SDK_INT >= 11) { childState |= child.getMeasuredState(); } allFillParent = allFillParent && lp.width == android.view.ViewGroup.LayoutParams.MATCH_PARENT; if (lp.weight > 0) { weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } } if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child == null || child.getVisibility() == GONE) { continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin); } } mTotalLength += getPaddingTop() + getPaddingBottom(); int heightSize = mTotalLength; heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); int heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; int delta = heightSize - mTotalLength; if (delta != 0 && totalWeight > 0.0f) { final float f = getWeightSum(); float weightSum = f > 0.0f ? f : totalWeight; mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { int share = (int) (childExtra * delta / weightSum); weightSum -= childExtra; delta -= share; final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width); if (lp.height != 0 || heightMode != MeasureSpec.EXACTLY) { int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } else { child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY)); } if (VERSION.SDK_INT >= 11) { 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 == android.view.ViewGroup.LayoutParams.MATCH_PARENT; alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); allFillParent = allFillParent && lp.width == android.view.ViewGroup.LayoutParams.MATCH_PARENT; final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); } mTotalLength += getPaddingTop() + getPaddingBottom(); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth); if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final View child = getChildAt(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 += getPaddingLeft() + getPaddingRight(); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); if (matchWidth) { forceUniformWidth(count, heightMeasureSpec); } } @Override protected void onDraw(Canvas canvas) { if (mDivider == null) { return; } if (getOrientation() == VERTICAL) { drawDividersVertical(canvas); } else { drawDividersHorizontal(canvas); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (getOrientation() == VERTICAL) { layoutVertical(); } else { layoutHorizontal(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (getOrientation() == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); } public int getGravity() { return mGravity; } @Override public void setGravity(int gravity) { super.setGravity(mGravity = gravity); } @Override public void setHorizontalGravity(int horizontalGravity) { final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { mGravity = mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK | gravity; requestLayout(); } } @Override public void setVerticalGravity(int verticalGravity) { final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { mGravity = mGravity & ~Gravity.VERTICAL_GRAVITY_MASK | gravity; requestLayout(); } } @Override public boolean shouldDelayChildPressedState() { return false; } }