10a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar/* 20a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * Copyright (C) 2015 The Android Open Source Project 30a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * 40a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 50a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * you may not use this file except in compliance with the License. 60a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * You may obtain a copy of the License at 70a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * 80a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 90a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * 100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * Unless required by applicable law or agreed to in writing, software 110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * See the License for the specific language governing permissions and 140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * limitations under the License. 150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar */ 160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpackage android.support.v7.widget; 180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Test; 210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.runner.RunWith; 220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.runners.Parameterized; 230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.graphics.Rect; 250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.os.Looper; 260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.os.Parcel; 270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.os.Parcelable; 284510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport android.support.v4.view.ViewCompat; 290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.test.suitebuilder.annotation.MediumTest; 300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.util.Log; 310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.view.View; 324510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport android.view.ViewParent; 330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.Arrays; 350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.BitSet; 360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.List; 370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.Map; 380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.UUID; 390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START; 410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL; 420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL; 434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.hasItem; 454510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.is; 464510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.not; 474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.sameInstance; 480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertEquals; 490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertNotNull; 504510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.junit.Assert.assertThat; 510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertTrue; 520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar@RunWith(Parameterized.class) 540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar@MediumTest 550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpublic class StaggeredGridLayoutManagerBaseConfigSetTest 560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar extends BaseStaggeredGridLayoutManagerTest { 570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Parameterized.Parameters(name = "{0}") 590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public static List<Config> getParams() { 600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar return createBaseVariations(); 610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar private final Config mConfig; 640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public StaggeredGridLayoutManagerBaseConfigSetTest(Config config) 660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar throws CloneNotSupportedException { 670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig = (Config) config.clone(); 680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void rTL() throws Throwable { 724143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar rtlTest(false, false); 730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void rTLChangeAfter() throws Throwable { 774143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar rtlTest(true, false); 780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 804143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar @Test 814143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar public void rTLItemWrapContent() throws Throwable { 824143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar rtlTest(false, true); 834143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 844143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar 854143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar @Test 864143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar public void rTLChangeAfterItemWrapContent() throws Throwable { 874143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar rtlTest(true, true); 884143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 894143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar 904143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar void rtlTest(boolean changeRtlAfter, final boolean wrapContent) throws Throwable { 910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mSpanCount == 1) { 920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig.mSpanCount = 2; 930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar String logPrefix = mConfig + ", changeRtlAfterLayout:" + changeRtlAfter; 954143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar setupByConfig(mConfig.itemCount(5), 964143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar new GridTestAdapter(mConfig.mItemCount, mConfig.mOrientation) { 974143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar @Override 984143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar public void onBindViewHolder(TestViewHolder holder, 994143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar int position) { 1004143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar super.onBindViewHolder(holder, position); 1014143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar if (wrapContent) { 1024143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar if (mOrientation == HORIZONTAL) { 1034143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar holder.itemView.getLayoutParams().height 1044143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar = RecyclerView.LayoutParams.WRAP_CONTENT; 1054143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } else { 1064143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar holder.itemView.getLayoutParams().width 1074143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar = RecyclerView.LayoutParams.MATCH_PARENT; 1084143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 1094143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 1104143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 1114143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar }); 1120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (changeRtlAfter) { 1130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 1140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 1150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.setFakeRtl(true); 1160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 1170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 1180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mFakeRTL = true; 1190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 1200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 1220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals("view should become rtl", true, mLayoutManager.isLayoutRTL()); 1230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager); 1240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View child0 = mLayoutManager.findViewByPosition(0); 1250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View child1 = mLayoutManager.findViewByPosition(mConfig.mOrientation == VERTICAL ? 1 1260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar : mConfig.mSpanCount); 1270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertNotNull(logPrefix + " child position 0 should be laid out", child0); 1280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertNotNull(logPrefix + " child position 0 should be laid out", child1); 1290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar logPrefix += " child1 pos:" + mLayoutManager.getPosition(child1); 1300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mOrientation == VERTICAL || !mConfig.mReverseLayout) { 1310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(logPrefix + " second child should be to the left of first child", 1320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar helper.getDecoratedEnd(child0) > helper.getDecoratedEnd(child1)); 1330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(logPrefix + " first child should be right aligned", 1340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar helper.getDecoratedEnd(child0), helper.getEndAfterPadding()); 1350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 1360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(logPrefix + " first child should be to the left of second child", 1370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar helper.getDecoratedStart(child1) >= helper.getDecoratedStart(child0)); 1380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(logPrefix + " first child should be left aligned", 1390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar helper.getDecoratedStart(child0), helper.getStartAfterPadding()); 1400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 1420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 1440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollBackAndPreservePositions() throws Throwable { 1460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBackAndPreservePositionsTest(false); 1470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 1490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollBackAndPreservePositionsWithRestore() throws Throwable { 1510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBackAndPreservePositionsTest(true); 1520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 1540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollBackAndPreservePositionsTest(final boolean saveRestoreInBetween) 1550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar throws Throwable { 1560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 1570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 1580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 1590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void onBoundItem(TestViewHolder vh, int position) { 1600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar StaggeredGridLayoutManager.LayoutParams 1610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar lp = (StaggeredGridLayoutManager.LayoutParams) vh.itemView 1620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getLayoutParams(); 1630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar lp.setFullSpan((position * 7) % (mConfig.mSpanCount + 1) == 0); 1640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }; 1660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 1670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int[] globalPositions = new int[mAdapter.getItemCount()]; 1680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Arrays.fill(globalPositions, Integer.MIN_VALUE); 1690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10) 1700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * (mConfig.mReverseLayout ? -1 : 1); 1710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 1720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int[] globalPos = new int[1]; 1730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 1740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 1750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 1760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int globalScrollPosition = 0; 1770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) { 1780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (int i = 0; i < mRecyclerView.getChildCount(); i++) { 1790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View child = mRecyclerView.getChildAt(i); 1800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int pos = mRecyclerView.getChildLayoutPosition(child); 1810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (globalPositions[pos] != Integer.MIN_VALUE) { 1820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar continue; 1830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mReverseLayout) { 1850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPositions[pos] = globalScrollPosition + 1860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child); 1870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 1880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPositions[pos] = globalScrollPosition + 1890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(child); 1900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalScrollPosition += mLayoutManager.scrollBy(scrollStep, 1930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.mRecycler, mRecyclerView.mState); 1940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 1960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions)); 1970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 1980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPos[0] = globalScrollPosition; 1990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 2010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 2020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (saveRestoreInBetween) { 2040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar saveRestore(mConfig); 2050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 2080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 2090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 2100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 2110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int globalScrollPosition = globalPos[0]; 2120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // now scroll back and make sure global positions match 2130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar BitSet shouldTest = new BitSet(mAdapter.getItemCount()); 2140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar shouldTest.set(0, mAdapter.getItemCount() - 1, true); 2150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar String assertPrefix = mConfig + ", restored in between:" + saveRestoreInBetween 2160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " global pos must match when scrolling in reverse for position "; 2170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int scrollAmount = Integer.MAX_VALUE; 2180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (!shouldTest.isEmpty() && scrollAmount != 0) { 2190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (int i = 0; i < mRecyclerView.getChildCount(); i++) { 2200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View child = mRecyclerView.getChildAt(i); 2210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int pos = mRecyclerView.getChildLayoutPosition(child); 2220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (!shouldTest.get(pos)) { 2230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar continue; 2240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar shouldTest.clear(pos); 2260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int globalPos; 2270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mReverseLayout) { 2280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPos = globalScrollPosition + 2290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child); 2300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 2310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPos = globalScrollPosition + 2320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(child); 2330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(assertPrefix + pos, 2350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalPositions[pos], globalPos); 2360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollAmount = mLayoutManager.scrollBy(-scrollStep, 2380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.mRecycler, mRecyclerView.mState); 2390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar globalScrollPosition += scrollAmount; 2400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue("all views should be seen", shouldTest.isEmpty()); 2420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 2440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 2450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar private void saveRestore(final Config config) throws Throwable { 2480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 2490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 2500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 2510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar try { 2520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Parcelable savedState = mRecyclerView.onSaveInstanceState(); 2530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // we append a suffix to the parcelable to test out of bounds 2540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar String parcelSuffix = UUID.randomUUID().toString(); 2550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Parcel parcel = Parcel.obtain(); 2560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar savedState.writeToParcel(parcel, 0); 2570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar parcel.writeString(parcelSuffix); 2580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar removeRecyclerView(); 2590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // reset for reading 2600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar parcel.setDataPosition(0); 2610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // re-create 2620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); 2630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar RecyclerView restored = new RecyclerView(getActivity()); 2640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager = new WrappedLayoutManager(config.mSpanCount, 2650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar config.mOrientation); 2660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.setGapStrategy(config.mGapStrategy); 2670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar restored.setLayoutManager(mLayoutManager); 2680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // use the same adapter for Rect matching 2690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar restored.setAdapter(mAdapter); 2700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar restored.onRestoreInstanceState(savedState); 2710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (Looper.myLooper() == Looper.getMainLooper()) { 2720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 2730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setRecyclerView(restored); 2740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 2750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 2760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setRecyclerView(restored); 2770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 2780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } catch (Throwable t) { 2800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar postExceptionToInstrumentation(t); 2810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 2840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 2850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void getFirstLastChildrenTest() throws Throwable { 2890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar getFirstLastChildrenTest(false); 2900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void getFirstLastChildrenTestProvideArray() throws Throwable { 2940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar getFirstLastChildrenTest(true); 2950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 2960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 2970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void getFirstLastChildrenTest(final boolean provideArr) throws Throwable { 2980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 2990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 3000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Runnable viewInBoundsTest = new Runnable() { 3010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 3020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 3030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren(); 3040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final String boundsLog = mLayoutManager.getBoundsLog(); 3050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar VisibleChildren queryResult = new VisibleChildren(mLayoutManager.getSpanCount()); 3060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.findFirstPartialVisibleClosestToStart = mLayoutManager 307e5874e666791a58d21eed482fb90e917445da873Chris Craik .findFirstVisibleItemClosestToStart(false); 3080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.findFirstPartialVisibleClosestToEnd = mLayoutManager 309e5874e666791a58d21eed482fb90e917445da873Chris Craik .findFirstVisibleItemClosestToEnd(false); 3100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.firstFullyVisiblePositions = mLayoutManager 3110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .findFirstCompletelyVisibleItemPositions( 3120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar provideArr ? new int[mLayoutManager.getSpanCount()] : null); 3130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.firstVisiblePositions = mLayoutManager 3140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .findFirstVisibleItemPositions( 3150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar provideArr ? new int[mLayoutManager.getSpanCount()] : null); 3160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.lastFullyVisiblePositions = mLayoutManager 3170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .findLastCompletelyVisibleItemPositions( 3180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar provideArr ? new int[mLayoutManager.getSpanCount()] : null); 3190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar queryResult.lastVisiblePositions = mLayoutManager 3200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .findLastVisibleItemPositions( 3210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar provideArr ? new int[mLayoutManager.getSpanCount()] : null); 3220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + ":\nfirst visible child should match traversal result\n" 3230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "traversed:" + visibleChildren + "\n" 3240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "queried:" + queryResult + "\n" 3250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + boundsLog, visibleChildren, queryResult 3260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 3270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }; 3290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(viewInBoundsTest); 3300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 3310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // case 3320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int scrollPosition = mAdapter.getItemCount(); 3330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 3340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 3350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 3360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.smoothScrollToPosition(scrollPosition); 3370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 3390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (mLayoutManager.isSmoothScrolling() || 3400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 3410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(viewInBoundsTest); 3420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 3430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Thread.sleep(400); 3440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // delete all items 3460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(2); 3470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mAdapter.deleteAndNotify(0, mAdapter.getItemCount()); 3480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 3490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // test empty case 3500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(viewInBoundsTest); 3510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // set a new adapter with huge items to test full bounds check 3520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 3530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int totalSpace = mLayoutManager.mPrimaryOrientation.getTotalSpace(); 3540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final TestAdapter newAdapter = new TestAdapter(100) { 3550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 3560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void onBindViewHolder(TestViewHolder holder, 3570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int position) { 3580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar super.onBindViewHolder(holder, position); 3590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mOrientation == LinearLayoutManager.HORIZONTAL) { 3600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar holder.itemView.setMinimumWidth(totalSpace + 100); 3610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 3620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar holder.itemView.setMinimumHeight(totalSpace + 100); 3630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }; 3660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 3670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 3680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 3690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.setAdapter(newAdapter); 3700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 3720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 3730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(viewInBoundsTest); 3740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 3750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 3760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 3770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // case 3780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 3790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 3800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 3810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int diff; 3820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mReverseLayout) { 3830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar diff = -1; 3840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 3850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar diff = 1; 3860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int distance = diff * 10; 3880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 3890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.scrollBy(distance, 0); 3900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 3910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mRecyclerView.scrollBy(0, distance); 3920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 3950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(viewInBoundsTest); 3960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar checkForMainThreadException(); 3970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 3980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 3990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 4000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void viewSnapTest() throws Throwable { 4010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(mConfig.mSpanCount + 1); 4020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(config); 4030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 4040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 4050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar void onBoundItem(TestViewHolder vh, int position) { 4060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar StaggeredGridLayoutManager.LayoutParams 4070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar lp = (StaggeredGridLayoutManager.LayoutParams) vh.itemView 4080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getLayoutParams(); 4090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mOrientation == HORIZONTAL) { 4100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar lp.width = mRecyclerView.getWidth() / 3; 4110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 4120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar lp.height = mRecyclerView.getHeight() / 3; 4130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 4160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 4170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar boolean assignRandomSize() { 4180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar return false; 4190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }; 4210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 4220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // run these tests twice. once initial layout, once after scroll 4230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar String logSuffix = ""; 4240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (int i = 0; i < 2; i++) { 4250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> itemRectMap = mLayoutManager.collectChildCoordinates(); 4260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect recyclerViewBounds = getDecoratedRecyclerViewBounds(); 4270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // workaround for SGLM's span distribution issue. Right now, it may leave gaps so we 4280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // avoid it by setting its layout params directly 4290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mOrientation == HORIZONTAL) { 4300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar recyclerViewBounds.bottom -= recyclerViewBounds.height() % config.mSpanCount; 4310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 4320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar recyclerViewBounds.right -= recyclerViewBounds.width() % config.mSpanCount; 4330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 4350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect usedLayoutBounds = new Rect(); 4360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (Rect rect : itemRectMap.values()) { 4370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.union(rect); 4380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 4400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 4410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, "testing view snapping (" + logSuffix + ") for config " + config); 4420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mOrientation == VERTICAL) { 4440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on left" + logSuffix, 4450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.left, recyclerViewBounds.left); 4460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on right" + logSuffix, 4470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.right, recyclerViewBounds.right); 4480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mReverseLayout) { 4490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on bottom" + logSuffix, 4500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.bottom, recyclerViewBounds.bottom); 4510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(config + " there should be some gap on top" + logSuffix, 4520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.top > recyclerViewBounds.top); 4530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 4540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on top" + logSuffix, 4550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.top, recyclerViewBounds.top); 4560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(config + " there should be some gap at the bottom" + logSuffix, 4570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.bottom < recyclerViewBounds.bottom); 4580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 4600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on top" + logSuffix, 4610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.top, recyclerViewBounds.top); 4620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap at the bottom" + logSuffix, 4630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.bottom, recyclerViewBounds.bottom); 4640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mReverseLayout) { 4650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no on right" + logSuffix, 4660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.right, recyclerViewBounds.right); 4670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(config + " there should be some gap on left" + logSuffix, 4680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.left > recyclerViewBounds.left); 4690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 4700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(config + " there should be no gap on left" + logSuffix, 4710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.left, recyclerViewBounds.left); 4720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(config + " there should be some gap on right" + logSuffix, 4730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar usedLayoutBounds.right < recyclerViewBounds.right); 4740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int scroll = config.mReverseLayout ? -500 : 500; 4770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBy(scroll); 4780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar logSuffix = " scrolled " + scroll; 4790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 4810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 4820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 4830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollToPositionWithOffsetTest() throws Throwable { 4840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 4850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 4860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 4870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .createOrientationHelper(mLayoutManager, mConfig.mOrientation); 4880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 4890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // try scrolling towards head, should not affect anything 4900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 4910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollToPositionWithOffset(0, 20); 4920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertRectSetsEqual(mConfig + " trying to over scroll with offset should be no-op", 4930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar before, mLayoutManager.collectChildCoordinates()); 4940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // try offsetting some visible children 4950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int testCount = 10; 4960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (testCount-- > 0) { 4970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // get middle child 4980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2); 4990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(child); 5000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int startOffset = mConfig.mReverseLayout ? 5010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 5020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getDecoratedEnd(child) 5030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 5040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getStartAfterPadding(); 5050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int scrollOffset = startOffset / 2; 5060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 5070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollToPositionWithOffset(position, scrollOffset); 5080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 5090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int finalOffset = mConfig.mReverseLayout ? 5100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 5110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getDecoratedEnd(child) 5120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 5130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getStartAfterPadding(); 5140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + " scroll with offset on a visible child should work fine", 5150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollOffset, finalOffset); 5160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 5180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // try scrolling to invisible children 5190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar testCount = 10; 5200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // we test above and below, one by one 5210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int offsetMultiplier = -1; 5220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (testCount-- > 0) { 5230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final TargetTuple target = findInvisibleTarget(mConfig); 5240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 5250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int offset = offsetMultiplier 5260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3; 5270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollToPositionWithOffset(target.mPosition, offset); 5280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 5290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 5300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertNotNull(mConfig + " scrolling to a mPosition with offset " + offset 5310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " should layout it", child); 5320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 5330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 5340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, mConfig + " post scroll to invisible mPosition " + bounds + " in " 5350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + layoutBounds + " with offset " + offset); 5360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 5380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mReverseLayout) { 5390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + " when scrolling with offset to an invisible in reverse " 5400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "layout, its end should align with recycler view's end - offset", 5410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getEndAfterPadding() - offset, 5420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedEnd(child) 5430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 5440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 5450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + " when scrolling with offset to an invisible child in normal" 5460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " layout its start should align with recycler view's start + " 5470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "offset", 5480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getStartAfterPadding() + offset, 5490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedStart(child) 5500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 5510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar offsetMultiplier *= -1; 5530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 5560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 5570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollToPositionTest() throws Throwable { 5580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 5590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 5600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 5610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .createOrientationHelper(mLayoutManager, mConfig.mOrientation); 5620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 5630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 5640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View view = mLayoutManager.getChildAt(i); 5650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect bounds = mLayoutManager.getViewBounds(view); 5660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (layoutBounds.contains(bounds)) { 5670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates(); 5680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(view); 5690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar StaggeredGridLayoutManager.LayoutParams layoutParams 5700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar = (StaggeredGridLayoutManager.LayoutParams) (view.getLayoutParams()); 5710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder; 5720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals("recycler view mPosition should match adapter mPosition", position, 5730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar vh.mBoundItem.mAdapterIndex); 5740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 5750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, "testing scroll to visible mPosition at " + position 5760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " " + bounds + " inside " + layoutBounds); 5770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 5790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollToPosition(position); 5800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 5810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 5820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar view = mLayoutManager.findViewByPosition(position); 5830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect newBounds = mLayoutManager.getViewBounds(view); 5840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, "after scrolling to visible mPosition " + 5850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar bounds + " equals " + newBounds); 5860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 5870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 5880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertRectSetsEqual( 5890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig + "scroll to mPosition on fully visible child should be no-op", 5900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar initialBounds, mLayoutManager.collectChildCoordinates()); 5910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 5920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(view); 5930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 5940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, 5950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar "child(" + position + ") not fully visible " + bounds + " not inside " 5960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + layoutBounds 5970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + mRecyclerView.getChildLayoutPosition(view) 5980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 5990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 6010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar runTestOnUiThread(new Runnable() { 6020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 6030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 6040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.scrollToPosition(position); 6050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 6070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 6080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar view = mLayoutManager.findViewByPosition(position); 6090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar bounds = mLayoutManager.getViewBounds(view); 6100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 6110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, "after scroll to partially visible child " + bounds + " in " 6120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + layoutBounds); 6130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(mConfig 6150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " after scrolling to a partially visible child, it should become fully " 6160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " visible. " + bounds + " not inside " + layoutBounds, 6170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar layoutBounds.contains(bounds) 6180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 6190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue( 6200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig + " when scrolling to a partially visible item, one of its edges " 6210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "should be on the boundaries", 6220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getStartAfterPadding() == 6230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedStart(view) 6240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar || orientationHelper.getEndAfterPadding() == 6250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedEnd(view)); 6260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 6290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // try scrolling to invisible children 6300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int testCount = 10; 6310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar while (testCount-- > 0) { 6320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final TargetTuple target = findInvisibleTarget(mConfig); 6330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 6340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollToPosition(target.mPosition); 6350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 6360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 6370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertNotNull(mConfig + " scrolling to a mPosition should lay it out", child); 6380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 6390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (DEBUG) { 6400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Log.d(TAG, mConfig + " post scroll to invisible mPosition " + bounds + " in " 6410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + layoutBounds); 6420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue(mConfig + " scrolling to a mPosition should make it fully visible", 6440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar layoutBounds.contains(bounds)); 6450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (target.mLayoutDirection == LAYOUT_START) { 6460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals( 6470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig + " when scrolling to an invisible child above, its start should" 6480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + " align with recycler view's start", 6490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getStartAfterPadding(), 6500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedStart(child) 6510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 6520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 6530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + " when scrolling to an invisible child below, its end " 6540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "should align with recycler view's end", 6550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getEndAfterPadding(), 6560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar orientationHelper.getDecoratedEnd(child) 6570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 6580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 6620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scollByTest() throws Throwable { 6640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 6650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 6660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // try invalid scroll. should not happen 6670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final View first = mLayoutManager.getChildAt(0); 6680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar OrientationHelper primaryOrientation = OrientationHelper 6690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .createOrientationHelper(mLayoutManager, mConfig.mOrientation); 6700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int scrollDist; 6710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mReverseLayout) { 6720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollDist = primaryOrientation.getDecoratedMeasurement(first) / 2; 6730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 6740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollDist = -primaryOrientation.getDecoratedMeasurement(first) / 2; 6750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 6770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBy(scrollDist); 6780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> after = mLayoutManager.collectChildCoordinates(); 6790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertRectSetsEqual( 6800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig + " if there are no more items, scroll should not happen (dt:" + scrollDist 6810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + ")", 6820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar before, after 6830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 6840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 6850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollDist = -scrollDist * 3; 6860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar before = mLayoutManager.collectChildCoordinates(); 6870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBy(scrollDist); 6880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar after = mLayoutManager.collectChildCoordinates(); 6890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int layoutStart = primaryOrientation.getStartAfterPadding(); 6900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int layoutEnd = primaryOrientation.getEndAfterPadding(); 6910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 6920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Rect afterRect = after.get(entry.getKey()); 6930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // offset rect 6940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mOrientation == VERTICAL) { 6950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar entry.getValue().offset(0, -scrollDist); 6960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 6970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar entry.getValue().offset(-scrollDist, 0); 6980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 6990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (afterRect == null || afterRect.isEmpty()) { 7000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // assert item is out of bounds 7010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int start, end; 7020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (mConfig.mOrientation == VERTICAL) { 7030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar start = entry.getValue().top; 7040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar end = entry.getValue().bottom; 7050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 7060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar start = entry.getValue().left; 7070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar end = entry.getValue().right; 7080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue( 7100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mConfig + " if item is missing after relayout, it should be out of bounds." 7110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + "item start: " + start + ", end:" + end + " layout start:" 7120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar + layoutStart + 7130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ", layout end:" + layoutEnd, 7140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar start <= layoutStart && end <= layoutEnd || 7150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar start >= layoutEnd && end >= layoutEnd 7160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar ); 7170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } else { 7180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals(mConfig + " Item should be laid out at the scroll offset coordinates", 7190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar entry.getValue(), 7200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar afterRect); 7210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertViewPositions(mConfig); 7240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 7260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 7270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void layoutOrderTest() throws Throwable { 7280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(mConfig); 7290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertViewPositions(mConfig); 7300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 7320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 7330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void consistentRelayout() throws Throwable { 7340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar consistentRelayoutTest(mConfig, false); 7350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 7370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 7380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void consistentRelayoutWithFullSpanFirstChild() throws Throwable { 7390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar consistentRelayoutTest(mConfig, true); 7400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 7410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 7424510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 7434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable { 7444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 7454510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config); 7464510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar waitFirstLayout(); 7474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar // pick position from child count so that it is not too far away 7484510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int pos = mRecyclerView.getChildCount() * 2; 7494510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar smoothScrollToPosition(pos, true); 7504510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos); 7514510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mPrimaryOrientation; 7524510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getDecoratedStart(vh.itemView); 7534510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(gap); 7544510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getDecoratedStart(vh.itemView); 7554510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 7564510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 7574510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 7584510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 7594510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar runTestOnUiThread(new Runnable() { 7604510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 7614510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 7624510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 7634510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationX(vh.itemView, size * 2); 7644510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 7654510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationY(vh.itemView, size * 2); 7664510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 7674510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 7684510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 7694510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 7704510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 7714510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 7724510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(pos)); 7734510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 7744510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 7754510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 7764510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 7774510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 7784510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable { 7794510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 7804510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config); 7814510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar waitFirstLayout(); 7824510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar // pick position from child count so that it is not too far away 7834510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int pos = mRecyclerView.getChildCount() * 2; 7844510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.expectLayouts(1); 7854510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollToPosition(pos); 7864510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.waitForLayout(2); 7874510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos); 7884510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mPrimaryOrientation; 7894510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 7904510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-gap); 7914510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 7924510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 7934510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 7944510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 7954510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 7964510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar runTestOnUiThread(new Runnable() { 7974510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 7984510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 7994510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 8004510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationX(vh.itemView, -size * 2); 8014510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 8024510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationY(vh.itemView, -size * 2); 8034510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 8044510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 8054510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 8064510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 8074510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 8084510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 8094510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(pos)); 8104510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 8114510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 8124510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 8134510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 8140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void consistentRelayoutTest(Config config, boolean firstChildMultiSpan) 8150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar throws Throwable { 8160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(config); 8170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (firstChildMultiSpan) { 8180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mAdapter.mFullSpanItems.add(0); 8190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 8200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 8210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // record all child positions 8220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 8230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar requestLayoutOnUIThread(mRecyclerView); 8240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar Map<Item, Rect> after = mLayoutManager.collectChildCoordinates(); 8250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertRectSetsEqual( 8260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar config + " simple re-layout, firstChildMultiSpan:" + firstChildMultiSpan, before, 8270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar after); 8280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar // scroll some to create inconsistency 8290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar View firstChild = mLayoutManager.getChildAt(0); 8300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int firstChildStartBeforeScroll = mLayoutManager.mPrimaryOrientation 8310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getDecoratedStart(firstChild); 8320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar int distance = mLayoutManager.mPrimaryOrientation.getDecoratedMeasurement(firstChild) / 2; 8330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar if (config.mReverseLayout) { 8340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar distance *= -1; 8350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 8360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar scrollBy(distance); 8370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitForMainThread(2); 8380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue("scroll by should move children", firstChildStartBeforeScroll != 8390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(firstChild)); 8400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar before = mLayoutManager.collectChildCoordinates(); 8410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 8420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar requestLayoutOnUIThread(mRecyclerView); 8430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 8440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar after = mLayoutManager.collectChildCoordinates(); 8450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertRectSetsEqual(config + " simple re-layout after scroll", before, after); 8460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 8470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar} 848