1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project 3999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 4999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * you may not use this file except in compliance with the License. 6999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * You may obtain a copy of the License at 7999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 8999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 10999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * See the License for the specific language governing permissions and 14999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * limitations under the License. 15999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */ 16999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget; 18999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.LayoutState.LAYOUT_END; 20ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.LayoutState.LAYOUT_START; 21ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL; 224510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 234510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.hasItem; 244510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.is; 254510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.not; 264510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.sameInstance; 274143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static org.junit.Assert.assertEquals; 284143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static org.junit.Assert.assertNotNull; 294510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.junit.Assert.assertThat; 304510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 318a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport android.graphics.Rect; 3235232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikasimport android.support.test.filters.LargeTest; 338a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport android.util.Log; 348a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport android.view.View; 358a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport android.view.ViewParent; 368a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikas 37c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull; 38c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 398a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport org.junit.Test; 408a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport org.junit.runner.RunWith; 418a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport org.junit.runners.Parameterized; 428a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikas 438a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport java.util.ArrayList; 448a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport java.util.List; 458a50bd8370f166d81f858bc03a6cdc0ae5a4f345Aurimas Liutikasimport java.util.Map; 46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/** 48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Tests that rely on the basic configuration and does not do any additions / removals 49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */ 50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar@RunWith(Parameterized.class) 5135232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikas@LargeTest 52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManagerTest { 53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private final Config mConfig; 55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 56999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public LinearLayoutManagerBaseConfigSetTest(Config config) { 57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig = config; 58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 59999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 60999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Parameterized.Parameters(name = "{0}") 62999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public static List<Config> configs() throws CloneNotSupportedException { 634143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar List<Config> result = new ArrayList<>(); 644143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar for (Config config : createBaseVariations()) { 654143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar result.add(config); 664143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 674143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar return result; 68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Test 71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void scrollToPositionWithOffsetTest() throws Throwable { 72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config config = ((Config) mConfig.clone()).itemCount(300); 730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(config, true); 74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .createOrientationHelper(mLayoutManager, config.mOrientation); 76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try scrolling towards head, should not affect anything 78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mStackFromEnd) { 80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1, 81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.mOrientationHelper.getEnd() - 500); 82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(0, 20); 84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsEqual(config + " trying to over scroll with offset should be no-op", 86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar before, mLayoutManager.collectChildCoordinates()); 87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try offsetting some visible children 88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int testCount = 10; 89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (testCount-- > 0) { 90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // get middle child 91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2); 92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(child); 93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int startOffset = config.mReverseLayout ? 94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getDecoratedEnd(child) 96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 97999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getStartAfterPadding(); 98999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2 99999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : startOffset / 2; 100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(position, scrollOffset); 102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int finalOffset = config.mReverseLayout ? 104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getDecoratedEnd(child) 106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getStartAfterPadding(); 108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + " scroll with offset on a visible child should work fine " + 109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar " offset:" + finalOffset + " , existing offset:" + startOffset + ", " 110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "child " + position, 111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollOffset, finalOffset); 112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try scrolling to invisible children 115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar testCount = 10; 116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // we test above and below, one by one 117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int offsetMultiplier = -1; 118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (testCount-- > 0) { 119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final TargetTuple target = findInvisibleTarget(config); 120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final String logPrefix = config + " " + target; 121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int offset = offsetMultiplier 123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3; 124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(target.mPosition, offset); 125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset 128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + " should layout it", child); 129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in " 132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + layoutBounds + " with offset " + offset); 133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mReverseLayout) { 136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse " 137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "layout, its end should align with recycler view's end - offset", 138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - offset, 139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getDecoratedEnd(child) 140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar logPrefix + " when scrolling with offset to an invisible child in normal" 144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + " layout its start should align with recycler view's start + " 145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "offset", 146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getStartAfterPadding() + offset, 147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getDecoratedStart(child) 148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar offsetMultiplier *= -1; 151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Test 155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void getFirstLastChildrenTest() throws Throwable { 156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(300); 157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setupByConfig(config, true); 158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Runnable viewInBoundsTest = new Runnable() { 159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren(); 162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final String boundsLog = mLayoutManager.getBoundsLog(); 163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + ":\nfirst visible child should match traversal result\n" 164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.firstVisiblePosition, 165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findFirstVisibleItemPosition() 166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + ":\nfirst fully visible child should match traversal result\n" 169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.firstFullyVisiblePosition, 170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition() 171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + ":\nlast visible child should match traversal result\n" 174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.lastVisiblePosition, 175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findLastVisibleItemPosition() 176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + ":\nlast fully visible child should match traversal result\n" 179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.lastFullyVisiblePosition, 180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 18442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(viewInBoundsTest); 185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // case 187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount(); 18842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.smoothScrollToPosition(scrollPosition); 192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (mLayoutManager.isSmoothScrolling() || 195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 19642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(viewInBoundsTest); 197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Thread.sleep(400); 198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // delete all items 200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(2); 201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount()); 202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // test empty case 20442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(viewInBoundsTest); 205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // set a new adapter with huge items to test full bounds check 206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace(); 208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final TestAdapter newAdapter = new TestAdapter(100) { 209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 2108a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int position) { 212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.onBindViewHolder(holder, position); 213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mOrientation == HORIZONTAL) { 214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar holder.itemView.setMinimumWidth(totalSpace + 5); 215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar holder.itemView.setMinimumHeight(totalSpace + 5); 217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 22042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.setAdapter(newAdapter); 224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 22742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(viewInBoundsTest); 228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 2304510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 2314510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable { 2324510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 2334510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config, true); 2344510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.expectLayouts(1); 2354510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollToPosition(500); 2364510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.waitForLayout(2); 2374510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500); 2384510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mOrientationHelper; 2394510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getDecoratedStart(vh.itemView); 2404510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(gap); 2414510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getDecoratedStart(vh.itemView); 2424510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 2434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 2454510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 24642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 2474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 2484510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 2494510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 250ca03208c6ef5bd79af99309d0e14db4a238cb691Aurimas Liutikas vh.itemView.setTranslationX(size * 2); 2514510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 252ca03208c6ef5bd79af99309d0e14db4a238cb691Aurimas Liutikas vh.itemView.setTranslationY(size * 2); 2534510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2544510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2554510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 2564510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 2574510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 2584510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 2594510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(500)); 2604510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 2614510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 2624510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2634510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2644510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 2654510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable { 2664510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 2674510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config, true); 2684510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.expectLayouts(1); 2694510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollToPosition(500); 2704510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.waitForLayout(2); 2714510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500); 2724510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mOrientationHelper; 2734510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 2744510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-gap); 2754510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 2764510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 2774510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2784510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 2794510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 28042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 2814510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 2824510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 2834510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 284ca03208c6ef5bd79af99309d0e14db4a238cb691Aurimas Liutikas vh.itemView.setTranslationX(-size * 2); 2854510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 286ca03208c6ef5bd79af99309d0e14db4a238cb691Aurimas Liutikas vh.itemView.setTranslationY(-size * 2); 2874510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2884510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2894510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 2904510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 2914510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 2924510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 2934510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(500)); 2944510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 2954510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 2964510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private TargetTuple findInvisibleTarget(Config config) { 299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE; 300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View child = mLayoutManager.getChildAt(i); 302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int position = mRecyclerView.getChildLayoutPosition(child); 303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position < minPosition) { 304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar minPosition = position; 305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position > maxPosition) { 307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar maxPosition = position; 308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int tailTarget = maxPosition + 311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2; 312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int headTarget = minPosition / 2; 313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int target; 314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // where will the child come from ? 315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int itemLayoutDirection; 316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) { 317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar target = tailTarget; 318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END; 319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar target = headTarget; 321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START; 322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, 325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition); 326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return new TargetTuple(target, itemLayoutDirection); 328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar} 330