LinearLayoutManagerTest.java revision a910619e83d0052e1d81aa5fe532821a2f99d76c
1d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar/* 2d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Copyright (C) 2014 The Android Open Source Project 3d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 4d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * you may not use this file except in compliance with the License. 6d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * You may obtain a copy of the License at 7d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 8d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 10d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * See the License for the specific language governing permissions and 14d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * limitations under the License. 15d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar */ 16d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 17d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpackage android.support.v7.widget; 18d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 19d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.content.Context; 208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.graphics.Rect; 218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.os.Parcel; 228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.os.Parcelable; 23a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.AccessibilityDelegateCompat; 24a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.accessibility.AccessibilityEventCompat; 25a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.accessibility.AccessibilityRecordCompat; 268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log; 27d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View; 28504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup; 29a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent; 30310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout; 31d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 32c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END; 33c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START; 34310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; 35310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL; 368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.lang.reflect.Field; 37d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList; 388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.LinkedHashMap; 39d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List; 408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.Map; 418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.UUID; 42d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.CountDownLatch; 43d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.TimeUnit; 44504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger; 45d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/** 478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}. 488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p> 498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation 508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling 518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing. 528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */ 53d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest { 54d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private static final boolean DEBUG = false; 568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private static final String TAG = "LinearLayoutManagerTest"; 588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 59d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar WrappedLinearLayoutManager mLayoutManager; 60d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 61d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar TestAdapter mTestAdapter; 62d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 63d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final List<Config> mBaseVariations = new ArrayList<Config>(); 64d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 65d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 66d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar protected void setUp() throws Exception { 67d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super.setUp(); 68310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar for (int orientation : new int[]{VERTICAL, HORIZONTAL}) { 69d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (boolean reverseLayout : new boolean[]{false, true}) { 70d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (boolean stackFromBottom : new boolean[]{false, true}) { 71d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom)); 72d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 73d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 74d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 75d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 76d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar protected List<Config> addConfigVariation(List<Config> base, String fieldName, 788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Object... variations) 798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException { 808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar List<Config> newConfigs = new ArrayList<Config>(); 818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Field field = Config.class.getDeclaredField(fieldName); 828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Config config : base) { 838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Object variation : variations) { 848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Config newConfig = (Config) config.clone(); 858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar field.set(newConfig, variation); 868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar newConfigs.add(newConfig); 878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return newConfigs; 908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable { 938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView = new RecyclerView(getActivity()); 948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setHasFixedSize(true); 95504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount) 96504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar : config.mTestAdapter; 978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setAdapter(mTestAdapter); 98d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation, 99d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config.mReverseLayout); 100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 10149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 1028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setLayoutManager(mLayoutManager); 1038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForFirstLayout) { 1048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar waitForFirstLayout(); 1058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 1068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 1078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 108310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void testKeepFocusOnRelayout() throws Throwable { 109310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true); 110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int center = (mLayoutManager.findLastVisibleItemPosition() 111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar - mLayoutManager.findFirstVisibleItemPosition()) / 2; 112310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(center); 113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView); 114310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar vh.itemView.requestFocus(); 118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertTrue("view should have the focus", vh.itemView.hasFocus()); 121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar // add a bunch of items right before that view, make sure it keeps its position 122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.expectLayouts(2); 123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int childCountToAdd = mRecyclerView.getChildCount() * 2; 124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mTestAdapter.addAndNotify(center, childCountToAdd); 125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar center += childCountToAdd; // offset item 126310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForLayout(2); 127310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForAnimationsToEnd(20); 128310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForPosition(center); 129310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertNotNull("focused child should stay in layout", postVH); 130310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertSame("same view holder should be kept for unchanged child", vh, postVH); 131310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("focused child's screen position should stay unchanged", top, 132310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView)); 133310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 134310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 135245b9720dad47a694d16a1d0f48ad462bc27989fYigit Boyar public void testResize() throws Throwable { 136310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5 137310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar , Config.DEFAULT_ITEM_COUNT)) { 138310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar stackFromEndTest(config); 139310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar removeRecyclerView(); 140310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 141310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 142310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 143c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public void testScrollToPositionWithOffset() throws Throwable { 144c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar for (Config config : mBaseVariations) { 145c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffsetTest(config.itemCount(300)); 146c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar removeRecyclerView(); 147c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 148c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 149c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 150c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public void scrollToPositionWithOffsetTest(Config config) throws Throwable { 151c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar setupByConfig(config, true); 152c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 153c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .createOrientationHelper(mLayoutManager, config.mOrientation); 154c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 155c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try scrolling towards head, should not affect anything 156c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 157c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (config.mStackFromEnd) { 158c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1, 159c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.mOrientationHelper.getEnd() - 500); 160c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 161c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(0, 20); 162c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 163c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertRectSetsEqual(config + " trying to over scroll with offset should be no-op", 164c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar before, mLayoutManager.collectChildCoordinates()); 165c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try offsetting some visible children 166c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int testCount = 10; 167c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar while (testCount-- > 0) { 168c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // get middle child 169c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2); 170c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int position = mRecyclerView.getChildPosition(child); 171c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int startOffset = config.mReverseLayout ? 172c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 173c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getDecoratedEnd(child) 174c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 175c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getStartAfterPadding(); 176c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2 177c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : startOffset / 2; 178c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.expectLayouts(1); 179c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(position, scrollOffset); 180c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.waitForLayout(2); 181c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int finalOffset = config.mReverseLayout ? 182c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 183c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getDecoratedEnd(child) 184c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 185c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getStartAfterPadding(); 186c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(config + " scroll with offset on a visible child should work fine " + 187c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar " offset:" + finalOffset + " , existing offset:" + startOffset + ", " 188c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "child " + position, 189c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollOffset, finalOffset); 190c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 191c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 192c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try scrolling to invisible children 193c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar testCount = 10; 194c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // we test above and below, one by one 195c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int offsetMultiplier = -1; 196c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar while (testCount-- > 0) { 197c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final TargetTuple target = findInvisibleTarget(config); 198c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final String logPrefix = config + " " + target; 199c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.expectLayouts(1); 200c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int offset = offsetMultiplier 201c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3; 202c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(target.mPosition, offset); 203c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.waitForLayout(2); 204c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 205c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset 206c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + " should layout it", child); 207c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 208c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (DEBUG) { 209c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in " 210c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + layoutBounds + " with offset " + offset); 211c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 212c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 213c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (config.mReverseLayout) { 214c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse " 215c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "layout, its end should align with recycler view's end - offset", 216c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - offset, 217c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getDecoratedEnd(child) 218c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ); 219c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 220c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal" 221c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + " layout its start should align with recycler view's start + " 222c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "offset", 223c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getStartAfterPadding() + offset, 224c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getDecoratedStart(child) 225c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ); 226c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 227c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar offsetMultiplier *= -1; 228c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 229c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 230c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 231c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar private TargetTuple findInvisibleTarget(Config config) { 232c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE; 233c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 234c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar View child = mLayoutManager.getChildAt(i); 235c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int position = mRecyclerView.getChildPosition(child); 236c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (position < minPosition) { 237c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar minPosition = position; 238c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 239c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (position > maxPosition) { 240c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar maxPosition = position; 241c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 242c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 243c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int tailTarget = maxPosition + 244c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2; 245c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int headTarget = minPosition / 2; 246c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int target; 247c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // where will the child come from ? 248c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int itemLayoutDirection; 249c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) { 250c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar target = tailTarget; 251c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END; 252c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 253c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar target = headTarget; 254c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START; 255c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 256c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (DEBUG) { 257c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Log.d(TAG, 258c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition); 259c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 260c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar return new TargetTuple(target, itemLayoutDirection); 261c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 262c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 263310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void stackFromEndTest(final Config config) throws Throwable { 264310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final FrameLayout container = getRecyclerViewContainer(); 265310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 266310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 267310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 268310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, 0, 0); 269310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 270310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 271310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 272310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(config, true); 273310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition(); 274310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition(); 275310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition(); 276310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition(); 277310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.expectLayouts(1); 278310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar // resize the recycler view to half 279310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 280310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 281310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 282310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mOrientation == HORIZONTAL) { 283310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, container.getWidth() / 2, 0); 284310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } else { 285310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, 0, container.getWidth() / 2); 286310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 287310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 288310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 289310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForLayout(1); 290310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mStackFromEnd) { 291310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last visible position should not change.", 292310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition()); 293310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last completely visible position should not change", 294310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar lastCompletelyVisibleItemPosition, 295310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition()); 296310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } else { 297310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: first visible position should not change.", 298310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition()); 299310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last completely visible position should not change", 300310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar firstCompletelyVisibleItemPosition, 301310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition()); 302310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 303310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 304310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 3056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void testScrollToPositionWithPredictive() throws Throwable { 3066e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET); 3076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 3086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(3, 20); 3096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 3106e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 3116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar LinearLayoutManager.INVALID_OFFSET); 3126e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 3136e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10); 3146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 3166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset) 3176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throws Throwable { 318310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 3196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 3206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.mOnLayoutListener = new OnLayoutListener() { 3216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 3226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state) { 3236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (state.isPreLayout()) { 3246e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position should still be pending", 3256e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollPosition, mLayoutManager.mPendingScrollPosition); 3266e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 3276e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position offset should still be pending", 3286e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset, mLayoutManager.mPendingScrollPositionOffset); 3296e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3306e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 3316e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar RecyclerView.ViewHolder vh = 3326e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mRecyclerView.findViewHolderForPosition(scrollPosition); 3336e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertNotNull("scroll to position should work", vh); 3346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 3356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("scroll offset should be applied properly", 3366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getPaddingTop() + scrollOffset + 3376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar ((RecyclerView.LayoutParams) vh.itemView 3386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar .getLayoutParams()).topMargin, 3396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getDecoratedTop(vh.itemView)); 3406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }; 3446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.expectLayouts(2); 3456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar runTestOnUiThread(new Runnable() { 3466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 3476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void run() { 3486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 3496e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mTestAdapter.addAndNotify(0, 1); 3506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) { 3516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPosition(scrollPosition); 3526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 3536e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPositionWithOffset(scrollPosition, 3546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset); 3556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 3576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable throwable) { 3586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throwable.printStackTrace(); 3596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 3616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3626e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }); 3636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.waitForLayout(2); 3646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar checkForMainThreadException(); 3656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 3666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 3678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private void waitForFirstLayout() throws Throwable { 368d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(1); 3698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setRecyclerView(mRecyclerView); 370d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 371d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 372d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 373504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void testRecycleDuringAnimations() throws Throwable { 374504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final AtomicInteger childCount = new AtomicInteger(0); 375504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestAdapter adapter = new TestAdapter(300) { 376504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 377504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public TestViewHolder onCreateViewHolder(ViewGroup parent, 378504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int viewType) { 379504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 380504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 381504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 382504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder); 383504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 384504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return testViewHolder; 385504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 386504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 387310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(300) 388504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar .adapter(adapter), true); 389504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 390504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() { 391504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 392504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void putRecycledView(RecyclerView.ViewHolder scrap) { 393504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.putRecycledView(scrap); 394504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int cnt = childCount.decrementAndGet(); 395504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 396504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap); 397504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 398504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 399504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 400504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 401504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public RecyclerView.ViewHolder getRecycledView(int viewType) { 402504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType); 403504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (recycledView != null) { 404504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 405504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 406504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView); 407504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 408504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 409504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return recycledView; 410504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 411504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 412504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500); 413504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.setRecycledViewPool(pool); 414504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 415504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 416504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now keep adding children to trigger more children being created etc. 417504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 418504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(15, 1); 419504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(15); 420504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 421504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 422504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 423504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 424504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 425504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 426504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now trigger lots of add again, followed by a scroll to position 427504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 428504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(5 + (i % 3) * 3, 1); 429504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(25); 430504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 431504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20); 432504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 433504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 434504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 435504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 436504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 437504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 438d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 439d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void testGetFirstLastChildrenTest() throws Throwable { 440d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (Config config : mBaseVariations) { 441d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar getFirstLastChildrenTest(config); 442d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 443d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 444d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 44549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void testDontRecycleChildrenOnDetach() throws Throwable { 44649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(false), true); 44749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar runTestOnUiThread(new Runnable() { 44849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 44949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 45049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 45149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.setLayoutManager(new TestLayoutManager()); 45249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("No views are recycled", recyclerSize, 45349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 45449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 45549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 45649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 45749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 45849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void testRecycleChildrenOnDetach() throws Throwable { 45949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(true), true); 46049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar final int childCount = mLayoutManager.getChildCount(); 46149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar runTestOnUiThread(new Runnable() { 46249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 46349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 46449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 46549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews( 46649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mTestAdapter.getItemViewType(0), recyclerSize + childCount); 46749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.setLayoutManager(new TestLayoutManager()); 46849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("All children should be recycled", childCount + recyclerSize, 46949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 47049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 47149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 47249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 47349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 474d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void getFirstLastChildrenTest(final Config config) throws Throwable { 4758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setupByConfig(config, true); 476d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Runnable viewInBoundsTest = new Runnable() { 477d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 478d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 479d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren(); 480d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final String boundsLog = mLayoutManager.getBoundsLog(); 481d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals(config + ":\nfirst visible child should match traversal result\n" 482d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.firstVisiblePosition, 483d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findFirstVisibleItemPosition() 484d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 485d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals( 486d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config + ":\nfirst fully visible child should match traversal result\n" 487d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.firstFullyVisiblePosition, 488d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition() 489d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 490d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 491d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals(config + ":\nlast visible child should match traversal result\n" 492d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.lastVisiblePosition, 493d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findLastVisibleItemPosition() 494d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 495d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals( 496d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config + ":\nlast fully visible child should match traversal result\n" 497d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.lastFullyVisiblePosition, 498d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 499d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 500d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 501d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }; 502d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 503d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 504d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // case 505d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount(); 506d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(new Runnable() { 507d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 508d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 509d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.smoothScrollToPosition(scrollPosition); 510d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 511d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }); 512d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar while (mLayoutManager.isSmoothScrolling() || 513d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 514d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 5158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar Thread.sleep(400); 516d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 517d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // delete all items 518d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(2); 519d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount()); 520d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 521d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // test empty case 522d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 523d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // set a new adapter with huge items to test full bounds check 524d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(1); 525d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace(); 526d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final TestAdapter newAdapter = new TestAdapter(100) { 527d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 528d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void onBindViewHolder(TestViewHolder holder, 529d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int position) { 530d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super.onBindViewHolder(holder, position); 531310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mOrientation == HORIZONTAL) { 532d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar holder.itemView.setMinimumWidth(totalSpace + 5); 533d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } else { 534d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar holder.itemView.setMinimumHeight(totalSpace + 5); 535d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 536d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 537d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }; 538d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(new Runnable() { 539d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 540d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 541d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.setAdapter(newAdapter); 542d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 543d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }); 544d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 545d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 546d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 547d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 5488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void testSavedState() throws Throwable { 5498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{ 5508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 5518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 5538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // do nothing 5548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 5568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 5588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "doing nothing"; 5598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 5618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 5628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 5648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 5658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPosition(mTestAdapter.getItemCount() * 3 / 4); 5668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 5678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 5698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 5718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position"; 5728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 5748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 5758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 5778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 5788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3, 5798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 50); 5808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 5818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 5838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 5858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position with positive offset"; 5868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 5888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 5898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 5918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 5928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3, 5938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar -50); 5948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 5958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 5968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 5978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 5988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 5998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position with negative offset"; 6008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }; 6038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{ 6058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 6068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 6088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Doing nothing"; 6098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 6118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 6128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 6148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // update config as well so that restore assertions will work 6158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mOrientation = 1 - config.mOrientation; 6168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setOrientation(config.mOrientation); 6178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 6218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return config.mItemCount == 0; 6228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 6268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing orientation"; 6278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 6298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 6308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 6328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mStackFromEnd = !config.mStackFromEnd; 6338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 6348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 6388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return true; //stack from end should not move items on change 6398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 6438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing stack from end"; 6448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 6468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 6478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 6498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mReverseLayout = !config.mReverseLayout; 6508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setReverseLayout(config.mReverseLayout); 6518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 6558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return config.mItemCount == 0; 6568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 6608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing reverse layout"; 6618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 66275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar }, 66375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar new PostRestoreRunnable() { 66449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 66549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar void onAfterRestore(Config config) throws Throwable { 66649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach; 66749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 66849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 66949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 67049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 67149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar boolean shouldLayoutMatch(Config config) { 67249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar return true; 67349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 67449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 67549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 67649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar String describe() { 677b97e8219784e623526bc3c6077a698d608f04fd9Yigit Boyar return "Change should recycle children"; 67849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 67949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }, 68049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar new PostRestoreRunnable() { 68175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar int position; 68275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 68375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterRestore(Config config) throws Throwable { 68475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar position = mTestAdapter.getItemCount() / 2; 68575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar mLayoutManager.scrollToPosition(position); 68675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 68775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 68875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 68975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar boolean shouldLayoutMatch(Config config) { 69075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar return mTestAdapter.getItemCount() == 0; 69175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 69275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 69375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 69475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar String describe() { 695aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar return "Scroll to position " + position ; 69675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 69775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 69875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 69975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterReLayout(Config config) { 70075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar if (mTestAdapter.getItemCount() > 0) { 701aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar assertEquals(config + ":scrolled view should be last completely visible", 70275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar position, 703aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar config.mStackFromEnd ? 704aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 705aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar : mLayoutManager.findFirstCompletelyVisibleItemPosition()); 70675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 70775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 7088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }; 71075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar boolean[] waitForLayoutOptions = new boolean[]{true, false}; 71149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300); 71249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true); 71349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar for (Config config : variations) { 7148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) { 7158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (boolean waitForLayout : waitForLayoutOptions) { 7168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) { 7178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable, 7188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postRestoreRunnable); 7198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 7208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void savedStateTest(Config config, boolean waitForLayout, 7288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation) 7298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throws Throwable { 7308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (DEBUG) { 7318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " + 7328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config + " post layout action " + postLayoutOperation.describe() + 7338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar "post restore action " + postRestoreOperation.describe()); 7348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setupByConfig(config, false); 7368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForLayout) { 7378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar waitForFirstLayout(); 7388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postLayoutOperation.run(); 7398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 7418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Parcelable savedState = mRecyclerView.onSaveInstanceState(); 7428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // we append a suffix to the parcelable to test out of bounds 7438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar String parcelSuffix = UUID.randomUUID().toString(); 7448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Parcel parcel = Parcel.obtain(); 7458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedState.writeToParcel(parcel, 0); 7468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.writeString(parcelSuffix); 7478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 7488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // reset for reading 7498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.setDataPosition(0); 7508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // re-create 7518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); 7528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 7538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar RecyclerView restored = new RecyclerView(getActivity()); 7558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // this config should be no op. 7568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), 757b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar config.mOrientation, config.mReverseLayout); 758b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 7598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.setLayoutManager(mLayoutManager); 7608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // use the same adapter for Rect matching 7618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.setAdapter(mTestAdapter); 7628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.onRestoreInstanceState(savedState); 7638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postRestoreOperation.onAfterRestore(config); 7648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals("Parcel reading should not go out of bounds", parcelSuffix, 7658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.readString()); 7668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 7678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setRecyclerView(restored); 7688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 7698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // calculate prefix here instead of above to include post restore changes 7708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() + 7718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar "\npostRestore:" + postRestoreOperation.describe() + "\n"; 7728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, reverse layout should be preserved", 7738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mReverseLayout, mLayoutManager.getReverseLayout()); 7748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, orientation should be preserved", 7758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mOrientation, mLayoutManager.getOrientation()); 7768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, stack from end should be preserved", 7778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mStackFromEnd, mLayoutManager.getStackFromEnd()); 7788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForLayout) { 7798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (postRestoreOperation.shouldLayoutMatch(config)) { 7808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsEqual( 7818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar logPrefix + ": on restore, previous view positions should be preserved", 7828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar before, mLayoutManager.collectChildCoordinates()); 7838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } else { 7848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsNotEqual( 7858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar logPrefix 78649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar + ": on restore with changes, previous view positions should NOT " 78749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar + "be preserved", 7888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar before, mLayoutManager.collectChildCoordinates()); 7898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 79075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar postRestoreOperation.onAfterReLayout(config); 7918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void scrollToPositionWithOffset(final int position, final int offset) throws Throwable { 7958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 7968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 7988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.scrollToPositionWithOffset(position, offset); 7998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 8018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void assertRectSetsNotEqual(String message, Map<Item, Rect> before, 8048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> after) { 8058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Throwable throwable = null; 8068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar try { 8078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsEqual("NOT " + message, before, after); 8088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } catch (Throwable t) { 8098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throwable = t; 8108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertNotNull(message + "\ntwo layout should be different", throwable); 8128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) { 815668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar StringBuilder sb = new StringBuilder(); 816668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("checking rectangle equality."); 817668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("before:\n"); 818668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 819668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 820668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar } 821668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("after:\n"); 822668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar for (Map.Entry<Item, Rect> entry : after.entrySet()) { 823668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 8248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 825668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar message = message + "\n" + sb.toString(); 8268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(message + ":\nitem counts should be equal", before.size() 8278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar , after.size()); 8288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 8298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Rect afterRect = after.get(entry.getKey()); 8308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertNotNull(message + ":\nSame item should be visible after simple re-layout", 8318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar afterRect); 8328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(message + ":\nItem should be laid out at the same coordinates", 8338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar entry.getValue(), afterRect); 8348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 837a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar public void testAccessibilityPositions() throws Throwable { 838a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 839a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityDelegateCompat delegateCompat = mRecyclerView 840a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar .getCompatAccessibilityDelegate(); 841a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityEvent event = AccessibilityEvent.obtain(); 842a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar runTestOnUiThread(new Runnable() { 843a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar @Override 844a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar public void run() { 845a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event); 846a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 847a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar }); 848a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityRecordCompat record = AccessibilityEventCompat 849a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar .asRecord(event); 850a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have first position", 851a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar record.getFromIndex(), 852a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findFirstVisibleItemPosition()); 853a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have last position", 854a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar record.getToIndex(), 855a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findLastVisibleItemPosition()); 856a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 857a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar 858d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar static class VisibleChildren { 859d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 860d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int firstVisiblePosition = RecyclerView.NO_POSITION; 861d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 862d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int firstFullyVisiblePosition = RecyclerView.NO_POSITION; 863d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 864d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int lastVisiblePosition = RecyclerView.NO_POSITION; 865d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 866d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int lastFullyVisiblePosition = RecyclerView.NO_POSITION; 867d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 868d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 869d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String toString() { 870d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return "VisibleChildren{" + 871d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar "firstVisiblePosition=" + firstVisiblePosition + 872d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", firstFullyVisiblePosition=" + firstFullyVisiblePosition + 873d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", lastVisiblePosition=" + lastVisiblePosition + 874d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", lastFullyVisiblePosition=" + lastFullyVisiblePosition + 875d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar '}'; 876d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 877d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 878d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 8798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract private class PostLayoutRunnable { 8808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract void run() throws Throwable; 8828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract String describe(); 8848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract private class PostRestoreRunnable { 8878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 8898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract String describe(); 8928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 8948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return true; 8958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 89675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 89775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterReLayout(Config config) { 89875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 89975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar }; 9008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar class WrappedLinearLayoutManager extends LinearLayoutManager { 903d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 904d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar CountDownLatch layoutLatch; 905d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 9068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar OrientationHelper mSecondaryOrientation; 9078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar OnLayoutListener mOnLayoutListener; 9096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 910d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 911d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super(context, orientation, reverseLayout); 912d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 913d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 914d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void expectLayouts(int count) { 915d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar layoutLatch = new CountDownLatch(count); 916d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 917d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 918d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void waitForLayout(long timeout) throws InterruptedException { 919d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar waitForLayout(timeout, TimeUnit.SECONDS); 920d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 921d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 9228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 9238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void setOrientation(int orientation) { 9248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar super.setOrientation(orientation); 9258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation = null; 9268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 929504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) { 930504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 931504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child)); 932504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 933504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.removeAndRecycleView(child, recycler); 934504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 935504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 936504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 937504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) { 938504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 939504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index))); 940504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 941504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.removeAndRecycleViewAt(index, recycler); 942504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 943504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 944504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 94594c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar void ensureLayoutState() { 94694c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar super.ensureLayoutState(); 9478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (mSecondaryOrientation == null) { 94894c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar mSecondaryOrientation = OrientationHelper.createOrientationHelper(this, 94994c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar 1 - getOrientation()); 9508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 953d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException { 9546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit); 955d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals("all expected layouts should be executed at the expected time", 956d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 0, layoutLatch.getCount()); 9576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar getInstrumentation().waitForIdleSync(); 958d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 959d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 960d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String getBoundsLog() { 961d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar StringBuilder sb = new StringBuilder(); 962d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding()) 963d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding()); 964d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("\nchildren bounds\n"); 965d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childCount = getChildCount(); 966d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (int i = 0; i < childCount; i++) { 967d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar View child = getChildAt(i); 968d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child)) 969d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append("[").append("start:").append( 970d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientationHelper.getDecoratedStart(child)).append(", end:") 971d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n"); 972d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 973d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return sb.toString(); 974d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 975d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 976310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException { 977310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 978310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (itemAnimator == null) { 979310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar return; 980310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 981310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final CountDownLatch latch = new CountDownLatch(1); 982310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final boolean running = itemAnimator.isRunning( 983310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 984310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 985310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void onAnimationsFinished() { 986310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar latch.countDown(); 987310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 988310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 989310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar ); 990310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (running) { 991310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar latch.await(timeoutInSeconds, TimeUnit.SECONDS); 992310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 993310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 994310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 995d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public VisibleChildren traverseAndFindVisibleChildren() { 996d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int childCount = getChildCount(); 997d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final VisibleChildren visibleChildren = new VisibleChildren(); 998d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int start = mOrientationHelper.getStartAfterPadding(); 999d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int end = mOrientationHelper.getEndAfterPadding(); 1000d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (int i = 0; i < childCount; i++) { 1001d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar View child = getChildAt(i); 1002d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childStart = mOrientationHelper.getDecoratedStart(child); 1003d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childEnd = mOrientationHelper.getDecoratedEnd(child); 1004d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final boolean fullyVisible = childStart >= start && childEnd <= end; 1005d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final boolean hidden = childEnd <= start || childStart >= end; 1006d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (hidden) { 1007d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar continue; 1008d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1009d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int position = getPosition(child); 1010d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (fullyVisible) { 1011d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position < visibleChildren.firstFullyVisiblePosition || 1012d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) { 1013d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstFullyVisiblePosition = position; 1014d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1015d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1016d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position > visibleChildren.lastFullyVisiblePosition) { 1017d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.lastFullyVisiblePosition = position; 1018d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1019d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1020d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1021d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position < visibleChildren.firstVisiblePosition || 1022d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) { 1023d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstVisiblePosition = position; 1024d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1025d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1026d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position > visibleChildren.lastVisiblePosition) { 1027d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.lastVisiblePosition = position; 1028d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1029d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1030d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1031d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return visibleChildren; 1032d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1033d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Rect getViewBounds(View view) { 10358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (getOrientation() == HORIZONTAL) { 10368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return new Rect( 10378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedStart(view), 10388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedStart(view), 10398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedEnd(view), 10408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedEnd(view)); 10418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } else { 10428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return new Rect( 10438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedStart(view), 10448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedStart(view), 10458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedEnd(view), 10468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedEnd(view)); 10478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> collectChildCoordinates() throws Throwable { 10528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>(); 10538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 10548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 10558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 10568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final int childCount = getChildCount(); 10578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (int i = 0; i < childCount; i++) { 10588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar View child = getChildAt(i); 10598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child 10608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar .getLayoutParams(); 10618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar TestViewHolder vh = (TestViewHolder) lp.mViewHolder; 10628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar items.put(vh.mBindedItem, getViewBounds(child)); 10638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 10668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return items; 10678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 1069d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 1070d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 10716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 10726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (mOnLayoutListener != null) { 10736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mOnLayoutListener.before(recycler, state); 10746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 10756e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar super.onLayoutChildren(recycler, state); 10766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (mOnLayoutListener != null) { 10776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mOnLayoutListener.after(recycler, state); 10786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 10796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable t) { 10806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar postExceptionToInstrumentation(t); 10816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 1082d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar layoutLatch.countDown(); 1083d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 10846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 10856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 10866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 10876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 10886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar static class OnLayoutListener { 10896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void before(RecyclerView.Recycler recycler, RecyclerView.State state){} 10906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state){} 1091d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1092d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar static class Config implements Cloneable { 1094d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar private static final int DEFAULT_ITEM_COUNT = 100; 1096d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1097d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar private boolean mStackFromEnd; 1098d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1099310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int mOrientation = VERTICAL; 1100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar boolean mReverseLayout = false; 1102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 110349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar boolean mRecycleChildrenOnDetach = false; 110449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 1105d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int mItemCount = DEFAULT_ITEM_COUNT; 1106d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1107504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar TestAdapter mTestAdapter; 1108504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1109d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config(int orientation, boolean reverseLayout, boolean stackFromEnd) { 1110d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientation = orientation; 1111d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mReverseLayout = reverseLayout; 1112d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mStackFromEnd = stackFromEnd; 1113d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1114d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1115d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public Config() { 1116d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1117d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1118d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1119504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Config adapter(TestAdapter adapter) { 1120504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mTestAdapter = adapter; 1121504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return this; 1122504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1123504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 112449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) { 112549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecycleChildrenOnDetach = recycleChildrenOnDetach; 112649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar return this; 112749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 112849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 1129d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config orientation(int orientation) { 1130d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientation = orientation; 1131d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1132d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1133d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1134d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config stackFromBottom(boolean stackFromBottom) { 1135d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mStackFromEnd = stackFromBottom; 1136d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1137d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1138d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1139d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config reverseLayout(boolean reverseLayout) { 1140d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mReverseLayout = reverseLayout; 1141d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1142d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1143d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1144d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public Config itemCount(int itemCount) { 1145d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mItemCount = itemCount; 1146d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1147d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1148d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 11498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // required by convention 11508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 11518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public Object clone() throws CloneNotSupportedException { 11528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return super.clone(); 11538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 1155d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 1156d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String toString() { 1157d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return "Config{" + 1158d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar "mStackFromEnd=" + mStackFromEnd + 1159d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mOrientation=" + mOrientation + 1160d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mReverseLayout=" + mReverseLayout + 116149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach + 1162d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mItemCount=" + mItemCount + 1163d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar '}'; 1164d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1165d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1166d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar} 1167