From 40e1df34a9549a25020a325d0abc4d43c5a6b6e2 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Tue, 25 Aug 2015 16:50:27 -0400 Subject: [PATCH] Correctly distribute weight to pre-measured zero-dimension views Previously, the measurements obtained from LinearLayout when using EXACTLY and AT_MOST modes would be different even if the resulting height was the same. This was the result of non-EXACTLY measurement accounting for the size of zero-dimension views and then redistrib- uting space as though they had initally been set as AT_MOST, whereas EXACTLY would always redistribute space as though they had initially been set as zero-dimension. Makes perfect sense, right? This CL updates non-EXACTLY measurement to always calculate the minimum dimensions requested by zero-dimension views, but then to redistribute the space according to weight. Bug: 22810327 Change-Id: Iba8a3d26f2724e5fa9a30457119b4564eb285edb --- core/java/android/widget/LinearLayout.java | 220 ++++++++++----------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index b5e08ca5caa0d..ad939be13a44f 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -98,6 +99,13 @@ public class LinearLayout extends ViewGroup { */ public static final int SHOW_DIVIDER_END = 4; + /** + * Compatibility check. Old versions of the platform would give different + * results from measurement passes using EXACTLY and non-EXACTLY modes, + * even when the resulting size was the same. + */ + private final boolean mAllowInconsistentMeasurement; + /** * Whether the children of this layout are baseline aligned. Only applicable * if {@link #mOrientation} is horizontal. @@ -231,6 +239,9 @@ public class LinearLayout extends ViewGroup { mShowDividers = a.getInt(R.styleable.LinearLayout_showDividers, SHOW_DIVIDER_NONE); mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayout_dividerPadding, 0); + final int version = context.getApplicationInfo().targetSdkVersion; + mAllowInconsistentMeasurement = version <= Build.VERSION_CODES.M; + a.recycle(); } @@ -699,6 +710,7 @@ public class LinearLayout extends ViewGroup { final boolean useLargestChild = mUseLargestChild; int largestChildHeight = Integer.MIN_VALUE; + int consumedExcessSpace = 0; // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { @@ -718,26 +730,25 @@ public class LinearLayout extends ViewGroup { mTotalLength += mDividerHeight; } - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); + final LayoutParams lp = (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 boolean useExcessSpace = lp.height == 0 && lp.weight > 0; + 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. final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } 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; + 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. lp.height = LayoutParams.WRAP_CONTENT; } @@ -745,15 +756,19 @@ public class LinearLayout extends ViewGroup { // 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 usedHeight = totalWeight == 0 ? mTotalLength : 0; + 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. + lp.height = 0; + consumedExcessSpace += childHeight; + } + final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); @@ -857,52 +872,42 @@ public class LinearLayout extends ViewGroup { // 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. - int delta = heightSize - mTotalLength; + final int delta = heightSize - mTotalLength + + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace); if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { - float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; + final 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) { + if (child == null || child.getVisibility() == View.GONE) { continue; } - - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); - - float childExtra = lp.weight; + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final 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)); + final int share = (int) (childExtra * delta / weightSum); + final int childHeight; + 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 { - // child was skipped in the loop above. - // Measure for this first time here - child.measure(childWidthMeasureSpec, - MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, - MeasureSpec.EXACTLY)); + // 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)); @@ -1043,6 +1048,7 @@ public class LinearLayout extends ViewGroup { final boolean isExactly = widthMode == MeasureSpec.EXACTLY; int largestChildWidth = Integer.MIN_VALUE; + int usedExcessSpace = 0; // See how wide everyone is. Also remember max height. for (int i = 0; i < count; ++i) { @@ -1062,15 +1068,15 @@ public class LinearLayout extends ViewGroup { mTotalLength += mDividerWidth; } - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) - child.getLayoutParams(); + final LayoutParams lp = (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. + + final boolean useExcessSpace = lp.width == 0 && lp.weight > 0; + if (widthMode == 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. if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { @@ -1094,14 +1100,12 @@ public class LinearLayout extends ViewGroup { skippedMeasure = true; } } 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; + if (useExcessSpace) { + // The widthMode 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 width. We'll restore the original width of 0 + // after measurement. lp.width = LayoutParams.WRAP_CONTENT; } @@ -1109,22 +1113,26 @@ public class LinearLayout extends ViewGroup { // 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, + final int usedWidth = totalWeight == 0 ? mTotalLength : 0; + measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth, heightMeasureSpec, 0); - if (oldWidth != Integer.MIN_VALUE) { - lp.width = oldWidth; + final int childWidth = child.getMeasuredWidth(); + if (useExcessSpace) { + // Restore the original width and record how much space + // we've allocated to excess-only children so that we can + // match the behavior of EXACTLY measurement. + lp.width = 0; + usedExcessSpace += childWidth; } - final int childWidth = child.getMeasuredWidth(); if (isExactly) { - mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + - getNextLocationOffset(child); + 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)); + mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + + lp.rightMargin + getNextLocationOffset(child)); } if (useLargestChild) { @@ -1242,9 +1250,10 @@ public class LinearLayout extends ViewGroup { // 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. - int delta = widthSize - mTotalLength; + final int delta = widthSize - mTotalLength + + (mAllowInconsistentMeasurement ? 0 : usedExcessSpace); if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { - float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; + final 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; @@ -1254,45 +1263,32 @@ public class LinearLayout extends ViewGroup { 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; + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final 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 share = (int) (childExtra * delta / weightSum); + final int childWidth; + if (lp.width == 0 && (!mAllowInconsistentMeasurement + || widthMode == MeasureSpec.EXACTLY)) { + // This child needs to be laid out from scratch using + // only its share of excess space. + childWidth = share; + } else { + // This child had some intrinsic width to which we + // need to add its share of excess space. + childWidth = child.getMeasuredWidth() + share; + } - final int childHeightMeasureSpec = getChildMeasureSpec( - heightMeasureSpec, + final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.max(0, childWidth), MeasureSpec.EXACTLY); + 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); - } + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // Child may now not fit in horizontal dimension. childState = combineMeasuredStates(childState,