diff --git a/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java b/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java index 68b10184af1a020f1b977af05cd0de2a18304395..af9d1bb05fb9cf34ca814cb08f17896dcffdd76f 100644 --- a/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java +++ b/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java @@ -411,4 +411,25 @@ public class FlexboxHelperTest { assertThat(view3.getMeasuredWidth(), isEqualAllowingError(333)); assertThat(view4.getMeasuredWidth(), isEqualAllowingError(333)); } + + @Test + public void testMakeCombinedLong() { + int higher = -1; + int lower = 10; + long combined = mFlexboxHelper.makeCombinedLong(lower, higher); + assertThat(mFlexboxHelper.extractHigherInt(combined), is(higher)); + assertThat(mFlexboxHelper.extractLowerInt(combined), is(lower)); + + higher = Integer.MAX_VALUE; + lower = Integer.MIN_VALUE; + combined = mFlexboxHelper.makeCombinedLong(lower, higher); + assertThat(mFlexboxHelper.extractHigherInt(combined), is(higher)); + assertThat(mFlexboxHelper.extractLowerInt(combined), is(lower)); + + higher = View.MeasureSpec.makeMeasureSpec(500, View.MeasureSpec.EXACTLY); + lower = View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.UNSPECIFIED); + combined = mFlexboxHelper.makeCombinedLong(lower, higher); + assertThat(mFlexboxHelper.extractHigherInt(combined), is(higher)); + assertThat(mFlexboxHelper.extractLowerInt(combined), is(lower)); + } } diff --git a/flexbox/src/androidTest/java/com/google/android/flexbox/test/FlexboxLayoutManagerTest.java b/flexbox/src/androidTest/java/com/google/android/flexbox/test/FlexboxLayoutManagerTest.java index 5765110047a092eb8b90b5136943cbc9e3ff07d9..f66842db234847ac7cddd7ab42070cfa995819f7 100644 --- a/flexbox/src/androidTest/java/com/google/android/flexbox/test/FlexboxLayoutManagerTest.java +++ b/flexbox/src/androidTest/java/com/google/android/flexbox/test/FlexboxLayoutManagerTest.java @@ -18,6 +18,7 @@ package com.google.android.flexbox.test; import com.google.android.flexbox.AlignContent; import com.google.android.flexbox.AlignItems; +import com.google.android.flexbox.AlignSelf; import com.google.android.flexbox.FlexDirection; import com.google.android.flexbox.FlexWrap; import com.google.android.flexbox.FlexboxLayoutManager; @@ -1421,6 +1422,164 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getChildCount(), is(not(200))); } + @Test + @FlakyTest + public void testAlignItems_stretch_direction_row() throws Throwable { + final FlexboxTestActivity activity = mActivityRule.getActivity(); + final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(); + final TestAdapter adapter = new TestAdapter(); + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.setContentView(R.layout.recyclerview); + RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); + layoutManager.setFlexDirection(FlexDirection.ROW); + layoutManager.setAlignItems(AlignItems.STRETCH); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 70, 80); + adapter.addItem(lp1); + FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 70, 50); + adapter.addItem(lp2); + FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 70, 30); + adapter.addItem(lp3); + // RecyclerView width: 320, height: 240. + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + assertThat(layoutManager.getFlexDirection(), is(FlexDirection.ROW)); + assertThat(layoutManager.getAlignItems(), is(AlignItems.STRETCH)); + assertThat(layoutManager.getFlexItemCount(), is(3)); + assertThat(layoutManager.getFlexLines().size(), is(1)); + // Verify all items heights are stretched + assertThat(layoutManager.getChildAt(0).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(1).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(2).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + } + + @Test + @FlakyTest + public void testAlignItems_stretch_direction_column() throws Throwable { + final FlexboxTestActivity activity = mActivityRule.getActivity(); + final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(); + final TestAdapter adapter = new TestAdapter(); + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.setContentView(R.layout.recyclerview); + RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); + layoutManager.setFlexDirection(FlexDirection.COLUMN); + layoutManager.setAlignItems(AlignItems.STRETCH); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 80, 70); + adapter.addItem(lp1); + FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 50, 70); + adapter.addItem(lp2); + FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 30, 70); + adapter.addItem(lp3); + // RecyclerView width: 320, height: 240. + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + assertThat(layoutManager.getFlexDirection(), is(FlexDirection.COLUMN)); + assertThat(layoutManager.getAlignItems(), is(AlignItems.STRETCH)); + assertThat(layoutManager.getFlexItemCount(), is(3)); + assertThat(layoutManager.getFlexLines().size(), is(1)); + // Verify all items widths are stretched + assertThat(layoutManager.getChildAt(0).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(1).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(2).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + } + + @Test + @FlakyTest + public void testAlignSelf_stretch_direction_row() throws Throwable { + final FlexboxTestActivity activity = mActivityRule.getActivity(); + final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(); + final TestAdapter adapter = new TestAdapter(); + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.setContentView(R.layout.recyclerview); + RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); + layoutManager.setFlexDirection(FlexDirection.ROW); + layoutManager.setAlignItems(AlignItems.FLEX_START); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 70, 80); + adapter.addItem(lp1); + FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 70, 50); + adapter.addItem(lp2); + FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 70, 30); + lp3.setAlignSelf(AlignSelf.STRETCH); + adapter.addItem(lp3); + // RecyclerView width: 320, height: 240. + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + assertThat(layoutManager.getFlexDirection(), is(FlexDirection.ROW)); + assertThat(layoutManager.getAlignItems(), is(AlignItems.FLEX_START)); + assertThat(layoutManager.getFlexItemCount(), is(3)); + assertThat(layoutManager.getFlexLines().size(), is(1)); + // Verify the item whose align self is set to stretch is stretched + assertThat(layoutManager.getChildAt(0).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(1).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 50))); + assertThat(layoutManager.getChildAt(2).getHeight(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + } + + @Test + @FlakyTest + public void testAlignSelf_stretch_direction_column() throws Throwable { + final FlexboxTestActivity activity = mActivityRule.getActivity(); + final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(); + final TestAdapter adapter = new TestAdapter(); + mActivityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.setContentView(R.layout.recyclerview); + RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); + layoutManager.setFlexDirection(FlexDirection.COLUMN); + layoutManager.setAlignItems(AlignItems.FLEX_START); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 80, 70); + adapter.addItem(lp1); + FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 50, 70); + adapter.addItem(lp2); + FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 30, 70); + lp3.setAlignSelf(AlignSelf.STRETCH); + adapter.addItem(lp3); + // RecyclerView width: 320, height: 240. + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + assertThat(layoutManager.getFlexDirection(), is(FlexDirection.COLUMN)); + assertThat(layoutManager.getAlignItems(), is(AlignItems.FLEX_START)); + assertThat(layoutManager.getFlexItemCount(), is(3)); + assertThat(layoutManager.getFlexLines().size(), is(1)); + // Verify the item whose align self is set to stretch is stretched + assertThat(layoutManager.getChildAt(0).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + assertThat(layoutManager.getChildAt(1).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 50))); + assertThat(layoutManager.getChildAt(2).getWidth(), + isEqualAllowingError(TestUtil.dpToPixel(activity, 80))); + } + /** * Creates a new flex item. * diff --git a/flexbox/src/main/java/com/google/android/flexbox/AlignSelf.java b/flexbox/src/main/java/com/google/android/flexbox/AlignSelf.java index 37fca7b27659c066a543fdab46504d95042ce35e..1f420e342dd3c704bcb49bdb5d4bde10cb2c08aa 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/AlignSelf.java +++ b/flexbox/src/main/java/com/google/android/flexbox/AlignSelf.java @@ -37,4 +37,19 @@ public @interface AlignSelf { * the {@link AlignItems} attribute from its parent. */ int AUTO = -1; + + /** This item's edge is placed on the cross start line. */ + int FLEX_START = AlignItems.FLEX_START; + + /** This item's edge is placed on the cross end line. */ + int FLEX_END = AlignItems.FLEX_END; + + /** This item's edge is centered along the cross axis. */ + int CENTER = AlignItems.CENTER; + + /** This items is aligned based on their text's baselines. */ + int BASELINE = AlignItems.BASELINE; + + /** This item is stretched to fill the flex line's cross size. */ + int STRETCH = AlignItems.STRETCH; } diff --git a/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java b/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java index 02c424902e7483efb520fdc556b5f326fe3147b8..ee1722c8c786875531ae4d2786e5faf28dd27efe 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java +++ b/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java @@ -18,6 +18,7 @@ package com.google.android.flexbox; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.view.MarginLayoutParamsCompat; import android.support.v4.view.ViewCompat; import android.util.SparseIntArray; @@ -77,6 +78,15 @@ class FlexboxHelper { @Nullable long[] mMeasureSpecCache; + /** + * Cache a flex item's measured width and height. The first 32 bit represents the height, the last + * 32 bit represents the width of each flex item. + * E.g. an entry is created like + * {@code (long) view.getMeasuredHeight() << 32 | view.getMeasuredWidth()} + */ + @Nullable + private long[] mMeasuredSizeCache; + FlexboxHelper(FlexContainer flexContainer) { mFlexContainer = flexContainer; } @@ -313,11 +323,7 @@ class FlexboxHelper { + flexItem.getMarginTop() + flexItem.getMarginBottom(), flexItem.getHeight()); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[i] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(i, childWidthMeasureSpec, childHeightMeasureSpec, child); // Check the size constraint after the first measurement for the child // To prevent the child's width/height violate the size constraints imposed by the @@ -522,11 +528,7 @@ class FlexboxHelper { + flexItem.getMarginTop() + flexItem.getMarginBottom(), childHeight); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[i] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(i, childWidthMeasureSpec, childHeightMeasureSpec, child); // Check the size constraint after the first measurement for the child // To prevent the child's width/height violate the size constraints imposed by the @@ -673,9 +675,7 @@ class FlexboxHelper { int heightSpec = View.MeasureSpec .makeMeasureSpec(childHeight, View.MeasureSpec.EXACTLY); view.measure(widthSpec, heightSpec); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[index] = makeCombinedMeasureSpec(widthSpec, heightSpec); - } + updateMeasureCache(index, widthSpec, heightSpec, view); } } @@ -812,14 +812,13 @@ class FlexboxHelper { if (!mChildrenFrozen[childIndex] && flexItem.getFlexGrow() > 0f) { int childMeasuredWidth = child.getMeasuredWidth(); - if (mMeasureSpecCache != null) { - // Retrieve the measured width from the measure spec cache because there + if (mMeasuredSizeCache != null) { + // Retrieve the measured width from the cache because there // are some cases that the view is re-created from the last measure, thus // View#getMeasuredWidth returns 0. // E.g. if the flex container is FlexboxLayoutManager, the case happens // frequently - int childWidthSpec = extractWidthMeasureSpec(mMeasureSpecCache[childIndex]); - childMeasuredWidth = View.MeasureSpec.getSize(childWidthSpec); + childMeasuredWidth = extractLowerInt(mMeasuredSizeCache[childIndex]); } float rawCalculatedWidth = childMeasuredWidth + unitSpace * flexItem.getFlexGrow(); @@ -855,11 +854,8 @@ class FlexboxHelper { child.measure(childWidthMeasureSpec, childHeightMeasureSpec); largestCrossSize = Math.max(largestCrossSize, child.getMeasuredHeight() + flexItem.getMarginTop() + flexItem.getMarginBottom()); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[childIndex] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(childIndex, childWidthMeasureSpec, childHeightMeasureSpec, + child); } flexLine.mMainSize += child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight(); @@ -867,15 +863,14 @@ class FlexboxHelper { // The direction of the main axis is vertical if (!mChildrenFrozen[childIndex] && flexItem.getFlexGrow() > 0f) { int childMeasuredHeight = child.getMeasuredHeight(); - if (mMeasureSpecCache != null) { - // Retrieve the measured height from the measure spec cache because there + if (mMeasuredSizeCache != null) { + // Retrieve the measured height from the cache because there // are some cases that the view is re-created from the last measure, thus // View#getMeasuredHeight returns 0. // E.g. if the flex container is FlexboxLayoutManager, that case happens // frequently - int childHeightSpec = - extractHeightMeasureSpec(mMeasureSpecCache[childIndex]); - childMeasuredHeight = View.MeasureSpec.getSize(childHeightSpec); + childMeasuredHeight = + extractHigherInt(mMeasuredSizeCache[childIndex]); } float rawCalculatedHeight = childMeasuredHeight + unitSpace * flexItem.getFlexGrow(); @@ -912,11 +907,8 @@ class FlexboxHelper { child.measure(childWidthMeasureSpec, childHeightMeasureSpec); largestCrossSize = Math.max(largestCrossSize, child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight()); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[childIndex] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(childIndex, childWidthMeasureSpec, childHeightMeasureSpec, + child); } flexLine.mMainSize += child.getMeasuredHeight() + flexItem.getMarginTop() + flexItem.getMarginBottom(); @@ -992,14 +984,13 @@ class FlexboxHelper { // The direction of main axis is horizontal if (!mChildrenFrozen[childIndex] && flexItem.getFlexShrink() > 0f) { int childMeasuredWidth = child.getMeasuredWidth(); - if (mMeasureSpecCache != null) { - // Retrieve the measured width from the measure spec cache because there + if (mMeasuredSizeCache != null) { + // Retrieve the measured width from the cache because there // are some cases that the view is re-created from the last measure, thus // View#getMeasuredWidth returns 0. - // E.g. if the flex container is FlexboxLayoutManager, that case happens + // E.g. if the flex container is FlexboxLayoutManager, the case happens // frequently - int childWidthSpec = extractWidthMeasureSpec(mMeasureSpecCache[childIndex]); - childMeasuredWidth = View.MeasureSpec.getSize(childWidthSpec); + childMeasuredWidth = extractLowerInt(mMeasuredSizeCache[childIndex]); } float rawCalculatedWidth = childMeasuredWidth - unitShrink * flexItem.getFlexShrink(); @@ -1035,11 +1026,8 @@ class FlexboxHelper { child.measure(childWidthMeasureSpec, childHeightMeasureSpec); largestCrossSize = Math.max(largestCrossSize, child.getMeasuredHeight() + flexItem.getMarginTop() + flexItem.getMarginBottom()); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[childIndex] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(childIndex, childWidthMeasureSpec, childHeightMeasureSpec, + child); } flexLine.mMainSize += child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight(); @@ -1047,15 +1035,13 @@ class FlexboxHelper { // The direction of main axis is vertical if (!mChildrenFrozen[childIndex] && flexItem.getFlexShrink() > 0f) { int childMeasuredHeight = child.getMeasuredHeight(); - if (mMeasureSpecCache != null) { - // Retrieve the measured height from the measure spec cache because there + if (mMeasuredSizeCache != null) { + // Retrieve the measured height from the cache because there // are some cases that the view is re-created from the last measure, thus // View#getMeasuredHeight returns 0. - // E.g. if the flex container is FlexboxLayoutManager, the case happens + // E.g. if the flex container is FlexboxLayoutManager, that case happens // frequently - int childHeightSpec = - extractHeightMeasureSpec(mMeasureSpecCache[childIndex]); - childMeasuredHeight = View.MeasureSpec.getSize(childHeightSpec); + childMeasuredHeight = extractHigherInt(mMeasuredSizeCache[childIndex]); } float rawCalculatedHeight = childMeasuredHeight - unitShrink * flexItem.getFlexShrink(); @@ -1087,11 +1073,8 @@ class FlexboxHelper { child.measure(childWidthMeasureSpec, childHeightMeasureSpec); largestCrossSize = Math.max(largestCrossSize, child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight()); - if (mMeasureSpecCache != null) { - mMeasureSpecCache[childIndex] = makeCombinedMeasureSpec( - childWidthMeasureSpec, - childHeightMeasureSpec); - } + updateMeasureCache(childIndex, childWidthMeasureSpec, childHeightMeasureSpec, + child); } flexLine.mMainSize += child.getMeasuredHeight() + flexItem.getMarginTop() + flexItem.getMarginBottom(); @@ -1328,11 +1311,11 @@ class FlexboxHelper { switch (flexDirection) { case FlexDirection.ROW: // Intentional fall through case FlexDirection.ROW_REVERSE: - stretchViewVertically(view, flexLine.mCrossSize); + stretchViewVertically(view, flexLine.mCrossSize, viewIndex); break; case FlexDirection.COLUMN: case FlexDirection.COLUMN_REVERSE: - stretchViewHorizontally(view, flexLine.mCrossSize); + stretchViewHorizontally(view, flexLine.mCrossSize, viewIndex); break; default: throw new IllegalArgumentException( @@ -1347,11 +1330,11 @@ class FlexboxHelper { switch (flexDirection) { case FlexDirection.ROW: // Intentional fall through case FlexDirection.ROW_REVERSE: - stretchViewVertically(view, flexLine.mCrossSize); + stretchViewVertically(view, flexLine.mCrossSize, index); break; case FlexDirection.COLUMN: case FlexDirection.COLUMN_REVERSE: - stretchViewHorizontally(view, flexLine.mCrossSize); + stretchViewHorizontally(view, flexLine.mCrossSize, index); break; default: throw new IllegalArgumentException( @@ -1367,15 +1350,32 @@ class FlexboxHelper { * * @param view the View to be stretched * @param crossSize the cross size + * @param index the index of the view */ - private void stretchViewVertically(View view, int crossSize) { - // TODO: For FlexboxLayoutManager, retrieve the measured width from the cache - FlexboxLayout.LayoutParams lp = (FlexboxLayout.LayoutParams) view.getLayoutParams(); - int newHeight = crossSize - lp.topMargin - lp.bottomMargin; - newHeight = Math.max(newHeight, 0); - view.measure(View.MeasureSpec - .makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(newHeight, View.MeasureSpec.EXACTLY)); + private void stretchViewVertically(View view, int crossSize, int index) { + FlexItem flexItem = (FlexItem) view.getLayoutParams(); + int newHeight = crossSize - flexItem.getMarginTop() - flexItem.getMarginBottom(); + newHeight = Math.max(newHeight, flexItem.getMinHeight()); + newHeight = Math.min(newHeight, flexItem.getMaxHeight()); + int childWidthSpec; + int measuredWidth; + if (mMeasuredSizeCache != null) { + // Retrieve the measured height from the cache because there + // are some cases that the view is re-created from the last measure, thus + // View#getMeasuredHeight returns 0. + // E.g. if the flex container is FlexboxLayoutManager, that case happens + // frequently + measuredWidth = extractLowerInt(mMeasuredSizeCache[index]); + } else { + measuredWidth = view.getMeasuredWidth(); + } + childWidthSpec = View.MeasureSpec.makeMeasureSpec(measuredWidth, + View.MeasureSpec.EXACTLY); + + int childHeightSpec = View.MeasureSpec.makeMeasureSpec(newHeight, View.MeasureSpec.EXACTLY); + view.measure(childWidthSpec, childHeightSpec); + + updateMeasureCache(index, childWidthSpec, childHeightSpec, view); } /** @@ -1383,16 +1383,30 @@ class FlexboxHelper { * * @param view the View to be stretched * @param crossSize the cross size + * @param index the index of the view */ - private void stretchViewHorizontally(View view, int crossSize) { - // TODO: For FlexboxLayoutManager, retrieve the measured height from the cache - FlexboxLayout.LayoutParams lp = (FlexboxLayout.LayoutParams) view.getLayoutParams(); - int newWidth = crossSize - lp.leftMargin - lp.rightMargin; - newWidth = Math.max(newWidth, 0); - view.measure(View.MeasureSpec - .makeMeasureSpec(newWidth, View.MeasureSpec.EXACTLY), - View.MeasureSpec - .makeMeasureSpec(view.getMeasuredHeight(), View.MeasureSpec.EXACTLY)); + private void stretchViewHorizontally(View view, int crossSize, int index) { + FlexItem flexItem = (FlexItem) view.getLayoutParams(); + int newWidth = crossSize - flexItem.getMarginLeft() - flexItem.getMarginRight(); + newWidth = Math.max(newWidth, flexItem.getMinWidth()); + newWidth = Math.min(newWidth, flexItem.getMaxWidth()); + int childHeightSpec; + int measuredHeight; + if (mMeasuredSizeCache != null) { + // Retrieve the measured height from the cache because there + // are some cases that the view is re-created from the last measure, thus + // View#getMeasuredHeight returns 0. + // E.g. if the flex container is FlexboxLayoutManager, that case happens + // frequently + measuredHeight = extractHigherInt(mMeasuredSizeCache[index]); + } else { + measuredHeight = view.getMeasuredHeight(); + } + childHeightSpec = View.MeasureSpec.makeMeasureSpec(measuredHeight, + View.MeasureSpec.EXACTLY); + int childWidthSpec = View.MeasureSpec.makeMeasureSpec(newWidth, View.MeasureSpec.EXACTLY); + view.measure(childWidthSpec, childHeightSpec); + updateMeasureCache(index, childWidthSpec, childHeightSpec, view); } /** @@ -1554,6 +1568,16 @@ class FlexboxHelper { } } + void ensureMeasuredSizeCache(int size) { + if (mMeasuredSizeCache == null) { + mMeasuredSizeCache = new long[size < INITIAL_CAPACITY ? INITIAL_CAPACITY : size]; + } else if (mMeasuredSizeCache.length < size) { + int newCapacity = mMeasuredSizeCache.length * 2; + newCapacity = newCapacity >= size ? newCapacity : size; + mMeasuredSizeCache = Arrays.copyOf(mMeasuredSizeCache, newCapacity); + } + } + void ensureMeasureSpecCache(int size) { if (mMeasureSpecCache == null) { mMeasureSpecCache = new long[size < INITIAL_CAPACITY ? INITIAL_CAPACITY : size]; @@ -1567,18 +1591,18 @@ class FlexboxHelper { /** * @param measureSpec the long value that consists of width and height measure specs * @return the width measure spec from the combined long value - * @see #makeCombinedMeasureSpec(int, int) + * @see #makeCombinedLong(int, int) */ - int extractWidthMeasureSpec(long measureSpec) { - return (int) (measureSpec & MEASURE_SPEC_WIDTH_MASK); + int extractLowerInt(long measureSpec) { + return (int) measureSpec; } /** * @param measureSpec the long value that consists of width and height measure specs * @return the height measure spec from the combined long value - * @see #makeCombinedMeasureSpec(int, int) + * @see #makeCombinedLong(int, int) */ - int extractHeightMeasureSpec(long measureSpec) { + int extractHigherInt(long measureSpec) { return (int) (measureSpec >> 32); } @@ -1590,11 +1614,27 @@ class FlexboxHelper { * @param widthMeasureSpec the width measure spec to consist the result long value * @param heightMeasureSpec the height measure spec to consist the result long value * @return the combined long value - * @see #extractWidthMeasureSpec(long) - * @see #extractHeightMeasureSpec(long) + * @see #extractLowerInt(long) + * @see #extractHigherInt(long) */ - private long makeCombinedMeasureSpec(int widthMeasureSpec, int heightMeasureSpec) { - return (long) heightMeasureSpec << 32 | widthMeasureSpec; + @VisibleForTesting + long makeCombinedLong(int widthMeasureSpec, int heightMeasureSpec) { + // Suppress sign extension for the low bytes + return (long) heightMeasureSpec << 32 | (long) widthMeasureSpec & MEASURE_SPEC_WIDTH_MASK; + } + + private void updateMeasureCache(int index, int widthMeasureSpec, int heightMeasureSpec, + View view) { + if (mMeasureSpecCache != null) { + mMeasureSpecCache[index] = makeCombinedLong( + widthMeasureSpec, + heightMeasureSpec); + } + if (mMeasuredSizeCache != null) { + mMeasuredSizeCache[index] = makeCombinedLong( + view.getMeasuredWidth(), + view.getMeasuredHeight()); + } } void ensureIndexToFlexLine(int size) { diff --git a/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java b/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java index f65bc8720dd028c9968de827d2588c538eb7ed39..944933efc21fa6e1bf7be8be367faf64598adbfa 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java +++ b/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java @@ -454,6 +454,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements ensureOrientationHelper(); ensureLayoutState(); mFlexboxHelper.ensureMeasureSpecCache(childCount); + mFlexboxHelper.ensureMeasuredSizeCache(childCount); mFlexboxHelper.ensureIndexToFlexLine(childCount); mLayoutState.mShouldRecycle = false; @@ -884,8 +885,8 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements // retrieved from Recycler, in that case measured width/height are set to 0 even // each visible child should be measured at least once in the FlexboxHelper long measureSpec = mFlexboxHelper.mMeasureSpecCache[i]; - int widthSpec = mFlexboxHelper.extractWidthMeasureSpec(measureSpec); - int heightSpec = mFlexboxHelper.extractHeightMeasureSpec(measureSpec); + int widthSpec = mFlexboxHelper.extractLowerInt(measureSpec); + int heightSpec = mFlexboxHelper.extractHigherInt(measureSpec); LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (shouldMeasureChild(view, widthSpec, heightSpec, lp)) { // TODO: Need to consider decorator length @@ -1007,8 +1008,8 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements // retrieved from Recycler, in that case measured width/height are set to 0 even // each visible child should be measured at least once in the FlexboxHelper long measureSpec = mFlexboxHelper.mMeasureSpecCache[i]; - int widthSpec = mFlexboxHelper.extractWidthMeasureSpec(measureSpec); - int heightSpec = mFlexboxHelper.extractHeightMeasureSpec(measureSpec); + int widthSpec = mFlexboxHelper.extractLowerInt(measureSpec); + int heightSpec = mFlexboxHelper.extractHigherInt(measureSpec); LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (shouldMeasureChild(view, widthSpec, heightSpec, lp)) { // TODO: Need to consider decorator length