LinearLayoutManagerTest.java revision 542f1260934df280985294eaef1ec8469863281f
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; 26542f1260934df280985294eaef1ec8469863281fYigit Boyarimport android.util.AttributeSet; 278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log; 28d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View; 29504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup; 30a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent; 31310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout; 32d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 33c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END; 34c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START; 35310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; 36310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL; 378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.lang.reflect.Field; 38d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList; 398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.LinkedHashMap; 40d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List; 418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.Map; 428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.UUID; 43d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.CountDownLatch; 44d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.TimeUnit; 45504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger; 46d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/** 488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}. 498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p> 508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation 518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling 528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing. 538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */ 54d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest { 55d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private static final boolean DEBUG = false; 578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private static final String TAG = "LinearLayoutManagerTest"; 598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 60d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar WrappedLinearLayoutManager mLayoutManager; 61d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 62d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar TestAdapter mTestAdapter; 63d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 64d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final List<Config> mBaseVariations = new ArrayList<Config>(); 65d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 66d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 67d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar protected void setUp() throws Exception { 68d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super.setUp(); 69310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar for (int orientation : new int[]{VERTICAL, HORIZONTAL}) { 70d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (boolean reverseLayout : new boolean[]{false, true}) { 71d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (boolean stackFromBottom : new boolean[]{false, true}) { 72d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom)); 73d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 74d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 75d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 76d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 77d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar protected List<Config> addConfigVariation(List<Config> base, String fieldName, 798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Object... variations) 808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException { 818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar List<Config> newConfigs = new ArrayList<Config>(); 828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Field field = Config.class.getDeclaredField(fieldName); 838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Config config : base) { 848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Object variation : variations) { 858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Config newConfig = (Config) config.clone(); 868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar field.set(newConfig, variation); 878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar newConfigs.add(newConfig); 888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return newConfigs; 918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable { 94542f1260934df280985294eaef1ec8469863281fYigit Boyar mRecyclerView = new WrappedRecyclerView(getActivity()); 95542f1260934df280985294eaef1ec8469863281fYigit Boyar 968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setHasFixedSize(true); 97504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount) 98504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar : config.mTestAdapter; 998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setAdapter(mTestAdapter); 100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation, 101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config.mReverseLayout); 102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 10349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 1048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.setLayoutManager(mLayoutManager); 1058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForFirstLayout) { 1068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar waitForFirstLayout(); 1078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 1088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 1098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void testKeepFocusOnRelayout() throws Throwable { 111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true); 112310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int center = (mLayoutManager.findLastVisibleItemPosition() 113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar - mLayoutManager.findFirstVisibleItemPosition()) / 2; 114115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center); 115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView); 116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar vh.itemView.requestFocus(); 120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertTrue("view should have the focus", vh.itemView.hasFocus()); 123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar // add a bunch of items right before that view, make sure it keeps its position 124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.expectLayouts(2); 125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int childCountToAdd = mRecyclerView.getChildCount() * 2; 126310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mTestAdapter.addAndNotify(center, childCountToAdd); 127310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar center += childCountToAdd; // offset item 128310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForLayout(2); 129310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForAnimationsToEnd(20); 130115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center); 131310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertNotNull("focused child should stay in layout", postVH); 132310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertSame("same view holder should be kept for unchanged child", vh, postVH); 133310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("focused child's screen position should stay unchanged", top, 134310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView)); 135310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 136310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 137542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepFullFocusOnResize() throws Throwable { 138542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true); 139542f1260934df280985294eaef1ec8469863281fYigit Boyar } 140542f1260934df280985294eaef1ec8469863281fYigit Boyar 141542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepPartialFocusOnResize() throws Throwable { 142542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false); 143542f1260934df280985294eaef1ec8469863281fYigit Boyar } 144542f1260934df280985294eaef1ec8469863281fYigit Boyar 145542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepReverseFullFocusOnResize() throws Throwable { 146542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true); 147542f1260934df280985294eaef1ec8469863281fYigit Boyar } 148542f1260934df280985294eaef1ec8469863281fYigit Boyar 149542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepReversePartialFocusOnResize() throws Throwable { 150542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false); 151542f1260934df280985294eaef1ec8469863281fYigit Boyar } 152542f1260934df280985294eaef1ec8469863281fYigit Boyar 153542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepStackFromEndFullFocusOnResize() throws Throwable { 154542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true); 155542f1260934df280985294eaef1ec8469863281fYigit Boyar } 156542f1260934df280985294eaef1ec8469863281fYigit Boyar 157542f1260934df280985294eaef1ec8469863281fYigit Boyar public void testKeepStackFromEndPartialFocusOnResize() throws Throwable { 158542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false); 159542f1260934df280985294eaef1ec8469863281fYigit Boyar } 160542f1260934df280985294eaef1ec8469863281fYigit Boyar 161542f1260934df280985294eaef1ec8469863281fYigit Boyar public void keepFocusOnResizeTest(final Config config, boolean fullyVisible) throws Throwable { 162542f1260934df280985294eaef1ec8469863281fYigit Boyar setupByConfig(config, true); 163542f1260934df280985294eaef1ec8469863281fYigit Boyar final int targetPosition; 164542f1260934df280985294eaef1ec8469863281fYigit Boyar if (config.mStackFromEnd) { 165542f1260934df280985294eaef1ec8469863281fYigit Boyar targetPosition = mLayoutManager.findFirstVisibleItemPosition(); 166542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 167542f1260934df280985294eaef1ec8469863281fYigit Boyar targetPosition = mLayoutManager.findLastVisibleItemPosition(); 168542f1260934df280985294eaef1ec8469863281fYigit Boyar } 169542f1260934df280985294eaef1ec8469863281fYigit Boyar final OrientationHelper helper = mLayoutManager.mOrientationHelper; 170542f1260934df280985294eaef1ec8469863281fYigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView 171542f1260934df280985294eaef1ec8469863281fYigit Boyar .findViewHolderForLayoutPosition(targetPosition); 172542f1260934df280985294eaef1ec8469863281fYigit Boyar 173542f1260934df280985294eaef1ec8469863281fYigit Boyar // scroll enough to offset the child 174542f1260934df280985294eaef1ec8469863281fYigit Boyar int startMargin = helper.getDecoratedStart(vh.itemView) - 175542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 176542f1260934df280985294eaef1ec8469863281fYigit Boyar int endMargin = helper.getEndAfterPadding() - 177542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 178542f1260934df280985294eaef1ec8469863281fYigit Boyar Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin); 179542f1260934df280985294eaef1ec8469863281fYigit Boyar requestFocus(vh.itemView); 180542f1260934df280985294eaef1ec8469863281fYigit Boyar runTestOnUiThread(new Runnable() { 181542f1260934df280985294eaef1ec8469863281fYigit Boyar @Override 182542f1260934df280985294eaef1ec8469863281fYigit Boyar public void run() { 183542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("view should gain the focus", vh.itemView.hasFocus()); 184542f1260934df280985294eaef1ec8469863281fYigit Boyar } 185542f1260934df280985294eaef1ec8469863281fYigit Boyar }); 186542f1260934df280985294eaef1ec8469863281fYigit Boyar do { 187542f1260934df280985294eaef1ec8469863281fYigit Boyar Thread.sleep(100); 188542f1260934df280985294eaef1ec8469863281fYigit Boyar } while (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE); 189542f1260934df280985294eaef1ec8469863281fYigit Boyar // scroll enough to offset the child 190542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(vh.itemView) - 191542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 192542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - 193542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 194542f1260934df280985294eaef1ec8469863281fYigit Boyar 195542f1260934df280985294eaef1ec8469863281fYigit Boyar Log.d(TAG, "start margin " + startMargin + " , end margin:" + endMargin); 196542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("View should become fully visible", startMargin >= 0 && endMargin >= 0); 197542f1260934df280985294eaef1ec8469863281fYigit Boyar 198542f1260934df280985294eaef1ec8469863281fYigit Boyar int expectedOffset = 0; 199542f1260934df280985294eaef1ec8469863281fYigit Boyar boolean offsetAtStart = false; 200542f1260934df280985294eaef1ec8469863281fYigit Boyar if (!fullyVisible) { 201542f1260934df280985294eaef1ec8469863281fYigit Boyar // move it a bit such that it is no more fully visible 202542f1260934df280985294eaef1ec8469863281fYigit Boyar final int childSize = helper 203542f1260934df280985294eaef1ec8469863281fYigit Boyar .getDecoratedMeasurement(vh.itemView); 204542f1260934df280985294eaef1ec8469863281fYigit Boyar expectedOffset = childSize / 3; 205542f1260934df280985294eaef1ec8469863281fYigit Boyar if (startMargin < endMargin) { 206542f1260934df280985294eaef1ec8469863281fYigit Boyar scrollBy(expectedOffset); 207542f1260934df280985294eaef1ec8469863281fYigit Boyar offsetAtStart = true; 208542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 209542f1260934df280985294eaef1ec8469863281fYigit Boyar scrollBy(-expectedOffset); 210542f1260934df280985294eaef1ec8469863281fYigit Boyar offsetAtStart = false; 211542f1260934df280985294eaef1ec8469863281fYigit Boyar } 212542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(vh.itemView) - 213542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 214542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - 215542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 216542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("test sanity, view should not be fully visible", startMargin < 0 217542f1260934df280985294eaef1ec8469863281fYigit Boyar || endMargin < 0); 218542f1260934df280985294eaef1ec8469863281fYigit Boyar } 219542f1260934df280985294eaef1ec8469863281fYigit Boyar 220542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.expectLayouts(1); 221542f1260934df280985294eaef1ec8469863281fYigit Boyar runTestOnUiThread(new Runnable() { 222542f1260934df280985294eaef1ec8469863281fYigit Boyar @Override 223542f1260934df280985294eaef1ec8469863281fYigit Boyar public void run() { 224542f1260934df280985294eaef1ec8469863281fYigit Boyar final ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams(); 225542f1260934df280985294eaef1ec8469863281fYigit Boyar if (config.mOrientation == HORIZONTAL) { 226542f1260934df280985294eaef1ec8469863281fYigit Boyar layoutParams.width = mRecyclerView.getWidth() / 2; 227542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 228542f1260934df280985294eaef1ec8469863281fYigit Boyar layoutParams.height = mRecyclerView.getHeight() / 2; 229542f1260934df280985294eaef1ec8469863281fYigit Boyar } 230542f1260934df280985294eaef1ec8469863281fYigit Boyar mRecyclerView.setLayoutParams(layoutParams); 231542f1260934df280985294eaef1ec8469863281fYigit Boyar } 232542f1260934df280985294eaef1ec8469863281fYigit Boyar }); 233542f1260934df280985294eaef1ec8469863281fYigit Boyar Thread.sleep(100); 234542f1260934df280985294eaef1ec8469863281fYigit Boyar // add a bunch of items right before that view, make sure it keeps its position 235542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.waitForLayout(2); 236542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.waitForAnimationsToEnd(20); 237542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("view should preserve the focus", vh.itemView.hasFocus()); 238542f1260934df280985294eaef1ec8469863281fYigit Boyar final RecyclerView.ViewHolder postVH = mRecyclerView 239542f1260934df280985294eaef1ec8469863281fYigit Boyar .findViewHolderForLayoutPosition(targetPosition); 240542f1260934df280985294eaef1ec8469863281fYigit Boyar assertNotNull("focused child should stay in layout", postVH); 241542f1260934df280985294eaef1ec8469863281fYigit Boyar assertSame("same view holder should be kept for unchanged child", vh, postVH); 242542f1260934df280985294eaef1ec8469863281fYigit Boyar View focused = postVH.itemView; 243542f1260934df280985294eaef1ec8469863281fYigit Boyar 244542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(focused) - helper.getStartAfterPadding(); 245542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - helper.getDecoratedEnd(focused); 246542f1260934df280985294eaef1ec8469863281fYigit Boyar 247542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child should be somewhat visible", 248542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedStart(focused) < helper.getEndAfterPadding() 249542f1260934df280985294eaef1ec8469863281fYigit Boyar && helper.getDecoratedEnd(focused) > helper.getStartAfterPadding()); 250542f1260934df280985294eaef1ec8469863281fYigit Boyar if (fullyVisible) { 251542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child end should stay fully visible", 252542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin >= 0); 253542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child start should stay fully visible", 254542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin >= 0); 255542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 256542f1260934df280985294eaef1ec8469863281fYigit Boyar if (offsetAtStart) { 257542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("start should preserve its offset", startMargin < 0); 258542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("end should be visible", endMargin >= 0); 259542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 260542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("end should preserve its offset", endMargin < 0); 261542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("start should be visible", startMargin >= 0); 262542f1260934df280985294eaef1ec8469863281fYigit Boyar } 263542f1260934df280985294eaef1ec8469863281fYigit Boyar } 264542f1260934df280985294eaef1ec8469863281fYigit Boyar } 265542f1260934df280985294eaef1ec8469863281fYigit Boyar 266245b9720dad47a694d16a1d0f48ad462bc27989fYigit Boyar public void testResize() throws Throwable { 267310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5 268310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar , Config.DEFAULT_ITEM_COUNT)) { 269310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar stackFromEndTest(config); 270310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar removeRecyclerView(); 271310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 272310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 273310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 274c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public void testScrollToPositionWithOffset() throws Throwable { 275c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar for (Config config : mBaseVariations) { 276c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffsetTest(config.itemCount(300)); 277c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar removeRecyclerView(); 278c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 279c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 280c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 281c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public void scrollToPositionWithOffsetTest(Config config) throws Throwable { 282c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar setupByConfig(config, true); 283c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar OrientationHelper orientationHelper = OrientationHelper 284c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .createOrientationHelper(mLayoutManager, config.mOrientation); 285c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Rect layoutBounds = getDecoratedRecyclerViewBounds(); 286c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try scrolling towards head, should not affect anything 287c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 288c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (config.mStackFromEnd) { 289c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1, 290c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.mOrientationHelper.getEnd() - 500); 291c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 292c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(0, 20); 293c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 294c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertRectSetsEqual(config + " trying to over scroll with offset should be no-op", 295c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar before, mLayoutManager.collectChildCoordinates()); 296c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try offsetting some visible children 297c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int testCount = 10; 298c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar while (testCount-- > 0) { 299c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // get middle child 300c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2); 301115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar final int position = mRecyclerView.getChildLayoutPosition(child); 302c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int startOffset = config.mReverseLayout ? 303c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 304c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getDecoratedEnd(child) 305c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 306c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getStartAfterPadding(); 307c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2 308c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : startOffset / 2; 309c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.expectLayouts(1); 310c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(position, scrollOffset); 311c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.waitForLayout(2); 312c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int finalOffset = config.mReverseLayout ? 313c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - orientationHelper 314c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getDecoratedEnd(child) 315c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar : orientationHelper.getDecoratedStart(child) - orientationHelper 316c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar .getStartAfterPadding(); 317c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(config + " scroll with offset on a visible child should work fine " + 318c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar " offset:" + finalOffset + " , existing offset:" + startOffset + ", " 319c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "child " + position, 320c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollOffset, finalOffset); 321c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 322c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 323c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // try scrolling to invisible children 324c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar testCount = 10; 325c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // we test above and below, one by one 326c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int offsetMultiplier = -1; 327c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar while (testCount-- > 0) { 328c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final TargetTuple target = findInvisibleTarget(config); 329c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final String logPrefix = config + " " + target; 330c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.expectLayouts(1); 331c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int offset = offsetMultiplier 332c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3; 333c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar scrollToPositionWithOffset(target.mPosition, offset); 334c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mLayoutManager.waitForLayout(2); 335c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final View child = mLayoutManager.findViewByPosition(target.mPosition); 336c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset 337c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + " should layout it", child); 338c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final Rect bounds = mLayoutManager.getViewBounds(child); 339c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (DEBUG) { 340c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in " 341c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + layoutBounds + " with offset " + offset); 342c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 343c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 344c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (config.mReverseLayout) { 345c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse " 346c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "layout, its end should align with recycler view's end - offset", 347c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getEndAfterPadding() - offset, 348c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getDecoratedEnd(child) 349c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ); 350c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 351c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal" 352c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + " layout its start should align with recycler view's start + " 353c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar + "offset", 354c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getStartAfterPadding() + offset, 355c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar orientationHelper.getDecoratedStart(child) 356c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ); 357c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 358c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar offsetMultiplier *= -1; 359c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 360c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 361c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 362c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar private TargetTuple findInvisibleTarget(Config config) { 363c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE; 364c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 365c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar View child = mLayoutManager.getChildAt(i); 366115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar int position = mRecyclerView.getChildLayoutPosition(child); 367c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (position < minPosition) { 368c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar minPosition = position; 369c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 370c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (position > maxPosition) { 371c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar maxPosition = position; 372c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 373c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 374c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int tailTarget = maxPosition + 375c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2; 376c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int headTarget = minPosition / 2; 377c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int target; 378c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar // where will the child come from ? 379c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int itemLayoutDirection; 380c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) { 381c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar target = tailTarget; 382c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END; 383c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } else { 384c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar target = headTarget; 385c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START; 386c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 387c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar if (DEBUG) { 388c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar Log.d(TAG, 389c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition); 390c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 391c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar return new TargetTuple(target, itemLayoutDirection); 392c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 393c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 394310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void stackFromEndTest(final Config config) throws Throwable { 395310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final FrameLayout container = getRecyclerViewContainer(); 396310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 397310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 398310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 399310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, 0, 0); 400310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 401310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 402310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 403310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(config, true); 404310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition(); 405310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition(); 406310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition(); 407310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition(); 408310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.expectLayouts(1); 409310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar // resize the recycler view to half 410310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar runTestOnUiThread(new Runnable() { 411310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 412310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void run() { 413310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mOrientation == HORIZONTAL) { 414310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, container.getWidth() / 2, 0); 415310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } else { 416310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar container.setPadding(0, 0, 0, container.getWidth() / 2); 417310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 418310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 419310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar }); 420310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForLayout(1); 421310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mStackFromEnd) { 422310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last visible position should not change.", 423310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition()); 424310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last completely visible position should not change", 425310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar lastCompletelyVisibleItemPosition, 426310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition()); 427310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } else { 428310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: first visible position should not change.", 429310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition()); 430310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("[" + config + "]: last completely visible position should not change", 431310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar firstCompletelyVisibleItemPosition, 432310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition()); 433310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 434310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 435310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 4366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void testScrollToPositionWithPredictive() throws Throwable { 4376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET); 4386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 4396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(3, 20); 4406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 4416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 4426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar LinearLayoutManager.INVALID_OFFSET); 4436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 4446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10); 4456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 4476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset) 4486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throws Throwable { 449310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 4506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 4516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.mOnLayoutListener = new OnLayoutListener() { 4526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 4536e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state) { 4546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (state.isPreLayout()) { 4556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position should still be pending", 4566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollPosition, mLayoutManager.mPendingScrollPosition); 4576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 4586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position offset should still be pending", 4596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset, mLayoutManager.mPendingScrollPositionOffset); 4606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 4626e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar RecyclerView.ViewHolder vh = 463115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar mRecyclerView.findViewHolderForLayoutPosition(scrollPosition); 4646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertNotNull("scroll to position should work", vh); 4656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 4666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("scroll offset should be applied properly", 4676e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getPaddingTop() + scrollOffset + 4686e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar ((RecyclerView.LayoutParams) vh.itemView 4696e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar .getLayoutParams()).topMargin, 4706e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getDecoratedTop(vh.itemView)); 4716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }; 4756e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.expectLayouts(2); 4766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar runTestOnUiThread(new Runnable() { 4776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 4786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void run() { 4796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 4806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mTestAdapter.addAndNotify(0, 1); 4816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) { 4826e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPosition(scrollPosition); 4836e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 4846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPositionWithOffset(scrollPosition, 4856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset); 4866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 4886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable throwable) { 4896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throwable.printStackTrace(); 4906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 4926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4936e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }); 4946e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.waitForLayout(2); 4956e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar checkForMainThreadException(); 4966e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 4976e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 4988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar private void waitForFirstLayout() throws Throwable { 499d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(1); 5008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setRecyclerView(mRecyclerView); 501d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 502d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 503d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 504504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void testRecycleDuringAnimations() throws Throwable { 505504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final AtomicInteger childCount = new AtomicInteger(0); 506504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestAdapter adapter = new TestAdapter(300) { 507504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 508504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public TestViewHolder onCreateViewHolder(ViewGroup parent, 509504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int viewType) { 510504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 511504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 512504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 513504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder); 514504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 515504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return testViewHolder; 516504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 517504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 518310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(300) 519504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar .adapter(adapter), true); 520504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 521504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() { 522504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 523504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void putRecycledView(RecyclerView.ViewHolder scrap) { 524504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.putRecycledView(scrap); 525504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int cnt = childCount.decrementAndGet(); 526504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 527504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap); 528504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 529504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 530504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 531504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 532504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public RecyclerView.ViewHolder getRecycledView(int viewType) { 533504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType); 534504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (recycledView != null) { 535504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 536504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 537504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView); 538504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 539504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 540504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return recycledView; 541504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 542504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 543504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500); 544504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.setRecycledViewPool(pool); 545504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 546504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 547504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now keep adding children to trigger more children being created etc. 548504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 549504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(15, 1); 550504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(15); 551504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 552504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 553504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 554504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 555504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 556504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 557504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now trigger lots of add again, followed by a scroll to position 558504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 559504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(5 + (i % 3) * 3, 1); 560504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(25); 561504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 562504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20); 563504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 564504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 565504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 566504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 567504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 568504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 569d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 570d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void testGetFirstLastChildrenTest() throws Throwable { 571d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (Config config : mBaseVariations) { 572d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar getFirstLastChildrenTest(config); 573d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 574d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 575d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 57649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void testDontRecycleChildrenOnDetach() throws Throwable { 57749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(false), true); 57849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar runTestOnUiThread(new Runnable() { 57949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 58049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 58149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 58249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.setLayoutManager(new TestLayoutManager()); 58349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("No views are recycled", recyclerSize, 58449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 58549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 58649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 58749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 58849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 58949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void testRecycleChildrenOnDetach() throws Throwable { 59049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(true), true); 59149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar final int childCount = mLayoutManager.getChildCount(); 59249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar runTestOnUiThread(new Runnable() { 59349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 59449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 59549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 59649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews( 59749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mTestAdapter.getItemViewType(0), recyclerSize + childCount); 59849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.setLayoutManager(new TestLayoutManager()); 59949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("All children should be recycled", childCount + recyclerSize, 60049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 60149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 60249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 60349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 60449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 605d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void getFirstLastChildrenTest(final Config config) throws Throwable { 6068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setupByConfig(config, true); 607d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Runnable viewInBoundsTest = new Runnable() { 608d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 609d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 610d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren(); 611d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final String boundsLog = mLayoutManager.getBoundsLog(); 612d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals(config + ":\nfirst visible child should match traversal result\n" 613d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.firstVisiblePosition, 614d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findFirstVisibleItemPosition() 615d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 616d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals( 617d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config + ":\nfirst fully visible child should match traversal result\n" 618d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.firstFullyVisiblePosition, 619d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPosition() 620d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 621d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 622d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals(config + ":\nlast visible child should match traversal result\n" 623d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.lastVisiblePosition, 624d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findLastVisibleItemPosition() 625d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 626d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals( 627d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar config + ":\nlast fully visible child should match traversal result\n" 628d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar + boundsLog, visibleChildren.lastFullyVisiblePosition, 629d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 630d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ); 631d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 632d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }; 633d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 634d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching 635d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // case 636d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount(); 637d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(new Runnable() { 638d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 639d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 640d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.smoothScrollToPosition(scrollPosition); 641d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 642d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }); 643d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar while (mLayoutManager.isSmoothScrolling() || 644d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 645d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 6468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar Thread.sleep(400); 647d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 648d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // delete all items 649d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(2); 650d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount()); 651d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 652d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // test empty case 653d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 654d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar // set a new adapter with huge items to test full bounds check 655d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.expectLayouts(1); 656d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace(); 657d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final TestAdapter newAdapter = new TestAdapter(100) { 658d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 659d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void onBindViewHolder(TestViewHolder holder, 660d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int position) { 661d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super.onBindViewHolder(holder, position); 662310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (config.mOrientation == HORIZONTAL) { 663d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar holder.itemView.setMinimumWidth(totalSpace + 5); 664d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } else { 665d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar holder.itemView.setMinimumHeight(totalSpace + 5); 666d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 667d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 668d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }; 669d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(new Runnable() { 670d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 671d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void run() { 672d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mRecyclerView.setAdapter(newAdapter); 673d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 674d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar }); 675d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mLayoutManager.waitForLayout(2); 676d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar runTestOnUiThread(viewInBoundsTest); 677d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 678d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 6798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void testSavedState() throws Throwable { 6808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{ 6818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 6828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 6848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // do nothing 6858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 6878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 6898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "doing nothing"; 6908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 6928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 6938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 6948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 6958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 6968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPosition(mTestAdapter.getItemCount() * 3 / 4); 6978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 6988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 6998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position"; 7038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 7058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 7068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 7088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 7098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3, 7108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 50); 7118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 7128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position with positive offset"; 7178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 7198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostLayoutRunnable() { 7208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() throws Throwable { 7228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 7238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3, 7248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar -50); 7258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 7268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "scroll to position with negative offset"; 7318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }; 7348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{ 7368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 7378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Doing nothing"; 7408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 7428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 7438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 7458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // update config as well so that restore assertions will work 7468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mOrientation = 1 - config.mOrientation; 7478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setOrientation(config.mOrientation); 7488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 7528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return config.mItemCount == 0; 7538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing orientation"; 7588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 7608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 7618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 7638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mStackFromEnd = !config.mStackFromEnd; 7648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 7658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 7698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return true; //stack from end should not move items on change 7708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing stack from end"; 7758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }, 7778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar new PostRestoreRunnable() { 7788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 7808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mReverseLayout = !config.mReverseLayout; 7818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.setReverseLayout(config.mReverseLayout); 7828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 7868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return config.mItemCount == 0; 7878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 7888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 7898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 7908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public String describe() { 7918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return "Changing reverse layout"; 7928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 79375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar }, 79475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar new PostRestoreRunnable() { 79549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 79649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar void onAfterRestore(Config config) throws Throwable { 79749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach; 79849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 79949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 80049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 80149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 80249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar boolean shouldLayoutMatch(Config config) { 80349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar return true; 80449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 80549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 80649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 80749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar String describe() { 808b97e8219784e623526bc3c6077a698d608f04fd9Yigit Boyar return "Change should recycle children"; 80949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 81049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }, 81149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar new PostRestoreRunnable() { 81275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar int position; 81375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 81475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterRestore(Config config) throws Throwable { 81575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar position = mTestAdapter.getItemCount() / 2; 81675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar mLayoutManager.scrollToPosition(position); 81775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 81875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 81975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 82075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar boolean shouldLayoutMatch(Config config) { 82175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar return mTestAdapter.getItemCount() == 0; 82275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 82375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 82475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 82575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar String describe() { 826aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar return "Scroll to position " + position ; 82775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 82875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 82975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar @Override 83075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterReLayout(Config config) { 83175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar if (mTestAdapter.getItemCount() > 0) { 832aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar assertEquals(config + ":scrolled view should be last completely visible", 83375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar position, 834aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar config.mStackFromEnd ? 835aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPosition() 836aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar : mLayoutManager.findFirstCompletelyVisibleItemPosition()); 83775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 83875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar } 8398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }; 84175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar boolean[] waitForLayoutOptions = new boolean[]{true, false}; 84249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300); 84349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true); 84449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar for (Config config : variations) { 8458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) { 8468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (boolean waitForLayout : waitForLayoutOptions) { 8478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) { 8488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable, 8498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postRestoreRunnable); 8508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 8518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void savedStateTest(Config config, boolean waitForLayout, 8598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation) 8608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throws Throwable { 8618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (DEBUG) { 8628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " + 8638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config + " post layout action " + postLayoutOperation.describe() + 8648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar "post restore action " + postRestoreOperation.describe()); 8658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setupByConfig(config, false); 8678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForLayout) { 8688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar waitForFirstLayout(); 8698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postLayoutOperation.run(); 8708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 8718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 8728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Parcelable savedState = mRecyclerView.onSaveInstanceState(); 8738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // we append a suffix to the parcelable to test out of bounds 8748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar String parcelSuffix = UUID.randomUUID().toString(); 8758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Parcel parcel = Parcel.obtain(); 8768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedState.writeToParcel(parcel, 0); 8778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.writeString(parcelSuffix); 8788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 8798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // reset for reading 8808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.setDataPosition(0); 8818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // re-create 8828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); 8838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar removeRecyclerView(); 8848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 8858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar RecyclerView restored = new RecyclerView(getActivity()); 8868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // this config should be no op. 8878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), 888b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar config.mOrientation, config.mReverseLayout); 889b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 8908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.setLayoutManager(mLayoutManager); 8918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // use the same adapter for Rect matching 8928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.setAdapter(mTestAdapter); 8938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar restored.onRestoreInstanceState(savedState); 8948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar postRestoreOperation.onAfterRestore(config); 8958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals("Parcel reading should not go out of bounds", parcelSuffix, 8968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar parcel.readString()); 8978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.expectLayouts(1); 8988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar setRecyclerView(restored); 8998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.waitForLayout(2); 9008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // calculate prefix here instead of above to include post restore changes 9018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() + 9028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar "\npostRestore:" + postRestoreOperation.describe() + "\n"; 9038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, reverse layout should be preserved", 9048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mReverseLayout, mLayoutManager.getReverseLayout()); 9058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, orientation should be preserved", 9068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mOrientation, mLayoutManager.getOrientation()); 9078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(logPrefix + " on saved state, stack from end should be preserved", 9088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar config.mStackFromEnd, mLayoutManager.getStackFromEnd()); 9098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (waitForLayout) { 9108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (postRestoreOperation.shouldLayoutMatch(config)) { 9118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsEqual( 9128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar logPrefix + ": on restore, previous view positions should be preserved", 9138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar before, mLayoutManager.collectChildCoordinates()); 9148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } else { 9158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsNotEqual( 9168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar logPrefix 91749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar + ": on restore with changes, previous view positions should NOT " 91849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar + "be preserved", 9198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar before, mLayoutManager.collectChildCoordinates()); 9208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 92175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar postRestoreOperation.onAfterReLayout(config); 9228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void scrollToPositionWithOffset(final int position, final int offset) throws Throwable { 9268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 9278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 9288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 9298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mLayoutManager.scrollToPositionWithOffset(position, offset); 9308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 9328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void assertRectSetsNotEqual(String message, Map<Item, Rect> before, 9358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> after) { 9368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Throwable throwable = null; 9378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar try { 9388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertRectSetsEqual("NOT " + message, before, after); 9398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } catch (Throwable t) { 9408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throwable = t; 9418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertNotNull(message + "\ntwo layout should be different", throwable); 9438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 9458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) { 946668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar StringBuilder sb = new StringBuilder(); 947668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("checking rectangle equality."); 948668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("before:\n"); 949668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 950668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 951668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar } 952668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append("after:\n"); 953668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar for (Map.Entry<Item, Rect> entry : after.entrySet()) { 954668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 9558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 956668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar message = message + "\n" + sb.toString(); 9578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(message + ":\nitem counts should be equal", before.size() 9588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar , after.size()); 9598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 9608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Rect afterRect = after.get(entry.getKey()); 9618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertNotNull(message + ":\nSame item should be visible after simple re-layout", 9628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar afterRect); 9638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar assertEquals(message + ":\nItem should be laid out at the same coordinates", 9648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar entry.getValue(), afterRect); 9658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 9678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 968a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar public void testAccessibilityPositions() throws Throwable { 969a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 970a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityDelegateCompat delegateCompat = mRecyclerView 971a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar .getCompatAccessibilityDelegate(); 972a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityEvent event = AccessibilityEvent.obtain(); 973a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar runTestOnUiThread(new Runnable() { 974a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar @Override 975a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar public void run() { 976a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event); 977a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 978a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar }); 979a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityRecordCompat record = AccessibilityEventCompat 980a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar .asRecord(event); 981a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have first position", 982a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar record.getFromIndex(), 983a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findFirstVisibleItemPosition()); 984a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have last position", 985a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar record.getToIndex(), 986a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findLastVisibleItemPosition()); 987a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 988a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar 989d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar static class VisibleChildren { 990d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 991d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int firstVisiblePosition = RecyclerView.NO_POSITION; 992d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 993d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int firstFullyVisiblePosition = RecyclerView.NO_POSITION; 994d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 995d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int lastVisiblePosition = RecyclerView.NO_POSITION; 996d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 997d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int lastFullyVisiblePosition = RecyclerView.NO_POSITION; 998d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 999d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 1000d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String toString() { 1001d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return "VisibleChildren{" + 1002d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar "firstVisiblePosition=" + firstVisiblePosition + 1003d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", firstFullyVisiblePosition=" + firstFullyVisiblePosition + 1004d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", lastVisiblePosition=" + lastVisiblePosition + 1005d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", lastFullyVisiblePosition=" + lastFullyVisiblePosition + 1006d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar '}'; 1007d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1008d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1009d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract private class PostLayoutRunnable { 10118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract void run() throws Throwable; 10138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract String describe(); 10158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract private class PostRestoreRunnable { 10188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void onAfterRestore(Config config) throws Throwable { 10208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar abstract String describe(); 10238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar boolean shouldLayoutMatch(Config config) { 10258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return true; 10268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 102775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 102875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar void onAfterReLayout(Config config) { 102975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar 103075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar }; 10318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar class WrappedLinearLayoutManager extends LinearLayoutManager { 1034d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1035d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar CountDownLatch layoutLatch; 1036d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar OrientationHelper mSecondaryOrientation; 10388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar OnLayoutListener mOnLayoutListener; 10406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 1041d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 1042d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar super(context, orientation, reverseLayout); 1043d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1044d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1045d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void expectLayouts(int count) { 1046d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar layoutLatch = new CountDownLatch(count); 1047d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1048d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1049d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void waitForLayout(long timeout) throws InterruptedException { 1050d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar waitForLayout(timeout, TimeUnit.SECONDS); 1051d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1052d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 10538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 10548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void setOrientation(int orientation) { 10558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar super.setOrientation(orientation); 10568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation = null; 10578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 10598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 1060504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) { 1061504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 1062504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child)); 1063504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1064504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.removeAndRecycleView(child, recycler); 1065504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1066504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1067504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 1068504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) { 1069504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 1070504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index))); 1071504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1072504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.removeAndRecycleViewAt(index, recycler); 1073504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1074504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1075504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 107694c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar void ensureLayoutState() { 107794c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar super.ensureLayoutState(); 10788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (mSecondaryOrientation == null) { 107994c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar mSecondaryOrientation = OrientationHelper.createOrientationHelper(this, 108094c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar 1 - getOrientation()); 10818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 10838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 1084d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException { 10856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit); 1086d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar assertEquals("all expected layouts should be executed at the expected time", 1087d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 0, layoutLatch.getCount()); 10886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar getInstrumentation().waitForIdleSync(); 1089d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1090d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1091d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String getBoundsLog() { 1092d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar StringBuilder sb = new StringBuilder(); 1093d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding()) 1094d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding()); 1095d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("\nchildren bounds\n"); 1096d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childCount = getChildCount(); 1097d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (int i = 0; i < childCount; i++) { 1098d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar View child = getChildAt(i); 1099d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child)) 1100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append("[").append("start:").append( 1101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientationHelper.getDecoratedStart(child)).append(", end:") 1102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n"); 1103d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1104d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return sb.toString(); 1105d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1106d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1107310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException { 1108310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 1109310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (itemAnimator == null) { 1110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar return; 1111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 1112310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final CountDownLatch latch = new CountDownLatch(1); 1113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final boolean running = itemAnimator.isRunning( 1114310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 1115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar @Override 1116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar public void onAnimationsFinished() { 1117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar latch.countDown(); 1118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 1119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 1120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar ); 1121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar if (running) { 1122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar latch.await(timeoutInSeconds, TimeUnit.SECONDS); 1123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 1124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 1125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 1126d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public VisibleChildren traverseAndFindVisibleChildren() { 1127d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int childCount = getChildCount(); 1128d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final VisibleChildren visibleChildren = new VisibleChildren(); 1129d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int start = mOrientationHelper.getStartAfterPadding(); 1130d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int end = mOrientationHelper.getEndAfterPadding(); 1131d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar for (int i = 0; i < childCount; i++) { 1132d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar View child = getChildAt(i); 1133d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childStart = mOrientationHelper.getDecoratedStart(child); 1134d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int childEnd = mOrientationHelper.getDecoratedEnd(child); 1135d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final boolean fullyVisible = childStart >= start && childEnd <= end; 1136d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final boolean hidden = childEnd <= start || childStart >= end; 1137d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (hidden) { 1138d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar continue; 1139d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1140d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar final int position = getPosition(child); 1141d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (fullyVisible) { 1142d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position < visibleChildren.firstFullyVisiblePosition || 1143d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) { 1144d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstFullyVisiblePosition = position; 1145d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1146d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1147d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position > visibleChildren.lastFullyVisiblePosition) { 1148d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.lastFullyVisiblePosition = position; 1149d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1150d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1151d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1152d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position < visibleChildren.firstVisiblePosition || 1153d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) { 1154d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.firstVisiblePosition = position; 1155d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1156d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1157d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar if (position > visibleChildren.lastVisiblePosition) { 1158d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar visibleChildren.lastVisiblePosition = position; 1159d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1160d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1161d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1162d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return visibleChildren; 1163d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1164d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 11658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Rect getViewBounds(View view) { 11668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (getOrientation() == HORIZONTAL) { 11678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return new Rect( 11688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedStart(view), 11698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedStart(view), 11708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedEnd(view), 11718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedEnd(view)); 11728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } else { 11738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return new Rect( 11748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedStart(view), 11758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedStart(view), 11768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mSecondaryOrientation.getDecoratedEnd(view), 11778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mOrientationHelper.getDecoratedEnd(view)); 11788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 11808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 11828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Map<Item, Rect> collectChildCoordinates() throws Throwable { 11838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>(); 11848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 11858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 11868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 11878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar final int childCount = getChildCount(); 11888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar for (int i = 0; i < childCount; i++) { 11898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar View child = getChildAt(i); 11908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child 11918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar .getLayoutParams(); 11928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar TestViewHolder vh = (TestViewHolder) lp.mViewHolder; 1193115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar items.put(vh.mBoundItem, getViewBounds(child)); 11948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 11978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return items; 11988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 11998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 1200d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 1201d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 12026e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 12036e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (mOnLayoutListener != null) { 12046e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mOnLayoutListener.before(recycler, state); 12056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 12066e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar super.onLayoutChildren(recycler, state); 12076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (mOnLayoutListener != null) { 12086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mOnLayoutListener.after(recycler, state); 12096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 12106e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable t) { 12116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar postExceptionToInstrumentation(t); 12126e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 1213d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar layoutLatch.countDown(); 1214d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 12156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 12166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 12176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 12186e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 12196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar static class OnLayoutListener { 12206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void before(RecyclerView.Recycler recycler, RecyclerView.State state){} 12216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state){} 1222d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1223d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 12248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar static class Config implements Cloneable { 1225d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 12268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar private static final int DEFAULT_ITEM_COUNT = 100; 1227d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1228d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar private boolean mStackFromEnd; 1229d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1230310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int mOrientation = VERTICAL; 1231d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1232d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar boolean mReverseLayout = false; 1233d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 123449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar boolean mRecycleChildrenOnDetach = false; 123549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 1236d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar int mItemCount = DEFAULT_ITEM_COUNT; 1237d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1238504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar TestAdapter mTestAdapter; 1239504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1240d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config(int orientation, boolean reverseLayout, boolean stackFromEnd) { 1241d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientation = orientation; 1242d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mReverseLayout = reverseLayout; 1243d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mStackFromEnd = stackFromEnd; 1244d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1245d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1246d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public Config() { 1247d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1248d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1249d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1250504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Config adapter(TestAdapter adapter) { 1251504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mTestAdapter = adapter; 1252504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return this; 1253504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1254504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 125549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) { 125649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecycleChildrenOnDetach = recycleChildrenOnDetach; 125749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar return this; 125849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 125949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 1260d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config orientation(int orientation) { 1261d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mOrientation = orientation; 1262d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1263d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1264d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1265d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config stackFromBottom(boolean stackFromBottom) { 1266d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mStackFromEnd = stackFromBottom; 1267d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1268d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1269d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1270d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar Config reverseLayout(boolean reverseLayout) { 1271d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mReverseLayout = reverseLayout; 1272d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1273d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1274d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 1275d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public Config itemCount(int itemCount) { 1276d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar mItemCount = itemCount; 1277d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return this; 1278d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1279d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 12808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar // required by convention 12818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 12828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public Object clone() throws CloneNotSupportedException { 12838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar return super.clone(); 12848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 12858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 1286d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar @Override 1287d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar public String toString() { 1288d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar return "Config{" + 1289d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar "mStackFromEnd=" + mStackFromEnd + 1290d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mOrientation=" + mOrientation + 1291d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mReverseLayout=" + mReverseLayout + 129249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach + 1293d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar ", mItemCount=" + mItemCount + 1294d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar '}'; 1295d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1296d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar } 1297542f1260934df280985294eaef1ec8469863281fYigit Boyar 1298542f1260934df280985294eaef1ec8469863281fYigit Boyar private static class WrappedRecyclerView extends RecyclerView { 1299542f1260934df280985294eaef1ec8469863281fYigit Boyar 1300542f1260934df280985294eaef1ec8469863281fYigit Boyar public WrappedRecyclerView(Context context) { 1301542f1260934df280985294eaef1ec8469863281fYigit Boyar super(context); 1302542f1260934df280985294eaef1ec8469863281fYigit Boyar init(context); 1303542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1304542f1260934df280985294eaef1ec8469863281fYigit Boyar 1305542f1260934df280985294eaef1ec8469863281fYigit Boyar public WrappedRecyclerView(Context context, AttributeSet attrs) { 1306542f1260934df280985294eaef1ec8469863281fYigit Boyar super(context, attrs); 1307542f1260934df280985294eaef1ec8469863281fYigit Boyar init(context); 1308542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1309542f1260934df280985294eaef1ec8469863281fYigit Boyar 1310542f1260934df280985294eaef1ec8469863281fYigit Boyar public WrappedRecyclerView(Context context, AttributeSet attrs, int defStyle) { 1311542f1260934df280985294eaef1ec8469863281fYigit Boyar super(context, attrs, defStyle); 1312542f1260934df280985294eaef1ec8469863281fYigit Boyar init(context); 1313542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1314542f1260934df280985294eaef1ec8469863281fYigit Boyar 1315542f1260934df280985294eaef1ec8469863281fYigit Boyar private void init(Context context) { 1316542f1260934df280985294eaef1ec8469863281fYigit Boyar initializeScrollbars(null); 1317542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1318542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1319d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar} 1320