Loading core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java +83 −69 Original line number Diff line number Diff line Loading @@ -53,6 +53,7 @@ import java.util.List; * - LinearLayout doesn't have <code>weightSum</code>. * - Horizontal LinearLayout's width should be measured EXACTLY. * - Horizontal LinearLayout shouldn't need baseLineAlignment. * - Horizontal LinearLayout shouldn't have any child that has negative left or right margin. * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY. * * @hide Loading Loading @@ -88,7 +89,7 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { final View weightedChildView = getSingleWeightedChild(); mShouldUseOptimizedLayout = isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec); && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec); if (mShouldUseOptimizedLayout) { onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec); Loading Loading @@ -118,7 +119,7 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { * @param heightMeasureSpec The height measurement specification. * @return `true` if optimization is possible, `false` otherwise. */ private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) { private boolean isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec) { final boolean hasWeightSum = getWeightSum() > 0.0f; if (hasWeightSum) { logSkipOptimizedOnMeasure("Has weightSum."); Loading @@ -142,9 +143,35 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { logSkipOptimizedOnMeasure("Need to apply baseline."); return false; } if (requiresNegativeMarginHandlingForHorizontalLinearLayout()) { logSkipOptimizedOnMeasure("Need to handle negative margins."); return false; } return true; } /** * @return if the horizontal linearlayout requires to handle negative margins in its children. * In that case, we can't use excessSpace because LinearLayout negative margin handling for * excess space and WRAP_CONTENT is different. */ private boolean requiresNegativeMarginHandlingForHorizontalLinearLayout() { if (getOrientation() == VERTICAL) { return false; } final List<View> activeChildren = getActiveChildren(); for (int i = 0; i < activeChildren.size(); i++) { final View child = activeChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); if (lp.leftMargin < 0 || lp.rightMargin < 0) { return true; } } return false; } /** * @return if the vertical linearlayout requires match_parent children remeasure */ Loading Loading @@ -337,94 +364,81 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { */ private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); int totalLength = 0; int maxWidth = 0; int usedHeight = 0; final List<View> activeChildren = getActiveChildren(); final int activeChildCount = activeChildren.size(); final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0) == weightedChildView; final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get( activeChildCount - 1) == weightedChildView; final int horizontalPaddings = getPaddingLeft() + getPaddingRight(); final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 1. Measure other child views. for (int i = 0; i < activeChildCount; i++) { final View child = activeChildren.get(i); if (child == weightedChildView) { // 1. Measure all unweighted children for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child == null || child.getVisibility() == GONE) { continue; } final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int requiredVerticalPadding = lp.topMargin + lp.bottomMargin; if (!isContentFirstItem && i == 0) { requiredVerticalPadding += getPaddingTop(); if (child == weightedChildView) { // In excessMode, LinearLayout add weighted child top and bottom margins to // totalLength when their sum is positive. if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) { totalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); } if (!isContentLastItem && i == activeChildCount - 1) { requiredVerticalPadding += getPaddingBottom(); continue; } child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec, horizontalPaddings + lp.leftMargin + lp.rightMargin, child.getLayoutParams().width), ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding, lp.height)); measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // LinearLayout only adds measured children heights and its top and bottom margins // to totalLength when their sum is positive. totalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); usedHeight += child.getMeasuredHeight() + requiredVerticalPadding; } // measure content final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams(); // Add padding to totalLength that we are going to use for remaining space. totalLength += mPaddingTop + mPaddingBottom; int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin; if (isContentFirstItem) { usedSpace += getPaddingTop(); } if (isContentLastItem) { usedSpace += getPaddingBottom(); // 2. generate measure spec for weightedChildView. final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams(); // height should be AT_MOST for non EXACT cases. final int childHeightMeasureMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; final int childHeightMeasureSpec; // In excess mode, LinearLayout measures weighted children with remaining space. Otherwise, // it is measured with remaining space just like other children. if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - totalLength), childHeightMeasureMode); } else { final int usedHeight = lp.topMargin + lp.bottomMargin + totalLength; childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - usedHeight), childHeightMeasureMode); } final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width); final int availableWidth = MeasureSpec.getSize(widthMeasureSpec); final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width); // 3. Measure weightedChildView with the remaining space. weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 2. Calculate remaining height for weightedChildView. final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST); totalLength = Math.max(totalLength, totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); // 3. Measure weightedChildView with the remaining remaining space. weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); maxWidth = Math.max(maxWidth, weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight(); final int measuredWidth; if (widthMode == MeasureSpec.EXACTLY) { measuredWidth = availableWidth; } else { measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd(); } final int measuredHeight; if (heightMode == MeasureSpec.EXACTLY) { measuredHeight = availableHeight; } else { measuredHeight = totalUsedHeight; } // Add padding to width maxWidth += getPaddingLeft() + getPaddingRight(); // 4. Set the container size setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), Math.max(getSuggestedMinimumHeight(), measuredHeight)); // Resolve final dimensions final int finalWidth = resolveSizeAndState(Math.max(maxWidth, getSuggestedMinimumWidth()), widthMeasureSpec, 0); final int finalHeight = resolveSizeAndState( Math.max(totalLength, getSuggestedMinimumHeight()), heightMeasureSpec, 0); setMeasuredDimension(finalWidth, finalHeight); } @NonNull Loading core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java +107 −87 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.flags.Flags; import androidx.test.InstrumentationRegistry; Loading Loading @@ -73,7 +74,7 @@ public class NotificationOptimizedLinearLayoutComparisonTest { private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50}; private static final int[] CHILD_WEIGHTS = {0, 1}; private static final int[] CHILD_MARGINS = {0, 10, -10}; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); Loading @@ -84,20 +85,75 @@ public class NotificationOptimizedLinearLayoutComparisonTest { mContext = InstrumentationRegistry.getTargetContext(); } @Test public void test() throws Throwable { final List<View> controlChildren = new ArrayList<>(); final List<View> testChildren = new ArrayList<>(); final View controlChild1 = buildChildView(); final View controlChild2 = buildChildView(); controlChildren.add(controlChild1); controlChildren.add(controlChild2); final View testChild1 = buildChildView(); final View testChild2 = buildChildView(); testChildren.add(testChild1); testChildren.add(testChild2); final LinearLayout controlContainer = buildLayout(false, controlChildren); final LinearLayout testContainer = buildLayout(true, testChildren); final LinearLayout.LayoutParams firstChildLayoutParams = new LinearLayout.LayoutParams(0, 0); final LinearLayout.LayoutParams secondChildLayoutParams = new LinearLayout.LayoutParams(0, 0); controlChild1.setLayoutParams(firstChildLayoutParams); controlChild2.setLayoutParams(secondChildLayoutParams); testChild1.setLayoutParams(firstChildLayoutParams); testChild2.setLayoutParams(secondChildLayoutParams); for (int orientation : ORIENTATIONS) { for (int widthSpec : MEASURE_SPECS) { for (int heightSpec : MEASURE_SPECS) { for (int firstChildGravity : GRAVITIES) { for (int secondChildGravity : GRAVITIES) { controlContainer.setOrientation(orientation); testContainer.setOrientation(orientation); for (int firstChildLayoutWidth : LAYOUT_PARAMS) { firstChildLayoutParams.width = firstChildLayoutWidth; for (int firstChildLayoutHeight : LAYOUT_PARAMS) { firstChildLayoutParams.height = firstChildLayoutHeight; for (int secondChildLayoutWidth : LAYOUT_PARAMS) { secondChildLayoutParams.width = secondChildLayoutWidth; for (int secondChildLayoutHeight : LAYOUT_PARAMS) { secondChildLayoutParams.height = secondChildLayoutHeight; for (int firstChildMargin : CHILD_MARGINS) { firstChildLayoutParams.setMargins(firstChildMargin, firstChildMargin, firstChildMargin, firstChildMargin); for (int secondChildMargin : CHILD_MARGINS) { secondChildLayoutParams.setMargins(secondChildMargin, secondChildMargin, secondChildMargin, secondChildMargin); for (int firstChildGravity : GRAVITIES) { firstChildLayoutParams.gravity = firstChildGravity; for (int secondChildGravity : GRAVITIES) { secondChildLayoutParams.gravity = secondChildGravity; for (int firstChildWeight : CHILD_WEIGHTS) { firstChildLayoutParams.weight = firstChildWeight; for (int secondChildWeight : CHILD_WEIGHTS) { executeTest(/*testSpec =*/createTestSpec( secondChildLayoutParams.weight = secondChildWeight; for (int widthSpec : MEASURE_SPECS) { for (int heightSpec : MEASURE_SPECS) { executeTest(controlContainer, testContainer, createTestSpec( orientation, widthSpec, heightSpec, firstChildLayoutWidth, Loading @@ -107,7 +163,10 @@ public class NotificationOptimizedLinearLayoutComparisonTest { firstChildGravity, secondChildGravity, firstChildWeight, secondChildWeight)); secondChildWeight, firstChildMargin, secondChildMargin) ); } } } Loading @@ -115,53 +174,17 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } } } } } } } } } private void executeTest(TestSpec testSpec) { // GIVEN final List<View> controlChildren = new ArrayList<>(); final List<View> testChildren = new ArrayList<>(); controlChildren.add( buildChildView( testSpec.mFirstChildLayoutWidth, testSpec.mFirstChildLayoutHeight, testSpec.mFirstChildGravity, testSpec.mFirstChildWeight)); controlChildren.add( buildChildView( testSpec.mSecondChildLayoutWidth, testSpec.mSecondChildLayoutHeight, testSpec.mSecondChildGravity, testSpec.mSecondChildWeight)); testChildren.add( buildChildView( testSpec.mFirstChildLayoutWidth, testSpec.mFirstChildLayoutHeight, testSpec.mFirstChildGravity, testSpec.mFirstChildWeight)); testChildren.add( buildChildView( testSpec.mSecondChildLayoutWidth, testSpec.mSecondChildLayoutHeight, testSpec.mSecondChildGravity, testSpec.mSecondChildWeight)); final LinearLayout controlContainer = buildLayout(false, testSpec.mOrientation, controlChildren); final LinearLayout testContainer = buildLayout(true, testSpec.mOrientation, testChildren); private void executeTest(LinearLayout controlContainer, LinearLayout testContainer, TestSpec testSpec) { // WHEN controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec); testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec); Loading @@ -171,6 +194,7 @@ public class NotificationOptimizedLinearLayoutComparisonTest { assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer); } private static class TestSpec { private final int mOrientation; private final int mWidthSpec; Loading @@ -183,6 +207,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { private final int mSecondChildGravity; private final int mFirstChildWeight; private final int mSecondChildWeight; private final int mFirstChildMargin; private final int mSecondChildMargin; TestSpec( int orientation, Loading @@ -195,7 +221,9 @@ public class NotificationOptimizedLinearLayoutComparisonTest { int firstChildGravity, int secondChildGravity, int firstChildWeight, int secondChildWeight) { int secondChildWeight, int firstChildMargin, int secondChildMargin) { mOrientation = orientation; mWidthSpec = widthSpec; mHeightSpec = heightSpec; Loading @@ -207,6 +235,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { mSecondChildGravity = secondChildGravity; mFirstChildWeight = firstChildWeight; mSecondChildWeight = secondChildWeight; mFirstChildMargin = firstChildMargin; mSecondChildMargin = secondChildMargin; } @Override Loading @@ -223,6 +253,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { + ", mSecondChildGravity=" + mSecondChildGravity + ", mFirstChildWeight=" + mFirstChildWeight + ", mSecondChildWeight=" + mSecondChildWeight + ", mFirstChildMargin=" + mFirstChildMargin + ", mSecondChildMargin=" + mSecondChildMargin + '}'; } Loading @@ -246,15 +278,13 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } private LinearLayout buildLayout(boolean isNotificationOptimized, @LinearLayout.OrientationMode int orientation, List<View> children) { private LinearLayout buildLayout(boolean isNotificationOptimized, List<View> children) { final LinearLayout linearLayout; if (isNotificationOptimized) { linearLayout = new NotificationOptimizedLinearLayout(mContext); } else { linearLayout = new LinearLayout(mContext); } linearLayout.setOrientation(orientation); for (int i = 0; i < children.size(); i++) { linearLayout.addView(children.get(i)); } Loading @@ -262,7 +292,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } private void assertLayoutsEqual(String testCase, View controlView, View testView) { mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase) mExpect.withMessage( "MeasuredWidths are not equal. Test Case:" + testCase) .that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth()); mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase) .that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight()); Loading @@ -286,23 +317,12 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } private static class TestView extends View { TestView(Context context) { super(context); } @Override public int getBaseline() { return 5; } } private TestSpec createTestSpec(int orientation, int widthSpec, int heightSpec, int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth, int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity, int firstChildWeight, int secondChildWeight) { int firstChildWeight, int secondChildWeight, int firstChildMargin, int secondChildMargin) { return new TestSpec( orientation, Loading @@ -314,16 +334,16 @@ public class NotificationOptimizedLinearLayoutComparisonTest { firstChildGravity, secondChildGravity, firstChildWeight, secondChildWeight); secondChildWeight, firstChildMargin, secondChildMargin); } private View buildChildView(int childLayoutWidth, int childLayoutHeight, int childGravity, int childWeight) { final View childView = new TestView(mContext); // Set desired size using LayoutParams LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth, childLayoutHeight, childWeight); params.gravity = childGravity; private View buildChildView() { final View childView = new TextView(mContext); // this is initial value. We are going to mutate this layout params during the test. LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); childView.setLayoutParams(params); return childView; } Loading Loading
core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java +83 −69 Original line number Diff line number Diff line Loading @@ -53,6 +53,7 @@ import java.util.List; * - LinearLayout doesn't have <code>weightSum</code>. * - Horizontal LinearLayout's width should be measured EXACTLY. * - Horizontal LinearLayout shouldn't need baseLineAlignment. * - Horizontal LinearLayout shouldn't have any child that has negative left or right margin. * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY. * * @hide Loading Loading @@ -88,7 +89,7 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { final View weightedChildView = getSingleWeightedChild(); mShouldUseOptimizedLayout = isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec); && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec); if (mShouldUseOptimizedLayout) { onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec); Loading Loading @@ -118,7 +119,7 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { * @param heightMeasureSpec The height measurement specification. * @return `true` if optimization is possible, `false` otherwise. */ private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) { private boolean isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec) { final boolean hasWeightSum = getWeightSum() > 0.0f; if (hasWeightSum) { logSkipOptimizedOnMeasure("Has weightSum."); Loading @@ -142,9 +143,35 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { logSkipOptimizedOnMeasure("Need to apply baseline."); return false; } if (requiresNegativeMarginHandlingForHorizontalLinearLayout()) { logSkipOptimizedOnMeasure("Need to handle negative margins."); return false; } return true; } /** * @return if the horizontal linearlayout requires to handle negative margins in its children. * In that case, we can't use excessSpace because LinearLayout negative margin handling for * excess space and WRAP_CONTENT is different. */ private boolean requiresNegativeMarginHandlingForHorizontalLinearLayout() { if (getOrientation() == VERTICAL) { return false; } final List<View> activeChildren = getActiveChildren(); for (int i = 0; i < activeChildren.size(); i++) { final View child = activeChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); if (lp.leftMargin < 0 || lp.rightMargin < 0) { return true; } } return false; } /** * @return if the vertical linearlayout requires match_parent children remeasure */ Loading Loading @@ -337,94 +364,81 @@ public class NotificationOptimizedLinearLayout extends LinearLayout { */ private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); int totalLength = 0; int maxWidth = 0; int usedHeight = 0; final List<View> activeChildren = getActiveChildren(); final int activeChildCount = activeChildren.size(); final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0) == weightedChildView; final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get( activeChildCount - 1) == weightedChildView; final int horizontalPaddings = getPaddingLeft() + getPaddingRight(); final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 1. Measure other child views. for (int i = 0; i < activeChildCount; i++) { final View child = activeChildren.get(i); if (child == weightedChildView) { // 1. Measure all unweighted children for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child == null || child.getVisibility() == GONE) { continue; } final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int requiredVerticalPadding = lp.topMargin + lp.bottomMargin; if (!isContentFirstItem && i == 0) { requiredVerticalPadding += getPaddingTop(); if (child == weightedChildView) { // In excessMode, LinearLayout add weighted child top and bottom margins to // totalLength when their sum is positive. if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) { totalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); } if (!isContentLastItem && i == activeChildCount - 1) { requiredVerticalPadding += getPaddingBottom(); continue; } child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec, horizontalPaddings + lp.leftMargin + lp.rightMargin, child.getLayoutParams().width), ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding, lp.height)); measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // LinearLayout only adds measured children heights and its top and bottom margins // to totalLength when their sum is positive. totalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); usedHeight += child.getMeasuredHeight() + requiredVerticalPadding; } // measure content final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams(); // Add padding to totalLength that we are going to use for remaining space. totalLength += mPaddingTop + mPaddingBottom; int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin; if (isContentFirstItem) { usedSpace += getPaddingTop(); } if (isContentLastItem) { usedSpace += getPaddingBottom(); // 2. generate measure spec for weightedChildView. final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams(); // height should be AT_MOST for non EXACT cases. final int childHeightMeasureMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; final int childHeightMeasureSpec; // In excess mode, LinearLayout measures weighted children with remaining space. Otherwise, // it is measured with remaining space just like other children. if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - totalLength), childHeightMeasureMode); } else { final int usedHeight = lp.topMargin + lp.bottomMargin + totalLength; childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - usedHeight), childHeightMeasureMode); } final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width); final int availableWidth = MeasureSpec.getSize(widthMeasureSpec); final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width); // 3. Measure weightedChildView with the remaining space. weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 2. Calculate remaining height for weightedChildView. final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST); totalLength = Math.max(totalLength, totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); // 3. Measure weightedChildView with the remaining remaining space. weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec); maxWidth = Math.max(maxWidth, weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight(); final int measuredWidth; if (widthMode == MeasureSpec.EXACTLY) { measuredWidth = availableWidth; } else { measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd(); } final int measuredHeight; if (heightMode == MeasureSpec.EXACTLY) { measuredHeight = availableHeight; } else { measuredHeight = totalUsedHeight; } // Add padding to width maxWidth += getPaddingLeft() + getPaddingRight(); // 4. Set the container size setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), Math.max(getSuggestedMinimumHeight(), measuredHeight)); // Resolve final dimensions final int finalWidth = resolveSizeAndState(Math.max(maxWidth, getSuggestedMinimumWidth()), widthMeasureSpec, 0); final int finalHeight = resolveSizeAndState( Math.max(totalLength, getSuggestedMinimumHeight()), heightMeasureSpec, 0); setMeasuredDimension(finalWidth, finalHeight); } @NonNull Loading
core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java +107 −87 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.flags.Flags; import androidx.test.InstrumentationRegistry; Loading Loading @@ -73,7 +74,7 @@ public class NotificationOptimizedLinearLayoutComparisonTest { private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50}; private static final int[] CHILD_WEIGHTS = {0, 1}; private static final int[] CHILD_MARGINS = {0, 10, -10}; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); Loading @@ -84,20 +85,75 @@ public class NotificationOptimizedLinearLayoutComparisonTest { mContext = InstrumentationRegistry.getTargetContext(); } @Test public void test() throws Throwable { final List<View> controlChildren = new ArrayList<>(); final List<View> testChildren = new ArrayList<>(); final View controlChild1 = buildChildView(); final View controlChild2 = buildChildView(); controlChildren.add(controlChild1); controlChildren.add(controlChild2); final View testChild1 = buildChildView(); final View testChild2 = buildChildView(); testChildren.add(testChild1); testChildren.add(testChild2); final LinearLayout controlContainer = buildLayout(false, controlChildren); final LinearLayout testContainer = buildLayout(true, testChildren); final LinearLayout.LayoutParams firstChildLayoutParams = new LinearLayout.LayoutParams(0, 0); final LinearLayout.LayoutParams secondChildLayoutParams = new LinearLayout.LayoutParams(0, 0); controlChild1.setLayoutParams(firstChildLayoutParams); controlChild2.setLayoutParams(secondChildLayoutParams); testChild1.setLayoutParams(firstChildLayoutParams); testChild2.setLayoutParams(secondChildLayoutParams); for (int orientation : ORIENTATIONS) { for (int widthSpec : MEASURE_SPECS) { for (int heightSpec : MEASURE_SPECS) { for (int firstChildGravity : GRAVITIES) { for (int secondChildGravity : GRAVITIES) { controlContainer.setOrientation(orientation); testContainer.setOrientation(orientation); for (int firstChildLayoutWidth : LAYOUT_PARAMS) { firstChildLayoutParams.width = firstChildLayoutWidth; for (int firstChildLayoutHeight : LAYOUT_PARAMS) { firstChildLayoutParams.height = firstChildLayoutHeight; for (int secondChildLayoutWidth : LAYOUT_PARAMS) { secondChildLayoutParams.width = secondChildLayoutWidth; for (int secondChildLayoutHeight : LAYOUT_PARAMS) { secondChildLayoutParams.height = secondChildLayoutHeight; for (int firstChildMargin : CHILD_MARGINS) { firstChildLayoutParams.setMargins(firstChildMargin, firstChildMargin, firstChildMargin, firstChildMargin); for (int secondChildMargin : CHILD_MARGINS) { secondChildLayoutParams.setMargins(secondChildMargin, secondChildMargin, secondChildMargin, secondChildMargin); for (int firstChildGravity : GRAVITIES) { firstChildLayoutParams.gravity = firstChildGravity; for (int secondChildGravity : GRAVITIES) { secondChildLayoutParams.gravity = secondChildGravity; for (int firstChildWeight : CHILD_WEIGHTS) { firstChildLayoutParams.weight = firstChildWeight; for (int secondChildWeight : CHILD_WEIGHTS) { executeTest(/*testSpec =*/createTestSpec( secondChildLayoutParams.weight = secondChildWeight; for (int widthSpec : MEASURE_SPECS) { for (int heightSpec : MEASURE_SPECS) { executeTest(controlContainer, testContainer, createTestSpec( orientation, widthSpec, heightSpec, firstChildLayoutWidth, Loading @@ -107,7 +163,10 @@ public class NotificationOptimizedLinearLayoutComparisonTest { firstChildGravity, secondChildGravity, firstChildWeight, secondChildWeight)); secondChildWeight, firstChildMargin, secondChildMargin) ); } } } Loading @@ -115,53 +174,17 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } } } } } } } } } private void executeTest(TestSpec testSpec) { // GIVEN final List<View> controlChildren = new ArrayList<>(); final List<View> testChildren = new ArrayList<>(); controlChildren.add( buildChildView( testSpec.mFirstChildLayoutWidth, testSpec.mFirstChildLayoutHeight, testSpec.mFirstChildGravity, testSpec.mFirstChildWeight)); controlChildren.add( buildChildView( testSpec.mSecondChildLayoutWidth, testSpec.mSecondChildLayoutHeight, testSpec.mSecondChildGravity, testSpec.mSecondChildWeight)); testChildren.add( buildChildView( testSpec.mFirstChildLayoutWidth, testSpec.mFirstChildLayoutHeight, testSpec.mFirstChildGravity, testSpec.mFirstChildWeight)); testChildren.add( buildChildView( testSpec.mSecondChildLayoutWidth, testSpec.mSecondChildLayoutHeight, testSpec.mSecondChildGravity, testSpec.mSecondChildWeight)); final LinearLayout controlContainer = buildLayout(false, testSpec.mOrientation, controlChildren); final LinearLayout testContainer = buildLayout(true, testSpec.mOrientation, testChildren); private void executeTest(LinearLayout controlContainer, LinearLayout testContainer, TestSpec testSpec) { // WHEN controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec); testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec); Loading @@ -171,6 +194,7 @@ public class NotificationOptimizedLinearLayoutComparisonTest { assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer); } private static class TestSpec { private final int mOrientation; private final int mWidthSpec; Loading @@ -183,6 +207,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { private final int mSecondChildGravity; private final int mFirstChildWeight; private final int mSecondChildWeight; private final int mFirstChildMargin; private final int mSecondChildMargin; TestSpec( int orientation, Loading @@ -195,7 +221,9 @@ public class NotificationOptimizedLinearLayoutComparisonTest { int firstChildGravity, int secondChildGravity, int firstChildWeight, int secondChildWeight) { int secondChildWeight, int firstChildMargin, int secondChildMargin) { mOrientation = orientation; mWidthSpec = widthSpec; mHeightSpec = heightSpec; Loading @@ -207,6 +235,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { mSecondChildGravity = secondChildGravity; mFirstChildWeight = firstChildWeight; mSecondChildWeight = secondChildWeight; mFirstChildMargin = firstChildMargin; mSecondChildMargin = secondChildMargin; } @Override Loading @@ -223,6 +253,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { + ", mSecondChildGravity=" + mSecondChildGravity + ", mFirstChildWeight=" + mFirstChildWeight + ", mSecondChildWeight=" + mSecondChildWeight + ", mFirstChildMargin=" + mFirstChildMargin + ", mSecondChildMargin=" + mSecondChildMargin + '}'; } Loading @@ -246,15 +278,13 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } private LinearLayout buildLayout(boolean isNotificationOptimized, @LinearLayout.OrientationMode int orientation, List<View> children) { private LinearLayout buildLayout(boolean isNotificationOptimized, List<View> children) { final LinearLayout linearLayout; if (isNotificationOptimized) { linearLayout = new NotificationOptimizedLinearLayout(mContext); } else { linearLayout = new LinearLayout(mContext); } linearLayout.setOrientation(orientation); for (int i = 0; i < children.size(); i++) { linearLayout.addView(children.get(i)); } Loading @@ -262,7 +292,8 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } private void assertLayoutsEqual(String testCase, View controlView, View testView) { mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase) mExpect.withMessage( "MeasuredWidths are not equal. Test Case:" + testCase) .that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth()); mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase) .that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight()); Loading @@ -286,23 +317,12 @@ public class NotificationOptimizedLinearLayoutComparisonTest { } } private static class TestView extends View { TestView(Context context) { super(context); } @Override public int getBaseline() { return 5; } } private TestSpec createTestSpec(int orientation, int widthSpec, int heightSpec, int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth, int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity, int firstChildWeight, int secondChildWeight) { int firstChildWeight, int secondChildWeight, int firstChildMargin, int secondChildMargin) { return new TestSpec( orientation, Loading @@ -314,16 +334,16 @@ public class NotificationOptimizedLinearLayoutComparisonTest { firstChildGravity, secondChildGravity, firstChildWeight, secondChildWeight); secondChildWeight, firstChildMargin, secondChildMargin); } private View buildChildView(int childLayoutWidth, int childLayoutHeight, int childGravity, int childWeight) { final View childView = new TestView(mContext); // Set desired size using LayoutParams LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth, childLayoutHeight, childWeight); params.gravity = childGravity; private View buildChildView() { final View childView = new TextView(mContext); // this is initial value. We are going to mutate this layout params during the test. LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); childView.setLayoutParams(params); return childView; } Loading