1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/* 2999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Copyright (C) 2015 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 17999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpackage android.support.v7.widget; 18999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 19999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END; 20999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START; 21999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.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 31e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.graphics.Rect; 32e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.support.v4.view.ViewCompat; 33e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.test.suitebuilder.annotation.MediumTest; 34e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.util.Log; 35e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.view.View; 36e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport android.view.ViewParent; 37e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 38e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.junit.Test; 39e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.junit.runner.RunWith; 40e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.junit.runners.Parameterized; 41e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 42e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport java.util.ArrayList; 43e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport java.util.List; 44e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport java.util.Map; 45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/** 47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Tests that rely on the basic configuration and does not do any additions / removals 48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */ 49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar@RunWith(Parameterized.class) 504510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar@MediumTest 51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManagerTest { 52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private final Config mConfig; 54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public LinearLayoutManagerBaseConfigSetTest(Config config) { 56999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig = config; 57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 59999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 60999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Parameterized.Parameters(name = "{0}") 61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public static List<Config> configs() throws CloneNotSupportedException { 624143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar List<Config> result = new ArrayList<>(); 634143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar for (Config config : createBaseVariations()) { 644143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar result.add(config); 654143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 664143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar return result; 67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Test 70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void scrollToPositionWithOffsetTest() throws Throwable { 71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config config = ((Config) mConfig.clone()).itemCount(300); 720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(config, true); 73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .createOrientationHelper(mLayoutManager, config.mOrientation); 75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try scrolling towards head, should not affect anything 77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mStackFromEnd) { 79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1, 80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.mOrientationHelper.getEnd() - 500); 81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(0, 20); 83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsEqual(config + " trying to over scroll with offset should be no-op", 85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar before, mLayoutManager.collectChildCoordinates()); 86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try offsetting some visible children 87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int testCount = 10; 88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (testCount-- > 0) { 89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // get middle child 90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2); 91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(child); 92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int startOffset = config.mReverseLayout ? 93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getDecoratedEnd(child) 95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getStartAfterPadding(); 97999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2 98999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : startOffset / 2; 99999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(position, scrollOffset); 101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int finalOffset = config.mReverseLayout ? 103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getDecoratedEnd(child) 105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getStartAfterPadding(); 107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + " scroll with offset on a visible child should work fine " + 108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar " offset:" + finalOffset + " , existing offset:" + startOffset + ", " 109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "child " + position, 110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollOffset, finalOffset); 111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // try scrolling to invisible children 114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar testCount = 10; 115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // we test above and below, one by one 116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int offsetMultiplier = -1; 117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (testCount-- > 0) { 118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final TargetTuple target = findInvisibleTarget(config); 119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final String logPrefix = config + " " + target; 120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int offset = offsetMultiplier 122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3; 123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(target.mPosition, offset); 124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset 127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + " should layout it", child); 128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in " 131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + layoutBounds + " with offset " + offset); 132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mReverseLayout) { 135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse " 136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "layout, its end should align with recycler view's end - offset", 137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getEndAfterPadding() - offset, 138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getDecoratedEnd(child) 139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar logPrefix + " when scrolling with offset to an invisible child in normal" 143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + " layout its start should align with recycler view's start + " 144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "offset", 145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getStartAfterPadding() + offset, 146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar orientationHelper.getDecoratedStart(child) 147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar offsetMultiplier *= -1; 150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Test 154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void getFirstLastChildrenTest() throws Throwable { 155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(300); 156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setupByConfig(config, true); 157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Runnable viewInBoundsTest = new Runnable() { 158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren(); 161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final String boundsLog = mLayoutManager.getBoundsLog(); 162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + ":\nfirst visible child should match traversal result\n" 163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.firstVisiblePosition, 164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findFirstVisibleItemPosition() 165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + ":\nfirst fully visible child should match traversal result\n" 168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.firstFullyVisiblePosition, 169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition() 170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + ":\nlast visible child should match traversal result\n" 173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.lastVisiblePosition, 174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findLastVisibleItemPosition() 175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals( 177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + ":\nlast fully visible child should match traversal result\n" 178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + boundsLog, visibleChildren.lastFullyVisiblePosition, 179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(viewInBoundsTest); 184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // case 186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount(); 187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(new Runnable() { 188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.smoothScrollToPosition(scrollPosition); 191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar while (mLayoutManager.isSmoothScrolling() || 194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(viewInBoundsTest); 196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Thread.sleep(400); 197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // delete all items 199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(2); 200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount()); 201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // test empty case 203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(viewInBoundsTest); 204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // set a new adapter with huge items to test full bounds check 205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace(); 207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final TestAdapter newAdapter = new TestAdapter(100) { 208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void onBindViewHolder(TestViewHolder holder, 210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int position) { 211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.onBindViewHolder(holder, position); 212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (config.mOrientation == HORIZONTAL) { 213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar holder.itemView.setMinimumWidth(totalSpace + 5); 214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar holder.itemView.setMinimumHeight(totalSpace + 5); 216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(new Runnable() { 220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.setAdapter(newAdapter); 223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(viewInBoundsTest); 227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 2294510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 2304510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable { 2314510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 2324510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config, true); 2334510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.expectLayouts(1); 2344510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollToPosition(500); 2354510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.waitForLayout(2); 2364510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500); 2374510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mOrientationHelper; 2384510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getDecoratedStart(vh.itemView); 2394510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(gap); 2404510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getDecoratedStart(vh.itemView); 2414510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 2424510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 2444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 2454510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar runTestOnUiThread(new Runnable() { 2464510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 2474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 2484510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 2494510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationX(vh.itemView, size * 2); 2504510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 2514510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationY(vh.itemView, size * 2); 2524510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2534510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2544510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 2554510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 2564510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 2574510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 2584510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(500)); 2594510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(size * 2); 2604510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 2614510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2624510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2634510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Test 2644510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable { 2654510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final Config config = ((Config) mConfig.clone()).itemCount(1000); 2664510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar setupByConfig(config, true); 2674510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.expectLayouts(1); 2684510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollToPosition(500); 2694510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar mLayoutManager.waitForLayout(2); 2704510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500); 2714510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar OrientationHelper helper = mLayoutManager.mOrientationHelper; 2724510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 2734510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-gap); 2744510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView); 2754510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat("test sanity", gap, is(0)); 2764510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar 2774510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar final int size = helper.getDecoratedMeasurement(vh.itemView); 2784510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView); 2794510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar runTestOnUiThread(new Runnable() { 2804510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar @Override 2814510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar public void run() { 2824510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar if (mConfig.mOrientation == HORIZONTAL) { 2834510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationX(vh.itemView, -size * 2); 2844510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } else { 2854510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar ViewCompat.setTranslationY(vh.itemView, -size * 2); 2864510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2874510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 2884510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar }); 2894510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 2904510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView)))); 2914510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView)); 2924510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(vh.getAdapterPosition(), is(500)); 2934510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar scrollBy(-size * 2); 2944510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView))); 2954510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar } 296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private TargetTuple findInvisibleTarget(Config config) { 298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE; 299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View child = mLayoutManager.getChildAt(i); 301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int position = mRecyclerView.getChildLayoutPosition(child); 302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position < minPosition) { 303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar minPosition = position; 304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position > maxPosition) { 306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar maxPosition = position; 307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int tailTarget = maxPosition + 310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2; 311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int headTarget = minPosition / 2; 312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int target; 313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // where will the child come from ? 314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int itemLayoutDirection; 315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) { 316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar target = tailTarget; 317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END; 318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar target = headTarget; 320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START; 321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, 324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition); 325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return new TargetTuple(target, itemLayoutDirection); 327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar} 329