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 287edbb503863b9a3644bb57b4e473777755a4ff..1afe08c55c40bd587d6043ee62dbe5d810dae812 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 a902a028f9b2ec9fd94e4e615ba9d1403091730b..6f77003c47bb809195cf2a489fb936786cda5cfa 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 891280793f4be4532ce2ac39576ca8f7994b2a08..37330bf57606a9f739507d0ff3f01224630ae706 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 66743cd74a9eadfe7a5423c770ec1ac371b118d8..eda2f3e85583c8fe57eaec3ecfc834c656c9482c 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 dd5a0e731131a7143fbd9dbfa793e856c0bdbc9e..05c1a212400723b1063f0f7fde78d559e48e4878 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 ce435d30d907f0aef67a299ae97bba7a57f8cde4..1437e83b9889ab69b79ab78b3e2dd9a17095045d 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