/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import com.android.internal.R; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.RemoteViews.RemoteView; /** * A Layout that arranges its children in a single column or a single row. The direction of * the row can be set by calling {@link #setOrientation(int) setOrientation()}. * You can also specify gravity, which specifies the alignment of all the child elements by * calling {@link #setGravity(int) setGravity()} or specify that specific children * grow to fill up any remaining space in the layout by setting the weight member of * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}. * The default orientation is horizontal. * *
See the Linear Layout * tutorial.
* ** Also see {@link LinearLayout.LayoutParams android.widget.LinearLayout.LayoutParams} * for layout attributes
*/ @RemoteView public class LinearLayout extends ViewGroup { public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; /** * Whether the children of this layout are baseline aligned. Only applicable * if {@link #mOrientation} is horizontal. */ @ViewDebug.ExportedProperty(category = "layout") private boolean mBaselineAligned = true; /** * If this layout is part of another layout that is baseline aligned, * use the child at this index as the baseline. * * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned * with whether the children of this layout are baseline aligned. */ @ViewDebug.ExportedProperty(category = "layout") private int mBaselineAlignedChildIndex = -1; /** * The additional offset to the child's baseline. * We'll calculate the baseline of this layout as we measure vertically; for * horizontal linear layouts, the offset of 0 is appropriate. */ @ViewDebug.ExportedProperty(category = "measurement") private int mBaselineChildTop = 0; @ViewDebug.ExportedProperty(category = "measurement") private int mOrientation; @ViewDebug.ExportedProperty(category = "measurement", mapping = { @ViewDebug.IntToString(from = -1, to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"), @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") }) private int mGravity = Gravity.LEFT | Gravity.TOP; @ViewDebug.ExportedProperty(category = "measurement") private int mTotalLength; @ViewDebug.ExportedProperty(category = "layout") private float mWeightSum; @ViewDebug.ExportedProperty(category = "layout") private boolean mUseLargestChild; private int[] mMaxAscent; private int[] mMaxDescent; private static final int VERTICAL_GRAVITY_COUNT = 4; private static final int INDEX_CENTER_VERTICAL = 0; private static final int INDEX_TOP = 1; private static final int INDEX_BOTTOM = 2; private static final int INDEX_FILL = 3; public LinearLayout(Context context) { super(context); } public LinearLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout); int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1); if (index >= 0) { setOrientation(index); } index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1); if (index >= 0) { setGravity(index); } boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true); if (!baselineAligned) { setBaselineAligned(baselineAligned); } mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0f); mBaselineAlignedChildIndex = a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1); // TODO: Better name, add Java APIs, make it public mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_useLargestChild, false); a.recycle(); } /** *Indicates whether widgets contained within this layout are aligned * on their baseline or not.
* * @return true when widgets are baseline-aligned, false otherwise */ public boolean isBaselineAligned() { return mBaselineAligned; } /** *Defines whether widgets contained in this layout are * baseline-aligned or not.
* * @param baselineAligned true to align widgets on their baseline, * false otherwise * * @attr ref android.R.styleable#LinearLayout_baselineAligned */ @android.view.RemotableViewMethod public void setBaselineAligned(boolean baselineAligned) { mBaselineAligned = baselineAligned; } @Override public int getBaseline() { if (mBaselineAlignedChildIndex < 0) { return super.getBaseline(); } if (getChildCount() <= mBaselineAlignedChildIndex) { throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "set to an index that is out of bounds."); } final View child = getChildAt(mBaselineAlignedChildIndex); final int childBaseline = child.getBaseline(); if (childBaseline == -1) { if (mBaselineAlignedChildIndex == 0) { // this is just the default case, safe to return -1 return -1; } // the user picked an index that points to something that doesn't // know how to calculate its baseline. throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "points to a View that doesn't know how to get its baseline."); } // TODO: This should try to take into account the virtual offsets // (See getNextLocationOffset and getLocationOffset) // We should add to childTop: // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex]) // and also add: // getLocationOffset(child) int childTop = mBaselineChildTop; if (mOrientation == VERTICAL) { final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; if (majorGravity != Gravity.TOP) { switch (majorGravity) { case Gravity.BOTTOM: childTop = mBottom - mTop - mPaddingBottom - mTotalLength; break; case Gravity.CENTER_VERTICAL: childTop += ((mBottom - mTop - mPaddingTop - mPaddingBottom) - mTotalLength) / 2; break; } } } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); return childTop + lp.topMargin + childBaseline; } /** * @return The index of the child that will be used if this layout is * part of a larger layout that is baseline aligned, or -1 if none has * been set. */ public int getBaselineAlignedChildIndex() { return mBaselineAlignedChildIndex; } /** * @param i The index of the child that will be used if this layout is * part of a larger layout that is baseline aligned. * * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex */ @android.view.RemotableViewMethod public void setBaselineAlignedChildIndex(int i) { if ((i < 0) || (i >= getChildCount())) { throw new IllegalArgumentException("base aligned child index out " + "of range (0, " + getChildCount() + ")"); } mBaselineAlignedChildIndex = i; } /** *Returns the view at the specified index. This method can be overriden * to take into account virtual children. Refer to * {@link android.widget.TableLayout} and {@link android.widget.TableRow} * for an example.
* * @param index the child's index * @return the child at the specified index */ View getVirtualChildAt(int index) { return getChildAt(index); } /** *Returns the virtual number of children. This number might be different * than the actual number of children if the layout can hold virtual * children. Refer to * {@link android.widget.TableLayout} and {@link android.widget.TableRow} * for an example.
* * @return the virtual number of children */ int getVirtualChildCount() { return getChildCount(); } /** * Returns the desired weights sum. * * @return A number greater than 0.0f if the weight sum is defined, or * a number lower than or equals to 0.0f if not weight sum is * to be used. */ public float getWeightSum() { return mWeightSum; } /** * Defines the desired weights sum. If unspecified the weights sum is computed * at layout time by adding the layout_weight of each child. * * This can be used for instance to give a single child 50% of the total * available space by giving it a layout_weight of 0.5 and setting the * weightSum to 1.0. * * @param weightSum a number greater than 0.0f, or a number lower than or equals * to 0.0f if the weight sum should be computed from the children's * layout_weight */ @android.view.RemotableViewMethod public void setWeightSum(float weightSum) { mWeightSum = Math.max(0.0f, weightSum); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } /** * Measures the children when the orientation of this LinearLayout is set * to {@link #VERTICAL}. * * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. * @param heightMeasureSpec Vertical space requirements as imposed by the parent. * * @see #getOrientation() * @see #setOrientation(int) * @see #onMeasure(int, int) */ void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; int maxWidth = 0; int alternativeMaxWidth = 0; int weightedMaxWidth = 0; boolean allFillParent = true; float totalWeight = 0; final int count = getVirtualChildCount(); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchWidth = false; final int baselineChildIndex = mBaselineAlignedChildIndex; final boolean useLargestChild = mUseLargestChild; int largestChildHeight = Integer.MIN_VALUE; // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child == null) { mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == View.GONE) { i += getChildrenSkipCount(child, i); continue; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. 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) { // 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 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). 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 + getNextLocationOffset(child)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } /** * 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. matchWidth = true; matchWidthLocally = true; } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); 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. */ weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } i += getChildrenSkipCount(child, i); } if (useLargestChild && heightMode == MeasureSpec.AT_MOST) { 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()); // Reconcile our calculated size with the heightMeasureSpec heightSize = resolveSize(heightSize, heightMeasureSpec); // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds int delta = heightSize - mTotalLength; if (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) { // 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 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 child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY)); } } 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); } if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; } maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightSize); if (matchWidth) { forceUniformWidth(count, heightMeasureSpec); } } 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.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; } } } } /** * Measures the children when the orientation of this LinearLayout is set * to {@link #HORIZONTAL}. * * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. * @param heightMeasureSpec Vertical space requirements as imposed by the parent. * * @see #getOrientation() * @see #setOrientation(int) * @see #onMeasure(int, int) */ void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; int maxHeight = 0; int alternativeMaxHeight = 0; int weightedMaxHeight = 0; boolean allFillParent = true; float totalWeight = 0; final int count = getVirtualChildCount(); 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 = mBaselineAligned; final boolean useLargestChild = mUseLargestChild; final boolean isExactly = widthMode == MeasureSpec.EXACTLY; int largestChildWidth = Integer.MIN_VALUE; // See how wide everyone is. Also remember max height. 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(); totalWeight += lp.weight; if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); } // Baseline alignment requires to measure widgets to obtain the // baseline offset (in particular for TextViews). The following // defeats the optimization mentioned above. Allow the child to // use as much space as it wants because we can shrink things // later (and re-measure). 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) { // widthMode 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 width of 0 oldWidth = 0; lp.width = 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). 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 + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } if (useLargestChild) { largestChildWidth = Math.max(childWidth, largestChildWidth); } } boolean matchHeightLocally = false; if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) { // The height of the linear layout will scale, and at least one // child said it wanted to match our height. Set a flag indicating that // we need to remeasure at least that view when we know our height. matchHeight = true; matchHeightLocally = true; } final int margin = lp.topMargin + lp.bottomMargin; final int childHeight = child.getMeasuredHeight() + margin; if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { // Translates the child's vertical gravity into an index // in the range 0..VERTICAL_GRAVITY_COUNT 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 == LayoutParams.MATCH_PARENT; if (lp.weight > 0) { /* * Heights of weighted Views are bogus if we end up * remeasuring, so keep them separate. */ weightedMaxHeight = Math.max(weightedMaxHeight, matchHeightLocally ? margin : childHeight); } else { alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight); } i += getChildrenSkipCount(child, i); } // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, // the most common case 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) { 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(); if (isExactly) { mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; int widthSize = mTotalLength; // Check against our minimum width widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); // Reconcile our calculated size with the widthMeasureSpec widthSize = resolveSize(widthSize, widthMeasureSpec); // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds int delta = widthSize - mTotalLength; if (delta != 0 && totalWeight > 0.0f) { float weightSum = mWeightSum > 0.0f ? mWeightSum : 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 = 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 said it could absorb extra space -- give him his share int share = (int) (childExtra * delta / weightSum); weightSum -= childExtra; delta -= share; final int childHeightMeasureSpec = getChildMeasureSpec( heightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin, lp.height); // TODO: Use a field like lp.isMeasured to figure out if this // child has been previously measured if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) { // child was measured once already above ... base new measurement // on stored values int childWidth = child.getMeasuredWidth() + share; if (childWidth < 0) { childWidth = 0; } child.measure( MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), childHeightMeasureSpec); } else { // child was skipped in the loop above. Measure for this first time here child.measure(MeasureSpec.makeMeasureSpec( share > 0 ? share : 0, MeasureSpec.EXACTLY), childHeightMeasureSpec); } } if (isExactly) { mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && lp.height == 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 == LayoutParams.MATCH_PARENT; if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { // Translates the child's vertical gravity into an index in the range 0..2 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); } } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; // TODO: Should we update widthSize with the new total length? // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, // the most common case 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 (!allFillParent && heightMode != MeasureSpec.EXACTLY) { maxHeight = alternativeMaxHeight; } maxHeight += mPaddingTop + mPaddingBottom; // Check against our minimum height maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); setMeasuredDimension(widthSize, resolveSize(maxHeight, heightMeasureSpec)); if (matchHeight) { forceUniformHeight(count, widthMeasureSpec); } } private void forceUniformHeight(int count, int widthMeasureSpec) { // Pretend that the linear layout has an exact size. This is the measured height of // ourselves. The measured height should be the max height of the children, changed // to accomodate the heightMesureSpec from the parent int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child.getVisibility() != GONE) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (lp.height == LayoutParams.MATCH_PARENT) { // Temporarily force children to reuse their old measured width // FIXME: this may not be right for something like wrapping text? int oldWidth = lp.width; lp.width = child.getMeasuredWidth(); // Remeasure with new dimensions measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0); lp.width = oldWidth; } } } } /** *Returns the number of children to skip after measuring/laying out * the specified child.
* * @param child the child after which we want to skip children * @param index the index of the child after which we want to skip children * @return the number of children to skip, 0 by default */ int getChildrenSkipCount(View child, int index) { return 0; } /** *Returns the size (width or height) that should be occupied by a null * child.
* * @param childIndex the index of the null child * @return the width or height of the child depending on the orientation */ int measureNullChild(int childIndex) { return 0; } /** *Measure the child according to the parent's measure specs. This * method should be overriden by subclasses to force the sizing of * children. This method is called by {@link #measureVertical(int, int)} and * {@link #measureHorizontal(int, int)}.
* * @param child the child to measure * @param childIndex the index of the child in this view * @param widthMeasureSpec horizontal space requirements as imposed by the parent * @param totalWidth extra space that has been used up by the parent horizontally * @param heightMeasureSpec vertical space requirements as imposed by the parent * @param totalHeight extra space that has been used up by the parent vertically */ void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); } /** *Return the location offset of the specified child. This can be used * by subclasses to change the location of a given widget.
* * @param child the child for which to obtain the location offset * @return the location offset in pixels */ int getLocationOffset(View child) { return 0; } /** *Return the size offset of the next sibling of the specified child.
* This can be used by subclasses to change the location of the widget
* following child
.