From add9faf32088da0037061cca140bcf71e192dd27 Mon Sep 17 00:00:00 2001 From: Takeshi Hagikura Date: Tue, 6 Dec 2016 15:59:02 +0900 Subject: [PATCH] Implement incremental calculation for the flex lines. (#151) In the RecyclerView, only the visible part of the RecyclerView is necessary to be rendered. Thus, not all the flex items need to be measured at first. This PR makes the flex lines to be calculated only the visible part. Also this PR removes the support for the order attribute from the FlexboxLayoutManger because calculating the order requires all view to be inflated at least once, which may lead to bad performance if there is a large number of items in the adapter. --- .../android/flexbox/FlexboxHelperTest.java | 21 +- .../test/FlexboxLayoutManagerTest.java | 382 ++++++++++++------ .../androidTest/res/layout/recyclerview.xml | 6 +- .../google/android/flexbox/FlexboxHelper.java | 344 ++++++++++++---- .../google/android/flexbox/FlexboxLayout.java | 33 +- .../android/flexbox/FlexboxLayoutManager.java | 178 +++++--- 6 files changed, 678 insertions(+), 286 deletions(-) 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 287edbb..1afe08c 100644 --- a/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java +++ b/flexbox/src/androidTest/java/com/google/android/flexbox/FlexboxHelperTest.java @@ -30,6 +30,7 @@ import android.view.View; import static com.google.android.flexbox.test.IsEqualAllowingError.isEqualAllowingError; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -77,6 +78,7 @@ public class FlexboxHelperTest { int heightMeasureSpec = View.MeasureSpec .makeMeasureSpec(1000, View.MeasureSpec.UNSPECIFIED); + mFlexboxHelper.ensureIndexToFlexLine(mFlexContainer.getFlexItemCount()); FlexboxHelper.FlexLinesResult result = mFlexboxHelper .calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec); @@ -88,10 +90,11 @@ public class FlexboxHelperTest { assertEquals(100, result.mFlexLines.get(1).getCrossSize()); assertEquals(100, result.mFlexLines.get(2).getCrossSize()); - assertEquals(0, mFlexboxHelper.mIndexToFlexLine.get(0)); - assertEquals(0, mFlexboxHelper.mIndexToFlexLine.get(1)); - assertEquals(1, mFlexboxHelper.mIndexToFlexLine.get(2)); - assertEquals(2, mFlexboxHelper.mIndexToFlexLine.get(3)); + assertNotNull(mFlexboxHelper.mIndexToFlexLine); + assertEquals(0, mFlexboxHelper.mIndexToFlexLine[0]); + assertEquals(0, mFlexboxHelper.mIndexToFlexLine[1]); + assertEquals(1, mFlexboxHelper.mIndexToFlexLine[2]); + assertEquals(2, mFlexboxHelper.mIndexToFlexLine[3]); } @Test @@ -118,6 +121,7 @@ public class FlexboxHelperTest { int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.UNSPECIFIED); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(500, View.MeasureSpec.EXACTLY); + mFlexboxHelper.ensureIndexToFlexLine(mFlexContainer.getFlexItemCount()); FlexboxHelper.FlexLinesResult result = mFlexboxHelper .calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec); @@ -129,10 +133,11 @@ public class FlexboxHelperTest { assertEquals(100, result.mFlexLines.get(1).getCrossSize()); assertEquals(100, result.mFlexLines.get(2).getCrossSize()); - assertEquals(0, mFlexboxHelper.mIndexToFlexLine.get(0)); - assertEquals(0, mFlexboxHelper.mIndexToFlexLine.get(1)); - assertEquals(1, mFlexboxHelper.mIndexToFlexLine.get(2)); - assertEquals(2, mFlexboxHelper.mIndexToFlexLine.get(3)); + assertNotNull(mFlexboxHelper.mIndexToFlexLine); + assertEquals(0, mFlexboxHelper.mIndexToFlexLine[0]); + assertEquals(0, mFlexboxHelper.mIndexToFlexLine[1]); + assertEquals(1, mFlexboxHelper.mIndexToFlexLine[2]); + assertEquals(2, mFlexboxHelper.mIndexToFlexLine[3]); } @Test 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 a902a02..6f77003 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 @@ -29,12 +29,20 @@ import org.junit.runner.RunWith; import android.content.Context; import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.action.CoordinatesProvider; +import android.support.test.espresso.action.GeneralLocation; +import android.support.test.espresso.action.GeneralSwipeAction; +import android.support.test.espresso.action.Press; +import android.support.test.espresso.action.Swipe; import android.support.test.filters.FlakyTest; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.support.v7.widget.RecyclerView; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.matcher.ViewMatchers.withId; import static com.google.android.flexbox.test.IsEqualAllowingError.isEqualAllowingError; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -170,17 +178,17 @@ public class FlexboxLayoutManagerTest { RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - // RecyclerView width: 400, height: 300. - // Computed FlexContainer width: 400, height: 650 + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + // RecyclerView width: 320, height: 240. + // Computed FlexContainer width: 320, height: 450 } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -189,6 +197,54 @@ public class FlexboxLayoutManagerTest { // (invisible) so are not included in the count of of the getChildCount. assertThat(layoutManager.getFlexItemCount(), is(9)); assertThat(layoutManager.getChildCount(), is(6)); + + // At first only the visible area of the flex lines are calculated + assertThat(layoutManager.getFlexLines().size(), is(3)); + } + + @Test + @FlakyTest + public void testAddViewHolders_direction_row_scrollVertically() 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); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + adapter.addItem(createLayoutParams(activity, 150, 90)); + // RecyclerView width: 320, height: 240. + // Computed FlexContainer width: 320, height: 450 + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + // In total 9 items are added but the seventh item and the items follow aren't attached + // (invisible) so are not included in the count of of the getChildCount. + assertThat(layoutManager.getFlexItemCount(), is(9)); + assertThat(layoutManager.getChildCount(), is(6)); + // At first only the visible area of the flex lines are calculated + assertThat(layoutManager.getFlexLines().size(), is(3)); + + onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER, + GeneralLocation.TOP_CENTER)); + assertThat(layoutManager.getFlexItemCount(), is(9)); + // The RecyclerView is swiped to top until it reaches the bottom of the view. + // The number of the visible views should be 5 + assertThat(layoutManager.getChildCount(), is(5)); + // Since the RecyclerView is swiped to the bottom, all flex lines should be calculated + // by now assertThat(layoutManager.getFlexLines().size(), is(5)); } @@ -214,7 +270,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 150, 130); lp3.setFlexGrow(1.0f); adapter.addItem(lp3); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -224,11 +280,11 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(2)); assertThat(layoutManager.getChildAt(0).getWidth(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 160))); assertThat(layoutManager.getChildAt(1).getWidth(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 160))); assertThat(layoutManager.getChildAt(2).getWidth(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 400))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 320))); } @Test @@ -244,17 +300,19 @@ public class FlexboxLayoutManagerTest { RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - adapter.addItem(createLayoutParams(activity, 150, 130)); - // RecyclerView width: 400, height: 300. - // Computed FlexContainer width: 650, height: 300 + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + + layoutManager.setFlexDirection(FlexDirection.COLUMN); + // RecyclerView width: 320, height: 240. + // Computed FlexContainer width: 450, height: 240 } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -263,6 +321,56 @@ public class FlexboxLayoutManagerTest { // (invisible) so are not included in the count of of the getChildCount. assertThat(layoutManager.getFlexItemCount(), is(9)); assertThat(layoutManager.getChildCount(), is(6)); + + // At first only the visible area of the flex lines are calculated + assertThat(layoutManager.getFlexLines().size(), is(3)); + } + + @Test + @FlakyTest + public void testAddViewHolders_direction_column_scrollHorizontally() 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); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + adapter.addItem(createLayoutParams(activity, 120, 100)); + + layoutManager.setFlexDirection(FlexDirection.COLUMN); + // RecyclerView width: 320, height: 240. + // Computed FlexContainer width: 500, height: 240 + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + // In total 9 items are added but the seventh item and the items follow aren't attached + // (invisible) so are not included in the count of of the getChildCount. + assertThat(layoutManager.getFlexItemCount(), is(9)); + assertThat(layoutManager.getChildCount(), is(6)); + // At first only the visible area of the flex lines are calculated + assertThat(layoutManager.getFlexLines().size(), is(3)); + + onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.CENTER_RIGHT, + GeneralLocation.CENTER_LEFT)); + assertThat(layoutManager.getFlexItemCount(), is(9)); + // The RecyclerView is swiped to top until it reaches the right edge of the view. + // The number of the visible views should be 5 + assertThat(layoutManager.getChildCount(), is(5)); + // Since the RecyclerView is swiped to the bottom, all flex lines should be calculated + // by now assertThat(layoutManager.getFlexLines().size(), is(5)); } @@ -286,7 +394,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 50, 100); adapter.addItem(lp3); layoutManager.setJustifyContent(JustifyContent.FLEX_START); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -328,7 +436,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 50, 100); adapter.addItem(lp3); layoutManager.setJustifyContent(JustifyContent.FLEX_END); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -337,17 +445,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 170))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 220))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 220))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 400))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 320))); } @Test @@ -370,7 +478,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 50, 100); adapter.addItem(lp3); layoutManager.setJustifyContent(JustifyContent.CENTER); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -379,17 +487,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 85))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 275))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 235))); } @Test @@ -412,7 +520,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 50, 100); adapter.addItem(lp3); layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -421,17 +529,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 42))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 28))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 92))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 78))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 308))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 242))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 358))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 292))); } @Test @@ -454,7 +562,7 @@ public class FlexboxLayoutManagerTest { FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 50, 100); adapter.addItem(lp3); layoutManager.setJustifyContent(JustifyContent.SPACE_BETWEEN); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -467,13 +575,13 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getChildAt(0).getRight(), isEqualAllowingError(TestUtil.dpToPixel(activity, 50))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 400))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 320))); } @Test @@ -497,7 +605,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.ROW_REVERSE); layoutManager.setJustifyContent(JustifyContent.FLEX_START); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -507,17 +615,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 400))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 320))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 220))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 170))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 220))); } @Test @@ -541,7 +649,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.ROW_REVERSE); layoutManager.setJustifyContent(JustifyContent.FLEX_END); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -585,7 +693,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.ROW_REVERSE); layoutManager.setJustifyContent(JustifyContent.CENTER); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -595,17 +703,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 275))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 235))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 85))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); } @Test @@ -629,7 +737,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.ROW_REVERSE); layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -639,17 +747,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 308))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 242))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 358))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 292))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 42))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 28))); assertThat(layoutManager.getChildAt(2).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 92))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 78))); } @Test @@ -673,7 +781,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.ROW_REVERSE); layoutManager.setJustifyContent(JustifyContent.SPACE_BETWEEN); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -683,13 +791,13 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 350))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 270))); assertThat(layoutManager.getChildAt(0).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 400))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 320))); assertThat(layoutManager.getChildAt(1).getLeft(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 135))); assertThat(layoutManager.getChildAt(1).getRight(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 185))); assertThat(layoutManager.getChildAt(2).getLeft(), isEqualAllowingError(TestUtil.dpToPixel(activity, 0))); assertThat(layoutManager.getChildAt(2).getRight(), @@ -717,7 +825,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN); layoutManager.setJustifyContent(JustifyContent.FLEX_START); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -761,7 +869,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN); layoutManager.setJustifyContent(JustifyContent.FLEX_END); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -771,17 +879,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 150))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 90))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 140))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 140))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 240))); } @Test @@ -805,7 +913,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN); layoutManager.setJustifyContent(JustifyContent.CENTER); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -815,17 +923,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 75))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 45))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 195))); } @Test @@ -849,7 +957,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN); layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -859,17 +967,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 25))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 15))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 75))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 65))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 275))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); } @Test @@ -893,7 +1001,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN); layoutManager.setJustifyContent(JustifyContent.SPACE_BETWEEN); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -907,13 +1015,13 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getChildAt(0).getBottom(), isEqualAllowingError(TestUtil.dpToPixel(activity, 50))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 240))); } @Test @@ -937,7 +1045,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN_REVERSE); layoutManager.setJustifyContent(JustifyContent.FLEX_START); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -947,17 +1055,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 240))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 140))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 150))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 90))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 200))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 140))); } @Test @@ -981,7 +1089,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN_REVERSE); layoutManager.setJustifyContent(JustifyContent.FLEX_END); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -1025,7 +1133,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN_REVERSE); layoutManager.setJustifyContent(JustifyContent.CENTER); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -1035,17 +1143,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 195))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 75))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 45))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); } @Test @@ -1069,7 +1177,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN_REVERSE); layoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -1079,17 +1187,17 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 275))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 225))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 25))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 15))); assertThat(layoutManager.getChildAt(2).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 75))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 65))); } @Test @@ -1113,7 +1221,7 @@ public class FlexboxLayoutManagerTest { adapter.addItem(lp3); layoutManager.setFlexDirection(FlexDirection.COLUMN_REVERSE); layoutManager.setJustifyContent(JustifyContent.SPACE_BETWEEN); - // RecyclerView width: 400, height: 300. + // RecyclerView width: 320, height: 240. } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -1123,19 +1231,20 @@ public class FlexboxLayoutManagerTest { assertThat(layoutManager.getFlexItemCount(), is(3)); assertThat(layoutManager.getFlexLines().size(), is(1)); assertThat(layoutManager.getChildAt(0).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 250))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 190))); assertThat(layoutManager.getChildAt(0).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 300))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 240))); assertThat(layoutManager.getChildAt(1).getTop(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 125))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 95))); assertThat(layoutManager.getChildAt(1).getBottom(), - isEqualAllowingError(TestUtil.dpToPixel(activity, 175))); + isEqualAllowingError(TestUtil.dpToPixel(activity, 145))); assertThat(layoutManager.getChildAt(2).getTop(), isEqualAllowingError(TestUtil.dpToPixel(activity, 0))); assertThat(layoutManager.getChildAt(2).getBottom(), isEqualAllowingError(TestUtil.dpToPixel(activity, 50))); } + /** * Creates a new flex item. * @@ -1150,4 +1259,9 @@ public class FlexboxLayoutManagerTest { TestUtil.dpToPixel(context, width), TestUtil.dpToPixel(context, height)); } + + private static ViewAction swipe(CoordinatesProvider from, CoordinatesProvider to) { + return new GeneralSwipeAction(Swipe.FAST, from, to, Press.FINGER); + } } + diff --git a/flexbox/src/androidTest/res/layout/recyclerview.xml b/flexbox/src/androidTest/res/layout/recyclerview.xml index 8912807..37330bf 100644 --- a/flexbox/src/androidTest/res/layout/recyclerview.xml +++ b/flexbox/src/androidTest/res/layout/recyclerview.xml @@ -13,8 +13,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + android:layout_width="320dp" + android:layout_height="240dp" /> 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 66743cd..eda2f3e 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java +++ b/flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java @@ -29,6 +29,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import static android.support.v7.widget.RecyclerView.NO_POSITION; + /** * Offers various calculations for Flexbox to use the common logic between the classes such as * {@link FlexboxLayout} and {@link FlexboxLayoutManager}. @@ -41,20 +43,6 @@ class FlexboxHelper { private final FlexContainer mFlexContainer; - /** - * Holds reordered indices, which {@link FlexItem#getOrder()} parameters are taken - * into account - */ - int[] mReorderedIndices; - - /** - * Caches the {@link FlexItem#getOrder()} attributes for children views. - * Key: the index of the view reordered indices using the {@link FlexItem#getOrder()} - * isn't taken into account) - * Value: the value for the order attribute - */ - private SparseIntArray mOrderCache; - /** * Holds the 'frozen' state of children during measure. If a view is frozen it will no longer * expand or shrink regardless of flex grow/flex shrink attributes. @@ -74,10 +62,11 @@ class FlexboxHelper { *

* this instance should have following entries *

- * {0, 0}, {1, 0}, {2, 0}, {3, 1}, {4, 1} + * [0, 0, 0, 1, 1, ...] *

*/ - SparseIntArray mIndexToFlexLine; + @Nullable + int[] mIndexToFlexLine; /** * Cache the measured spec. The first 32 bit represents the height measure spec, the last @@ -105,7 +94,7 @@ class FlexboxHelper { * @return an array which have the reordered indices */ int[] createReorderedIndices(View viewBeforeAdded, int indexForViewBeforeAdded, - ViewGroup.LayoutParams paramsForViewBeforeAdded) { + ViewGroup.LayoutParams paramsForViewBeforeAdded, SparseIntArray orderCache) { int childCount = mFlexContainer.getFlexItemCount(); List orders = createOrders(childCount); Order orderForViewToBeAdded = new Order(); @@ -131,7 +120,7 @@ class FlexboxHelper { } orders.add(orderForViewToBeAdded); - return sortOrdersIntoReorderedIndices(childCount + 1, orders); + return sortOrdersIntoReorderedIndices(childCount + 1, orders, orderCache); } /** @@ -140,10 +129,10 @@ class FlexboxHelper { * * @return @return an array which have the reordered indices */ - int[] createReorderedIndices() { + int[] createReorderedIndices(SparseIntArray orderCache) { int childCount = mFlexContainer.getFlexItemCount(); List orders = createOrders(childCount); - return sortOrdersIntoReorderedIndices(childCount, orders); + return sortOrdersIntoReorderedIndices(childCount, orders, orderCache); } @NonNull @@ -167,12 +156,9 @@ class FlexboxHelper { * * @return {@code true} if changed from the last measurement, {@code false} otherwise. */ - boolean isOrderChangedFromLastMeasurement() { + boolean isOrderChangedFromLastMeasurement(SparseIntArray orderCache) { int childCount = mFlexContainer.getFlexItemCount(); - if (mOrderCache == null) { - mOrderCache = new SparseIntArray(childCount); - } - if (mOrderCache.size() != childCount) { + if (orderCache.size() != childCount) { return true; } for (int i = 0; i < childCount; i++) { @@ -181,29 +167,60 @@ class FlexboxHelper { continue; } FlexItem flexItem = (FlexItem) view.getLayoutParams(); - if (flexItem.getOrder() != mOrderCache.get(i)) { + if (flexItem.getOrder() != orderCache.get(i)) { return true; } } return false; } - private int[] sortOrdersIntoReorderedIndices(int childCount, List orders) { + private int[] sortOrdersIntoReorderedIndices(int childCount, List orders, + SparseIntArray orderCache) { Collections.sort(orders); - if (mOrderCache == null) { - mOrderCache = new SparseIntArray(childCount); - } - mOrderCache.clear(); + orderCache.clear(); int[] reorderedIndices = new int[childCount]; int i = 0; for (Order order : orders) { reorderedIndices[i] = order.index; - mOrderCache.append(i, order.order); + orderCache.append(i, order.order); i++; } return reorderedIndices; } + /** + * Calculate how many flex lines are needed in the flex container. + * This method should calculate all the flex lines from the existing flex items. + * + * @see #calculateHorizontalFlexLines(int, int, int, int, List) + */ + FlexLinesResult calculateHorizontalFlexLines(int widthMeasureSpec, int heightMeasureSpec) { + return calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec, Integer.MAX_VALUE, + 0, null); + } + + /** + * Calculate how many flex lines are needed in the flex container. + * Stop calculating it if the calculated amount along the cross size reaches the argument + * as the needsCalcAmount. + * + * @param widthMeasureSpec the width measure spec imposed by the flex container + * @param heightMeasureSpec the height measure spec imposed by the flex container + * @param needsCalcAmount the amount of pixels where flex line calculation should be stopped + * this is needed to avoid the expensive calculation if the + * calculation is needed only the small part of the entire flex + * container. (E.g. If the flex container is the + * {@link FlexboxLayoutManager}, the calculation only needs the + * visible area, imposing the entire calculation may cause bad + * performance + * @see #calculateHorizontalFlexLines(int, int, int, int, List) + */ + FlexLinesResult calculateHorizontalFlexLines(int widthMeasureSpec, int heightMeasureSpec, + int needsCalcAmount) { + return calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec, needsCalcAmount, + 0, null); + } + /** * Calculate how many flex lines are needed in the flex container layout by measuring each * child when the direction of the flex line is horizontal (left to right or right to left). @@ -213,14 +230,32 @@ class FlexboxHelper { * * @param widthMeasureSpec the width measure spec imposed by the flex container * @param heightMeasureSpec the height measure spec imposed by the flex container + * @param needsCalcAmount the amount of pixels where flex line calculation should be stopped + * this is needed to avoid the expensive calculation if the + * calculation is needed only the small part of the entire flex + * container. (E.g. If the flex container is the + * {@link FlexboxLayoutManager}, the calculation only needs the + * visible area, imposing the entire calculation may cause bad + * performance + * @param fromIndex the index from which the calculation starts + * @param existingLines If not null, calculated flex lines will be added to this instance * @return a instance of {@link FlexLinesResult} that contains a list of flex lines and the * child state used by {@link View#setMeasuredDimension(int, int)}. */ - FlexLinesResult calculateHorizontalFlexLines(int widthMeasureSpec, int heightMeasureSpec) { + FlexLinesResult calculateHorizontalFlexLines(int widthMeasureSpec, + int heightMeasureSpec, int needsCalcAmount, int fromIndex, + @Nullable List existingLines) { int widthMode = View.MeasureSpec.getMode(widthMeasureSpec); int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); FlexLinesResult result = new FlexLinesResult(); - List flexLines = new ArrayList<>(); + List flexLines; + FlexLine flexLine = new FlexLine(); + if (existingLines == null) { + flexLines = new ArrayList<>(); + } else { + flexLines = existingLines; + } + result.mFlexLines = flexLines; int childCount = mFlexContainer.getFlexItemCount(); int childState = 0; @@ -229,20 +264,25 @@ class FlexboxHelper { int paddingLeft = mFlexContainer.getPaddingLeft(); int paddingRight = mFlexContainer.getPaddingRight(); int largestHeightInRow = Integer.MIN_VALUE; - FlexLine flexLine = new FlexLine(); + // The amount of cross size calculated in this method call + int sumCrossSize = 0; // The index of the view in a same flex line. int indexInFlexLine = 0; flexLine.mMainSize = paddingLeft + paddingRight; - for (int i = 0; i < childCount; i++) { + for (int i = fromIndex; i < childCount; i++) { View child = mFlexContainer.getReorderedFlexItemAt(i); if (child == null) { - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + } continue; } else if (child.getVisibility() == View.GONE) { flexLine.mGoneItemCount++; flexLine.mItemCount++; - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + } continue; } @@ -296,7 +336,7 @@ class FlexboxHelper { child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight(), flexItem, i, indexInFlexLine)) { if (flexLine.getItemCountNotGone() > 0) { - addFlexLine(flexLines, flexLine, i - 1); + addFlexLine(flexLines, flexLine); } flexLine = new FlexLine(); @@ -309,6 +349,9 @@ class FlexboxHelper { flexLine.mItemCount++; indexInFlexLine++; } + if (mIndexToFlexLine != null) { + mIndexToFlexLine[i] = flexLines.size(); + } flexLine.mMainSize += child.getMeasuredWidth() + flexItem.getMarginLeft() + flexItem.getMarginRight(); flexLine.mTotalFlexGrow += flexItem.getFlexGrow(); @@ -331,12 +374,61 @@ class FlexboxHelper { child.getMeasuredHeight() - child.getBaseline() + flexItem.getMarginBottom()); } - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + sumCrossSize += flexLine.mCrossSize; + } + + if (sumCrossSize > needsCalcAmount) { + // Stop the calculation if the sum of cross size calculated reached to the point + // beyond the needsCalcAmount value to avoid unneeded calculation in a + // RecyclerView. + // To be precise, the decoration length may be added to the sumCrossSize, + // but we omit adding the decoration length because even without the decorator + // length, it's guaranteed that calculation is done at least beyond the + // needsCalcAmount + break; + } } result.mChildState = childState; return result; } + /** + * Calculate how many flex lines are needed in the flex container. + * This method should calculate all the flex lines from the existing flex items. + * + * @param widthMeasureSpec the width measure spec imposed by the flex container + * @param heightMeasureSpec the height measure spec imposed by the flex container + * @see #calculateVerticalFlexLines(int, int, int, int, List) + */ + FlexLinesResult calculateVerticalFlexLines(int widthMeasureSpec, int heightMeasureSpec) { + return calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec, Integer.MAX_VALUE, + 0, null); + } + + /** + * Calculate how many flex lines are needed in the flex container. + * Stop calculating it if the calculated amount along the cross size reaches the argument + * as the needsCalcAmount. + * + * @param widthMeasureSpec the width measure spec imposed by the flex container + * @param heightMeasureSpec the height measure spec imposed by the flex container + * @param needsCalcAmount the amount of pixels where flex line calculation should be stopped + * this is needed to avoid the expensive calculation if the + * calculation is needed only the small part of the entire flex + * container. (E.g. If the flex container is the + * {@link FlexboxLayoutManager}, the calculation only needs the + * visible area, imposing the entire calculation may cause bad + * performance + * @see #calculateVerticalFlexLines(int, int, int, int, List) + */ + FlexLinesResult calculateVerticalFlexLines(int widthMeasureSpec, int heightMeasureSpec, + int needsCalcAmount) { + return calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec, needsCalcAmount, + 0, null); + } + /** * Calculate how many flex lines are needed in the flex container layout by measuring each * child when the direction of the flex line is vertical (top to bottom or bottom to top). @@ -346,14 +438,30 @@ class FlexboxHelper { * * @param widthMeasureSpec the width measure spec imposed by the flex container * @param heightMeasureSpec the height measure spec imposed by the flex container + * @param needsCalcAmount the amount of pixels where flex line calculation should be stopped + * this is needed to avoid the expensive calculation if the + * calculation is needed only the small part of the entire flex + * container. (E.g. If the flex container is the + * {@link FlexboxLayoutManager}, the calculation only needs the + * visible area, imposing the entire calculation may cause bad + * performance + * @param existingLines If not null, calculated flex lines will be added to this instance * @return a instance of {@link FlexLinesResult} that contains a list of flex lines and the * child state used by {@link View#setMeasuredDimension(int, int)}. */ - FlexLinesResult calculateVerticalFlexLines(int widthMeasureSpec, int heightMeasureSpec) { + FlexLinesResult calculateVerticalFlexLines(int widthMeasureSpec, int heightMeasureSpec, + int needsCalcAmount, int fromIndex, @Nullable List existingLines) { int heightMode = View.MeasureSpec.getMode(heightMeasureSpec); int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); FlexLinesResult result = new FlexLinesResult(); - List flexLines = new ArrayList<>(); + List flexLines; + FlexLine flexLine = new FlexLine(); + if (existingLines == null) { + flexLines = new ArrayList<>(); + } else { + flexLines = existingLines; + } + result.mFlexLines = flexLines; int childCount = mFlexContainer.getFlexItemCount(); int childState = 0; @@ -361,19 +469,25 @@ class FlexboxHelper { int paddingTop = mFlexContainer.getPaddingTop(); int paddingBottom = mFlexContainer.getPaddingBottom(); int largestWidthInColumn = Integer.MIN_VALUE; - FlexLine flexLine = new FlexLine(); flexLine.mMainSize = paddingTop + paddingBottom; + + // The amount of cross size calculated in this method call + int sumCrossSize = 0; // The index of the view in a same flex line. int indexInFlexLine = 0; - for (int i = 0; i < childCount; i++) { + for (int i = fromIndex; i < childCount; i++) { View child = mFlexContainer.getReorderedFlexItemAt(i); if (child == null) { - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + } continue; } else if (child.getVisibility() == View.GONE) { flexLine.mGoneItemCount++; flexLine.mItemCount++; - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + } continue; } @@ -431,7 +545,7 @@ class FlexboxHelper { flexItem, i, indexInFlexLine)) { if (flexLine.getItemCountNotGone() > 0) { - addFlexLine(flexLines, flexLine, i - 1); + addFlexLine(flexLines, flexLine); } flexLine = new FlexLine(); @@ -444,6 +558,10 @@ class FlexboxHelper { flexLine.mItemCount++; indexInFlexLine++; } + if (mIndexToFlexLine != null) { + mIndexToFlexLine[i] = flexLines.size(); + } + flexLine.mMainSize += child.getMeasuredHeight() + flexItem.getMarginTop() + flexItem.getMarginBottom(); flexLine.mTotalFlexGrow += flexItem.getFlexGrow(); @@ -454,7 +572,21 @@ class FlexboxHelper { flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestWidthInColumn); mFlexContainer.onNewFlexItemAdded(i, indexInFlexLine, flexLine); - addFlexLineIfLastFlexItem(flexLines, i, childCount, flexLine); + if (isLastFlexItem(i, childCount, flexLine)) { + addFlexLine(flexLines, flexLine); + sumCrossSize += flexLine.mCrossSize; + } + + if (sumCrossSize > needsCalcAmount) { + // Stop the calculation if the sum of cross size calculated reached to the point + // beyond the needsCalcAmount value to avoid unneeded calculation in a + // RecyclerView. + // To be precise, the decoration length may be added to the sumCrossSize, + // but we omit adding the decoration length because even without the decorator + // length, it's guaranteed that calculation is done at least beyond the + // needsCalcAmount + break; + } } result.mChildState = childState; return result; @@ -492,21 +624,13 @@ class FlexboxHelper { return maxSize < currentLength + childLength; } - private void addFlexLineIfLastFlexItem(List flexLines, int childIndex, int childCount, + private boolean isLastFlexItem(int childIndex, int childCount, FlexLine flexLine) { - if (childIndex == childCount - 1 && flexLine.getItemCountNotGone() != 0) { - // Add the flex line if this item is the last item - addFlexLine(flexLines, flexLine, childIndex); - } + return childIndex == childCount - 1 && flexLine.getItemCountNotGone() != 0; } - private List addFlexLine(List flexLines, FlexLine flexLine, int index) { + private List addFlexLine(List flexLines, FlexLine flexLine) { mFlexContainer.onNewFlexLineAdded(flexLine); - if (mIndexToFlexLine == null) { - mIndexToFlexLine = new SparseIntArray(); - } - mIndexToFlexLine.append(index, flexLines.size()); - flexLines.add(flexLine); return flexLines; } @@ -551,6 +675,13 @@ class FlexboxHelper { } } + /** + * @see #determineMainSize(int, int, int) + */ + void determineMainSize(int widthMeasureSpec, int heightMeasureSpec) { + determineMainSize(widthMeasureSpec, heightMeasureSpec, 0); + } + /** * Determine the main size by expanding (shrinking if negative remaining free space is given) * an individual child in each flex line if any children's mFlexGrow (or mFlexShrink if @@ -562,7 +693,7 @@ class FlexboxHelper { * @see FlexContainer#setFlexDirection(int) * @see FlexContainer#getFlexDirection() */ - void determineMainSize(int widthMeasureSpec, int heightMeasureSpec) { + void determineMainSize(int widthMeasureSpec, int heightMeasureSpec, int fromIndex) { ensureChildrenFrozen(mFlexContainer.getFlexItemCount()); int mainSize; int paddingAlongMainAxis; @@ -596,14 +727,13 @@ class FlexboxHelper { throw new IllegalArgumentException("Invalid flex direction: " + flexDirection); } - int childIndex = 0; for (FlexLine flexLine : mFlexContainer.getFlexLinesInternal()) { if (flexLine.mMainSize < mainSize) { - childIndex = expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, - mainSize, paddingAlongMainAxis, childIndex); + fromIndex = expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, + mainSize, paddingAlongMainAxis, fromIndex); } else { - childIndex = shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, - mainSize, paddingAlongMainAxis, childIndex); + fromIndex = shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, + mainSize, paddingAlongMainAxis, fromIndex); } } } @@ -627,7 +757,7 @@ class FlexboxHelper { * @param flexLine the flex line to which flex items belong * @param maxMainSize the maximum main size. Expanded main size will be this size * @param paddingAlongMainAxis the padding value along the main axis - * @param startIndex the start index of the children views to be expanded. This index + * @param fromIndex the start index of the children views to be expanded. This index * needs to * be an absolute index in the flex container (FlexboxLayout), * not the relative index in the flex line. @@ -637,8 +767,8 @@ class FlexboxHelper { * @see FlexItem#getFlexGrow() */ private int expandFlexItems(int widthMeasureSpec, int heightMeasureSpec, FlexLine flexLine, - int maxMainSize, int paddingAlongMainAxis, int startIndex) { - int childIndex = startIndex; + int maxMainSize, int paddingAlongMainAxis, int fromIndex) { + int childIndex = fromIndex; if (flexLine.mTotalFlexGrow <= 0 || maxMainSize < flexLine.mMainSize) { childIndex += flexLine.mItemCount; return childIndex; @@ -695,7 +825,7 @@ class FlexboxHelper { // To adjust the flex line length to the size of maxMainSize, remaining // positive free space needs to be re-distributed to other flex items // (children views). In that case, invoke this method again with the same - // startIndex. + // fromIndex. needsReexpand = true; newWidth = flexItem.getMaxWidth(); mChildrenFrozen[childIndex] = true; @@ -751,7 +881,7 @@ class FlexboxHelper { // To adjust the flex line length to the size of maxMainSize, remaining // positive free space needs to be re-distributed to other flex items // (children views). In that case, invoke this method again with the same - // startIndex. + // fromIndex. needsReexpand = true; newHeight = flexItem.getMaxHeight(); mChildrenFrozen[childIndex] = true; @@ -785,10 +915,10 @@ class FlexboxHelper { } if (needsReexpand && sizeBeforeExpand != flexLine.mMainSize) { - // Re-invoke the method with the same startIndex to distribute the positive free space + // Re-invoke the method with the same fromIndex to distribute the positive free space // that wasn't fully distributed (because of maximum length constraint) expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, maxMainSize, - paddingAlongMainAxis, startIndex); + paddingAlongMainAxis, fromIndex); } return childIndex; } @@ -801,7 +931,7 @@ class FlexboxHelper { * @param flexLine the flex line to which flex items belong * @param maxMainSize the maximum main size. Shrank main size will be this size * @param paddingAlongMainAxis the padding value along the main axis - * @param startIndex the start index of the children views to be shrank. This index + * @param fromIndex the start index of the children views to be shrank. This index * needs to * be an absolute index in the flex container (FlexboxLayout), * not the relative index in the flex line. @@ -811,8 +941,8 @@ class FlexboxHelper { * @see FlexItem#getFlexShrink() */ private int shrinkFlexItems(int widthMeasureSpec, int heightMeasureSpec, FlexLine flexLine, - int maxMainSize, int paddingAlongMainAxis, int startIndex) { - int childIndex = startIndex; + int maxMainSize, int paddingAlongMainAxis, int fromIndex) { + int childIndex = fromIndex; int sizeBeforeShrink = flexLine.mMainSize; if (flexLine.mTotalFlexShrink <= 0 || maxMainSize > flexLine.mMainSize) { childIndex += flexLine.mItemCount; @@ -868,7 +998,7 @@ class FlexboxHelper { // free space. To adjust the flex line length down to the maxMainSize, remaining // negative free space needs to be re-distributed to other flex items // (children views). In that case, invoke this method again with the same - // startIndex. + // fromIndex. needsReshrink = true; newWidth = flexItem.getMinWidth(); mChildrenFrozen[childIndex] = true; @@ -953,10 +1083,10 @@ class FlexboxHelper { } if (needsReshrink && sizeBeforeShrink != flexLine.mMainSize) { - // Re-invoke the method with the same startIndex to distribute the negative free space + // Re-invoke the method with the same fromIndex to distribute the negative free space // that wasn't fully distributed (because some views length were not enough) shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, - maxMainSize, paddingAlongMainAxis, startIndex); + maxMainSize, paddingAlongMainAxis, fromIndex); } return childIndex; } @@ -993,7 +1123,6 @@ class FlexboxHelper { return childHeightMeasureSpec; } - /** * Determines the cross size (Calculate the length along the cross axis). * Expand the cross size only if the height mode is MeasureSpec.EXACTLY, otherwise @@ -1149,23 +1278,29 @@ class FlexboxHelper { } } + void stretchViews() { + stretchViews(0); + } + /** * Expand the view if the {@link FlexContainer#getAlignItems()} attribute is set to {@link * AlignItems#STRETCH} or {@link FlexboxLayout.LayoutParams#mAlignSelf} is set as * {@link AlignItems#STRETCH}. * + * @param fromIndex the index from which value, stretch is calculated * @see FlexContainer#getFlexDirection() * @see FlexContainer#setFlexDirection(int) * @see FlexContainer#getAlignItems() * @see FlexContainer#setAlignItems(int) * @see FlexboxLayout.LayoutParams#mAlignSelf */ - void stretchViews() { + void stretchViews(int fromIndex) { int flexDirection = mFlexContainer.getFlexDirection(); if (mFlexContainer.getAlignItems() == AlignItems.STRETCH) { - int viewIndex = 0; + int viewIndex = fromIndex; for (FlexLine flexLine : mFlexContainer.getFlexLinesInternal()) { - for (int i = 0; i < flexLine.mItemCount; i++, viewIndex++) { + for (int i = 0, itemCount = flexLine.mItemCount; i < itemCount; + i++, viewIndex++) { View view = mFlexContainer.getReorderedFlexItemAt(viewIndex); FlexItem flexItem = (FlexItem) view.getLayoutParams(); if (flexItem.getAlignSelf() != AlignSelf.AUTO && @@ -1404,7 +1539,8 @@ class FlexboxHelper { mMeasureSpecCache = new long[size < INITIAL_CAPACITY ? INITIAL_CAPACITY : size]; } else if (mMeasureSpecCache.length < size) { int newCapacity = mMeasureSpecCache.length * 2; - mMeasureSpecCache = new long[newCapacity >= size ? newCapacity : size]; + newCapacity = newCapacity >= size ? newCapacity : size; + mMeasureSpecCache = Arrays.copyOf(mMeasureSpecCache, newCapacity); } } @@ -1441,6 +1577,48 @@ class FlexboxHelper { return (long) heightMeasureSpec << 32 | widthMeasureSpec; } + void ensureIndexToFlexLine(int size) { + if (mIndexToFlexLine == null) { + mIndexToFlexLine = new int[size < INITIAL_CAPACITY ? INITIAL_CAPACITY : size]; + } else if (mIndexToFlexLine.length < size) { + int newCapacity = mIndexToFlexLine.length * 2; + newCapacity = newCapacity >= size ? newCapacity : size; + mIndexToFlexLine = Arrays.copyOf(mIndexToFlexLine, newCapacity); + } + } + + /** + * Clear the from flex lines and the caches from the index passed as an argument. + * + * @param flexLines the flex lines to be cleared + * @param fromFlexItem the index from which, flex lines are cleared + */ + void clearFlexLines(List flexLines, int fromFlexItem) { + assert mIndexToFlexLine != null; + assert mMeasureSpecCache != null; + + int fromFlexLine = mIndexToFlexLine[fromFlexItem]; + // Deleting from the last to avoid unneeded copy it happens when deleting the middle of the + // item in the ArrayList + for (int i = flexLines.size() - 1; i >= fromFlexLine; i--) { + flexLines.remove(i); + } + + int fillTo = mIndexToFlexLine.length - 1; + if (fromFlexItem > fillTo) { + Arrays.fill(mIndexToFlexLine, NO_POSITION); + } else { + Arrays.fill(mIndexToFlexLine, fromFlexItem, fillTo, NO_POSITION); + } + + fillTo = mMeasureSpecCache.length - 1; + if (fromFlexItem > fillTo) { + Arrays.fill(mMeasureSpecCache, 0); + } else { + Arrays.fill(mMeasureSpecCache, fromFlexItem, fillTo, 0); + } + } + /** * A class that is used for calculating the view order which view's indices and order * properties from Flexbox are taken into account. diff --git a/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayout.java b/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayout.java index dd5a0e7..05c1a21 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayout.java +++ b/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayout.java @@ -25,6 +25,7 @@ import android.os.Parcelable; import android.support.annotation.IntDef; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; +import android.util.SparseIntArray; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; @@ -172,6 +173,20 @@ public class FlexboxLayout extends ViewGroup implements FlexContainer { /** The width of the {@link #mDividerDrawableVertical}. */ private int mDividerVerticalWidth; + /** + * Holds reordered indices, which {@link FlexItem#getOrder()} parameters are taken + * into account + */ + private int[] mReorderedIndices; + + /** + * Caches the {@link FlexItem#getOrder()} attributes for children views. + * Key: the index of the view reordered indices using the {@link FlexItem#getOrder()} + * isn't taken into account) + * Value: the value for the order attribute + */ + private SparseIntArray mOrderCache; + private FlexboxHelper mFlexboxHelper = new FlexboxHelper(this); private List mFlexLines = new ArrayList<>(); @@ -233,8 +248,11 @@ public class FlexboxLayout extends ViewGroup implements FlexContainer { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mFlexboxHelper.isOrderChangedFromLastMeasurement()) { - mFlexboxHelper.mReorderedIndices = mFlexboxHelper.createReorderedIndices(); + if (mOrderCache == null) { + mOrderCache = new SparseIntArray(getChildCount()); + } + if (mFlexboxHelper.isOrderChangedFromLastMeasurement(mOrderCache)) { + mReorderedIndices = mFlexboxHelper.createReorderedIndices(mOrderCache); } // TODO: Only calculate the children views which are affected from the last measure. @@ -274,10 +292,10 @@ public class FlexboxLayout extends ViewGroup implements FlexContainer { * returns {@code null}. */ public View getReorderedChildAt(int index) { - if (index < 0 || index >= mFlexboxHelper.mReorderedIndices.length) { + if (index < 0 || index >= mReorderedIndices.length) { return null; } - return getChildAt(mFlexboxHelper.mReorderedIndices[index]); + return getChildAt(mReorderedIndices[index]); } @Override @@ -287,12 +305,15 @@ public class FlexboxLayout extends ViewGroup implements FlexContainer { @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (mOrderCache == null) { + mOrderCache = new SparseIntArray(getChildCount()); + } // Create an array for the reordered indices before the View is added in the parent // ViewGroup since otherwise reordered indices won't be in effect before the // FlexboxLayout's onMeasure is called. // Because requestLayout is requested in the super.addView method. - mFlexboxHelper.mReorderedIndices = mFlexboxHelper - .createReorderedIndices(child, index, params); + mReorderedIndices = mFlexboxHelper + .createReorderedIndices(child, index, params, mOrderCache); super.addView(child, index, params); } 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 ce435d3..1437e83 100644 --- a/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java +++ b/flexbox/src/main/java/com/google/android/flexbox/FlexboxLayoutManager.java @@ -217,6 +217,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements if (mFlexDirection != flexDirection) { mFlexDirection = flexDirection; mOrientationHelper = null; + mFlexLines.clear(); requestLayout(); } } @@ -232,6 +233,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements if (mFlexWrap != flexWrap) { mFlexWrap = flexWrap; mOrientationHelper = null; + mFlexLines.clear(); requestLayout(); } } @@ -260,6 +262,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements public void setAlignItems(@AlignItems int alignItems) { if (mAlignItems != alignItems) { mAlignItems = alignItems; + mFlexLines.clear(); requestLayout(); } } @@ -274,6 +277,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements public void setAlignContent(@AlignContent int alignContent) { if (mAlignContent != alignContent) { mAlignContent = alignContent; + mFlexLines.clear(); requestLayout(); } } @@ -337,19 +341,22 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements } /** - * Returns a View, which is reordered by taking {@link LayoutParams#mOrder} parameters - * into account. + * Returns a View for the given index. + * The order attribute ({@link FlexItem#getOrder()}) is not supported by this class since + * otherwise all view holders need to be inflated at least once even though only the visible + * part of the layout is needed. + * Implementing just to conform the {@link FlexContainer} interface. * * @param index the index of the view - * @return the reordered view, which {@link LayoutParams@mOrder} is taken into account. + * @return the view for the given index. * If the index is negative or out of bounds of the number of contained views, * returns {@code null}. */ public View getReorderedChildAt(int index) { - if (index < 0 || index >= mFlexboxHelper.mReorderedIndices.length) { + if (index < 0 || index >= mState.getItemCount()) { return null; } - return mRecycler.getViewForPosition(mFlexboxHelper.mReorderedIndices[index]); + return mRecycler.getViewForPosition(index); } @Override @@ -447,38 +454,67 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements ensureOrientationHelper(); ensureLayoutState(); mFlexboxHelper.ensureMeasureSpecCache(childCount); + mFlexboxHelper.ensureIndexToFlexLine(childCount); mLayoutState.mShouldRecycle = false; mAnchorInfo.reset(); updateAnchorInfoForLayout(state, mAnchorInfo); - // TODO: If we support the order attribute, we need to inflate the all ViewHolders in the - // adapter instead of inflating only the visible ViewHolders, which is inefficient given - // that this is part of RecyclerView - if (mFlexboxHelper.isOrderChangedFromLastMeasurement()) { - mFlexboxHelper.mReorderedIndices = mFlexboxHelper.createReorderedIndices(); - } + // Unlike the FlexboxLayout, the order attribute is not supported, we don't calculated the + // order attribute because preparing the order attribute requires all + // view holders to be inflated at least once, which is inefficient if the number of items + // is large + + resolveLayoutDirection(); + updateLayoutStateToFillEnd(mAnchorInfo); + // Calculate the flex lines until the calculated cross size reaches the + // LayoutState#mAvailable (or until the end of the flex container) //noinspection ResourceType int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), getWidthMode()); //noinspection ResourceType int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(getHeight(), getHeightMode()); FlexboxHelper.FlexLinesResult flexLinesResult; - mFlexLines.clear(); - - // TODO: Change the code to calculate only the visible area - int paddingAlongCrossAxis; if (isMainAxisDirectionHorizontal()) { - flexLinesResult = mFlexboxHelper - .calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec); - paddingAlongCrossAxis = getPaddingTop() + getPaddingBottom(); + if (mFlexLines.size() > 0) { + // Remove the already calculated flex lines from the anchor position and + // calculate beyond the available amount (visible area that needs to be filled) + mFlexboxHelper.clearFlexLines(mFlexLines, mAnchorInfo.mPosition); + flexLinesResult = mFlexboxHelper + .calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec, + mLayoutState.mAvailable, mAnchorInfo.mPosition, mFlexLines); + } else { + mFlexboxHelper.ensureIndexToFlexLine(childCount); + flexLinesResult = mFlexboxHelper + .calculateHorizontalFlexLines(widthMeasureSpec, heightMeasureSpec, + mLayoutState.mAvailable); + } } else { - flexLinesResult = mFlexboxHelper - .calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec); - paddingAlongCrossAxis = getPaddingLeft() + getPaddingRight(); + if (mFlexLines.size() > 0) { + // Remove the already calculated flex lines from the anchor position and + // calculate beyond the available amount (visible area that needs to be filled) + mFlexboxHelper.clearFlexLines(mFlexLines, mAnchorInfo.mPosition); + flexLinesResult = mFlexboxHelper + .calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec, + mLayoutState.mAvailable, mAnchorInfo.mPosition, mFlexLines); + } else { + mFlexboxHelper.ensureIndexToFlexLine(childCount); + flexLinesResult = mFlexboxHelper + .calculateVerticalFlexLines(widthMeasureSpec, heightMeasureSpec, + mLayoutState.mAvailable); + } } mFlexLines = flexLinesResult.mFlexLines; - mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec); + mFlexboxHelper.determineMainSize(widthMeasureSpec, heightMeasureSpec, + mAnchorInfo.mPosition); + // Unlike the FlexboxLayout not calling FlexboxHelper#determineCrossSize because + // the align content attribute (which is used to determine the cross size) is only effective + // when the size of flex line is equal or more than 2 and the parent height + // (length along the cross size) is fixed. But in RecyclerView, these two conditions can't + // be true at the same time. Because it's scrollable along the cross axis + // or even if not (when flex wrap is "nowrap") the size of the flex lines should be 1. + mFlexboxHelper.stretchViews(mAnchorInfo.mPosition); + if (DEBUG) { for (int i = 0, size = mFlexLines.size(); i < size; i++) { FlexLine flexLine = mFlexLines.get(i); @@ -487,14 +523,8 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements flexLine.getItemCount())); } } - mFlexboxHelper.determineCrossSize(widthMeasureSpec, heightMeasureSpec, - paddingAlongCrossAxis); - mFlexboxHelper.stretchViews(); detachAndScrapAttachedViews(recycler); - - resolveLayoutDirection(); - updateLayoutStateToFillEnd(mAnchorInfo); int filledToEnd = fill(recycler, state, mLayoutState); if (DEBUG) { Log.d(TAG, String.format("filled: %d toward end", filledToEnd)); @@ -1104,10 +1134,10 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements } /** - * @param delta the delta for the amount that is being scrolled - * (either horizontally or vertically) + * @param delta the delta for the amount that is being scrolled + * (either horizontally or vertically) * @param recycler the Recycler instance - * @param state the Recycler.State instance + * @param state the Recycler.State instance * @return the amount actually scrolled */ private int handleScrolling(int delta, RecyclerView.Recycler recycler, @@ -1134,37 +1164,59 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements } private void updateLayoutState(@LayoutDirection int layoutDirection, int absDelta) { + assert mFlexboxHelper.mIndexToFlexLine != null; // TODO: Consider updating LayoutState#mExtra to support better smooth scrolling mLayoutState.mLayoutDirection = layoutDirection; if (layoutDirection == LayoutDirection.END) { View lastVisible = getChildAt(getChildCount() - 1); mLayoutState.mItemDirection = ItemDirection.TAIL; mLayoutState.mPosition = getPosition(lastVisible) + mLayoutState.mItemDirection; - mLayoutState.mFlexLinePosition = - mFlexboxHelper.mIndexToFlexLine - .get(mLayoutState.mPosition, 0); - + if (mFlexboxHelper.mIndexToFlexLine.length <= mLayoutState.mPosition) { + mLayoutState.mFlexLinePosition = NO_POSITION; + } else { + mLayoutState.mFlexLinePosition + = mFlexboxHelper.mIndexToFlexLine[mLayoutState.mPosition]; + } mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(lastVisible); mLayoutState.mScrollingOffset = mOrientationHelper.getDecoratedEnd(lastVisible) - mOrientationHelper.getEndAfterPadding(); + + // If the RecyclerView tries to scroll beyond the already calculated + // flex container, need to calculate until the amount that needs to be filled + if ((mLayoutState.mFlexLinePosition == NO_POSITION + || mLayoutState.mFlexLinePosition > mFlexLines.size() - 1) && + mLayoutState.mPosition <= getFlexItemCount()) { + //noinspection ResourceType + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(getWidth(), getWidthMode()); + //noinspection ResourceType + int heightMeasureSpec = View.MeasureSpec + .makeMeasureSpec(getHeight(), getHeightMode()); + int needsToFill = absDelta - mLayoutState.mScrollingOffset; + if (needsToFill > 0) { + mFlexboxHelper.calculateHorizontalFlexLines( + widthMeasureSpec, heightMeasureSpec, needsToFill, + mLayoutState.mPosition, mFlexLines); + } + } } else { View firstVisible = getChildAt(0); mLayoutState.mItemDirection = ItemDirection.TAIL; int position = getPosition(firstVisible); - FlexLine currentLine = mFlexLines.get(mFlexboxHelper.mIndexToFlexLine.get(position, 0)); + int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[position]; + if (flexLinePosition == NO_POSITION) { + flexLinePosition = 0; + } + FlexLine currentLine = mFlexLines.get(flexLinePosition); // The position of the next item toward start should be on the next flex line, // shifting the position by the number of the items in the current line. mLayoutState.mPosition = position - currentLine.getItemCount(); - mLayoutState.mFlexLinePosition = - mFlexboxHelper.mIndexToFlexLine - .get(mLayoutState.mPosition, 0); + mLayoutState.mFlexLinePosition = flexLinePosition; mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(firstVisible); mLayoutState.mScrollingOffset = -mOrientationHelper.getDecoratedStart(firstVisible) + mOrientationHelper.getStartAfterPadding(); } - mLayoutState.mAvailable = absDelta; - mLayoutState.mAvailable -= mLayoutState.mScrollingOffset; + mLayoutState.mAvailable = absDelta - mLayoutState.mScrollingOffset; } /** @@ -1211,11 +1263,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements */ public static class LayoutParams extends RecyclerView.LayoutParams implements FlexItem { - /** - * @see FlexItem#getOrder() - */ - private int mOrder = FlexItem.ORDER_DEFAULT; - /** * @see FlexItem#getFlexGrow() */ @@ -1415,7 +1462,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements public LayoutParams(LayoutParams source) { super(source); - mOrder = source.mOrder; mFlexGrow = source.mFlexGrow; mFlexShrink = source.mFlexShrink; mAlignSelf = source.mAlignSelf; @@ -1429,12 +1475,14 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements @Override public int getOrder() { - return mOrder; + return FlexItem.ORDER_DEFAULT; } @Override public void setOrder(int order) { - mOrder = order; + Log.w(TAG, "Setting the order in the " + + "FlexboxLayoutManager is not supported(ignored). Use FlexboxLayout " + + "if you need to reorder using the attribute."); } @Override @@ -1444,7 +1492,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(this.mOrder); dest.writeFloat(this.mFlexGrow); dest.writeFloat(this.mFlexShrink); dest.writeInt(this.mAlignSelf); @@ -1464,7 +1511,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements protected LayoutParams(Parcel in) { super(WRAP_CONTENT, WRAP_CONTENT); - this.mOrder = in.readInt(); this.mFlexGrow = in.readFloat(); this.mFlexShrink = in.readFloat(); this.mAlignSelf = in.readInt(); @@ -1541,8 +1587,34 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements } else { mCoordinate = mOrientationHelper.getDecoratedStart(view); } - mPosition = getPosition(view); - mFlexLinePosition = mFlexboxHelper.mIndexToFlexLine.get(mPosition, 0); + int position = getPosition(view); + // It's likely that the view is the first item in a flex line, but if not get the + // index of the first item in the same line because the calculation of the flex lines + // expects that it starts from the first item in a flex line + mPosition = getFirstItemIndexInLine(position); + assert mFlexboxHelper.mIndexToFlexLine != null; + int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[mPosition]; + mFlexLinePosition = flexLinePosition != NO_POSITION ? flexLinePosition : 0; + } + + /** + * @param index the index in which the flex is determined + * @return the index of the flex item that is the first item in the same line + */ + private int getFirstItemIndexInLine(int index) { + assert mFlexboxHelper.mIndexToFlexLine != null; + int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[index]; + if (index == 0 || mFlexboxHelper.mIndexToFlexLine[index - 1] != flexLinePosition) { + return index; + } + for (int i = index - 1; i > 0; i--) { + int first = mFlexboxHelper.mIndexToFlexLine[i]; + int second = mFlexboxHelper.mIndexToFlexLine[i - 1]; + if (first != second) { + return first; + } + } + return 0; } @Override -- GitLab