1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/* 2999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Copyright (C) 2015 The Android Open Source Project 3999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 4999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * you may not use this file except in compliance with the License. 6999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * You may obtain a copy of the License at 7999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 8999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 10999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * See the License for the specific language governing permissions and 14999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * limitations under the License. 15999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */ 16999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpackage android.support.v7.widget; 17999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 18e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; 19e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.support.v7.widget.LinearLayoutManager.VERTICAL; 20e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 21e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 22e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 23e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertEquals; 243911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurskaimport static org.junit.Assert.assertFalse; 25e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertNotNull; 26e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 27e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static java.util.concurrent.TimeUnit.SECONDS; 28e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 29999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.content.Context; 30999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect; 313911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurskaimport android.support.annotation.Nullable; 32c587f7dba5a337169e854e235da59f595255d6ccAga Madurskaimport android.support.v4.util.Pair; 33999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log; 34999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.view.View; 354143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport android.view.ViewGroup; 36999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 37e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.CoreMatchers; 38e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.MatcherAssert; 39e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 40999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.lang.reflect.Field; 41999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.ArrayList; 42999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.LinkedHashMap; 43999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List; 44999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map; 45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.CountDownLatch; 46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.TimeUnit; 47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest { 49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static final boolean DEBUG = false; 51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static final String TAG = "LinearLayoutManagerTest"; 52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static List<Config> createBaseVariations() { 54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar List<Config> variations = new ArrayList<>(); 55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int orientation : new int[]{VERTICAL, HORIZONTAL}) { 56999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (boolean reverseLayout : new boolean[]{false, true}) { 57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (boolean stackFromBottom : new boolean[]{false, true}) { 584143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar for (boolean wrap : new boolean[]{false, true}) { 594143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar variations.add( 604143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap)); 614143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 624143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar 63999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return variations; 67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar WrappedLinearLayoutManager mLayoutManager; 70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar TestAdapter mTestAdapter; 71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static List<Config> addConfigVariation(List<Config> base, String fieldName, 73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Object... variations) 74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException { 75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar List<Config> newConfigs = new ArrayList<Config>(); 76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Field field = Config.class.getDeclaredField(fieldName); 77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Config config : base) { 78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Object variation : variations) { 79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config newConfig = (Config) config.clone(); 80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar field.set(newConfig, variation); 81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar newConfigs.add(newConfig); 82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return newConfigs; 85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable { 883911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska setupByConfig(config, waitForFirstLayout, null, null); 893911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 903911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 913911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska void setupByConfig(Config config, boolean waitForFirstLayout, 923911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska @Nullable RecyclerView.LayoutParams childLayoutParams, 933911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska @Nullable RecyclerView.LayoutParams parentLayoutParams) throws Throwable { 94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView = inflateWrappedRV(); 95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.setHasFixedSize(true); 973911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska mTestAdapter = config.mTestAdapter == null 983911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska ? new TestAdapter(config.mItemCount, childLayoutParams) 99999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : config.mTestAdapter; 100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.setAdapter(mTestAdapter); 101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation, 1023911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska config.mReverseLayout); 103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.setStackFromEnd(config.mStackFromEnd); 104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.setLayoutManager(mLayoutManager); 1064143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar if (config.mWrap) { 1074143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar mRecyclerView.setLayoutParams( 1084143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar new ViewGroup.LayoutParams( 109e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas config.mOrientation == HORIZONTAL ? WRAP_CONTENT : MATCH_PARENT, 110e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas config.mOrientation == VERTICAL ? WRAP_CONTENT : MATCH_PARENT 1114143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar ) 1124143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar ); 1134143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 1143911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska if (parentLayoutParams != null) { 1153911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska mRecyclerView.setLayoutParams(parentLayoutParams); 1163911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 1173911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (waitForFirstLayout) { 119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar waitForFirstLayout(); 120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset) 124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throws Throwable { 125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.mOnLayoutListener = new OnLayoutListener() { 128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state) { 130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (state.isPreLayout()) { 131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("pending scroll position should still be pending", 132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollPosition, mLayoutManager.mPendingScrollPosition); 133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("pending scroll position offset should still be pending", 135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollOffset, mLayoutManager.mPendingScrollPositionOffset); 136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar RecyclerView.ViewHolder vh = 139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecyclerView.findViewHolderForLayoutPosition(scrollPosition); 140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull("scroll to position should work", vh); 141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("scroll offset should be applied properly", 143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.getPaddingTop() + scrollOffset + 144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ((RecyclerView.LayoutParams) vh.itemView 145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getLayoutParams()).topMargin, 146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.getDecoratedTop(vh.itemView)); 147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(2); 152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(new Runnable() { 153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar try { 156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mTestAdapter.addAndNotify(0, 1); 157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) { 158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.scrollToPosition(scrollPosition); 159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.scrollToPositionWithOffset(scrollPosition, 161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollOffset); 162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } catch (Throwable throwable) { 165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throwable.printStackTrace(); 166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar checkForMainThreadException(); 172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected void waitForFirstLayout() throws Throwable { 175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setRecyclerView(mRecyclerView); 177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void scrollToPositionWithOffset(final int position, final int offset) throws Throwable { 181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(new Runnable() { 182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.scrollToPositionWithOffset(position, offset); 185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void assertRectSetsNotEqual(String message, Map<Item, Rect> before, 190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Map<Item, Rect> after, boolean strictItemEquality) { 191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Throwable throwable = null; 192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar try { 193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsEqual("NOT " + message, before, after, strictItemEquality); 194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } catch (Throwable t) { 195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throwable = t; 196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull(message + "\ntwo layout should be different", throwable); 198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) { 201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsEqual(message, before, after, true); 202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after, 205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean strictItemEquality) { 206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar StringBuilder sb = new StringBuilder(); 207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("checking rectangle equality.\n"); 208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("before:\n"); 209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("after:\n"); 213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Map.Entry<Item, Rect> entry : after.entrySet()) { 214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n"); 215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar message = message + "\n" + sb.toString(); 217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(message + ":\nitem counts should be equal", before.size() 218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar , after.size()); 219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Item beforeItem = entry.getKey(); 221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect afterRect = null; 222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (strictItemEquality) { 223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar afterRect = after.get(beforeItem); 224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull(message + ":\nSame item should be visible after simple re-layout", 225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar afterRect); 226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) { 228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Item afterItem = afterEntry.getKey(); 229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) { 230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar afterRect = afterEntry.getValue(); 231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar break; 232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertNotNull(message + ":\nItem with same adapter index should be visible " + 235999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "after simple re-layout", 236999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar afterRect); 237999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 238999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(message + ":\nItem should be laid out at the same coordinates", 239999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar entry.getValue(), afterRect); 240999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 241999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 242999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 243999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar static class VisibleChildren { 244999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 245999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int firstVisiblePosition = RecyclerView.NO_POSITION; 246999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 247999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int firstFullyVisiblePosition = RecyclerView.NO_POSITION; 248999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 249999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int lastVisiblePosition = RecyclerView.NO_POSITION; 250999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 251999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int lastFullyVisiblePosition = RecyclerView.NO_POSITION; 252999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 253999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 254999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String toString() { 255999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "VisibleChildren{" + 256999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "firstVisiblePosition=" + firstVisiblePosition + 257999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", firstFullyVisiblePosition=" + firstFullyVisiblePosition + 258999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", lastVisiblePosition=" + lastVisiblePosition + 259999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", lastFullyVisiblePosition=" + lastFullyVisiblePosition + 260999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar '}'; 261999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 262999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 263999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 264999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar static class OnLayoutListener { 265999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 266999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void before(RecyclerView.Recycler recycler, RecyclerView.State state) { 267999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 268999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 269999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state) { 270999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 271999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 272999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 273999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar static class Config implements Cloneable { 274999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 275c587f7dba5a337169e854e235da59f595255d6ccAga Madurska static final int DEFAULT_ITEM_COUNT = 250; 276999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 277999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean mStackFromEnd; 278999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 279999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int mOrientation = VERTICAL; 280999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 281999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean mReverseLayout = false; 282999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 283999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean mRecycleChildrenOnDetach = false; 284999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 285999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int mItemCount = DEFAULT_ITEM_COUNT; 286999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 2874143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar boolean mWrap = false; 2884143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar 289999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar TestAdapter mTestAdapter; 290999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 291999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config(int orientation, boolean reverseLayout, boolean stackFromEnd) { 292999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientation = orientation; 293999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mReverseLayout = reverseLayout; 294999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mStackFromEnd = stackFromEnd; 295999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public Config() { 298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config adapter(TestAdapter adapter) { 302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mTestAdapter = adapter; 303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) { 307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mRecycleChildrenOnDetach = recycleChildrenOnDetach; 308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config orientation(int orientation) { 312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientation = orientation; 313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config stackFromBottom(boolean stackFromBottom) { 317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mStackFromEnd = stackFromBottom; 318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Config reverseLayout(boolean reverseLayout) { 322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mReverseLayout = reverseLayout; 323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public Config itemCount(int itemCount) { 327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mItemCount = itemCount; 328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return this; 329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // required by convention 332999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 333999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public Object clone() throws CloneNotSupportedException { 334999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return super.clone(); 335999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 336999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 337999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 338999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String toString() { 339999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Config{" + 340999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "mStackFromEnd=" + mStackFromEnd + 341999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", mOrientation=" + mOrientation + 342999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", mReverseLayout=" + mReverseLayout + 343999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach + 344999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ", mItemCount=" + mItemCount + 3454143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar ", wrap=" + mWrap + 346999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar '}'; 347999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 3484143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar 3494143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar public Config wrap(boolean wrap) { 3504143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar mWrap = wrap; 3514143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar return this; 3524143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar } 353999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 354999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 355999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar class WrappedLinearLayoutManager extends LinearLayoutManager { 356999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 357999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar CountDownLatch layoutLatch; 358c587f7dba5a337169e854e235da59f595255d6ccAga Madurska CountDownLatch snapLatch; 359ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik CountDownLatch prefetchLatch; 3603911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska CountDownLatch callbackLatch; 361ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik 362999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar OrientationHelper mSecondaryOrientation; 363999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 364999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar OnLayoutListener mOnLayoutListener; 365999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 3663911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska RecyclerView.OnScrollListener mCallbackListener = new RecyclerView.OnScrollListener() { 3673911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 3683911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska @Override 3693911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 3703911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska super.onScrollStateChanged(recyclerView, newState); 3713911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska callbackLatch.countDown(); 3723911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska if (callbackLatch.getCount() == 0L) { 3733911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska removeOnScrollListener(this); 3743911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 3753911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 3763911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska }; 3773911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 378999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 379999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super(context, orientation, reverseLayout); 380999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 381999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 382999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void expectLayouts(int count) { 383999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutLatch = new CountDownLatch(count); 384999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 385999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 3863911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska public void expectCallbacks(int count) throws Throwable { 3873911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska callbackLatch = new CountDownLatch(count); 3883911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska mRecyclerView.addOnScrollListener(mCallbackListener); 3893911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 3903911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 3913911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska private void removeOnScrollListener(RecyclerView.OnScrollListener listener) { 3923911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska mRecyclerView.removeOnScrollListener(listener); 3933911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 3943911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 39566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar public void waitForLayout(int seconds) throws Throwable { 39666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS); 39766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar checkForMainThreadException(); 39866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar MatcherAssert.assertThat("all layouts should complete on time", 39966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar layoutLatch.getCount(), CoreMatchers.is(0L)); 40066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar // use a runnable to ensure RV layout is finished 40166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar getInstrumentation().runOnMainSync(new Runnable() { 40266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar @Override 40366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar public void run() { 40466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar } 40566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar }); 406999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 407999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 4083911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska public void assertNoCallbacks(String msg, long timeout) throws Throwable { 4093911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska callbackLatch.await(timeout, TimeUnit.SECONDS); 4103911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska long latchCount = callbackLatch.getCount(); 4113911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska assertFalse(msg + " :" + latchCount, latchCount == 0); 4123911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska removeOnScrollListener(mCallbackListener); 4133911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska } 4143911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska 415ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik public void expectPrefetch(int count) { 416ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik prefetchLatch = new CountDownLatch(count); 417ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik } 418ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik 419ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik public void waitForPrefetch(int seconds) throws Throwable { 420ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik prefetchLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS); 421ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik checkForMainThreadException(); 422ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik MatcherAssert.assertThat("all prefetches should complete on time", 423ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik prefetchLatch.getCount(), CoreMatchers.is(0L)); 424ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik // use a runnable to ensure RV layout is finished 425ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik getInstrumentation().runOnMainSync(new Runnable() { 426ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik @Override 427ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik public void run() { 428ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik } 429ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik }); 430ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik } 431ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik 432c587f7dba5a337169e854e235da59f595255d6ccAga Madurska public void expectIdleState(int count) { 433c587f7dba5a337169e854e235da59f595255d6ccAga Madurska snapLatch = new CountDownLatch(count); 434c587f7dba5a337169e854e235da59f595255d6ccAga Madurska mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 435c587f7dba5a337169e854e235da59f595255d6ccAga Madurska @Override 436c587f7dba5a337169e854e235da59f595255d6ccAga Madurska public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 437c587f7dba5a337169e854e235da59f595255d6ccAga Madurska super.onScrollStateChanged(recyclerView, newState); 438c587f7dba5a337169e854e235da59f595255d6ccAga Madurska if (newState == RecyclerView.SCROLL_STATE_IDLE) { 439c587f7dba5a337169e854e235da59f595255d6ccAga Madurska snapLatch.countDown(); 440c587f7dba5a337169e854e235da59f595255d6ccAga Madurska if (snapLatch.getCount() == 0L) { 441c587f7dba5a337169e854e235da59f595255d6ccAga Madurska mRecyclerView.removeOnScrollListener(this); 442c587f7dba5a337169e854e235da59f595255d6ccAga Madurska } 443c587f7dba5a337169e854e235da59f595255d6ccAga Madurska } 444c587f7dba5a337169e854e235da59f595255d6ccAga Madurska } 445c587f7dba5a337169e854e235da59f595255d6ccAga Madurska }); 446c587f7dba5a337169e854e235da59f595255d6ccAga Madurska } 447c587f7dba5a337169e854e235da59f595255d6ccAga Madurska 448c587f7dba5a337169e854e235da59f595255d6ccAga Madurska public void waitForSnap(int seconds) throws Throwable { 449c587f7dba5a337169e854e235da59f595255d6ccAga Madurska snapLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS); 450c587f7dba5a337169e854e235da59f595255d6ccAga Madurska checkForMainThreadException(); 451c587f7dba5a337169e854e235da59f595255d6ccAga Madurska MatcherAssert.assertThat("all scrolling should complete on time", 452c587f7dba5a337169e854e235da59f595255d6ccAga Madurska snapLatch.getCount(), CoreMatchers.is(0L)); 453c587f7dba5a337169e854e235da59f595255d6ccAga Madurska // use a runnable to ensure RV layout is finished 454c587f7dba5a337169e854e235da59f595255d6ccAga Madurska getInstrumentation().runOnMainSync(new Runnable() { 455c587f7dba5a337169e854e235da59f595255d6ccAga Madurska @Override 4563911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska public void run() {} 457c587f7dba5a337169e854e235da59f595255d6ccAga Madurska }); 458c587f7dba5a337169e854e235da59f595255d6ccAga Madurska } 459c587f7dba5a337169e854e235da59f595255d6ccAga Madurska 460999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 461999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void setOrientation(int orientation) { 462999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.setOrientation(orientation); 463999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation = null; 464999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 465999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 466999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 467999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) { 468999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 469999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child)); 470999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 471999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.removeAndRecycleView(child, recycler); 472999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 473999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 474999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 475999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) { 476999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 477999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, 478999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index))); 479999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 480999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.removeAndRecycleViewAt(index, recycler); 481999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 482999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 483999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 484999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void ensureLayoutState() { 485999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.ensureLayoutState(); 486999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mSecondaryOrientation == null) { 487999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation = OrientationHelper.createOrientationHelper(this, 488999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 1 - getOrientation()); 489999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 490999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 491999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 492999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 493999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar LayoutState createLayoutState() { 494999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return new LayoutState() { 495999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 496999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View next(RecyclerView.Recycler recycler) { 497999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean hadMore = hasMore(mRecyclerView.mState); 498999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int position = mCurrentPosition; 499999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View next = super.next(recycler); 500999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("if has more, should return a view", hadMore, next != null); 501999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("position of the returned view must match current position", 502999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition()); 503999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return next; 504999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 505999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 506999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 507999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 508999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String getBoundsLog() { 509999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar StringBuilder sb = new StringBuilder(); 510999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding()) 511999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding()); 512999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("\nchildren bounds\n"); 513999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int childCount = getChildCount(); 514999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int i = 0; i < childCount; i++) { 515999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View child = getChildAt(i); 516999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child)) 517999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .append("[").append("start:").append( 518999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientationHelper.getDecoratedStart(child)).append(", end:") 519999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n"); 520999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 521999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return sb.toString(); 522999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 523999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 524999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException { 525999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 526999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (itemAnimator == null) { 527999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return; 528999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 529999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final CountDownLatch latch = new CountDownLatch(1); 530999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean running = itemAnimator.isRunning( 531999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 532999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 533999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void onAnimationsFinished() { 534999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar latch.countDown(); 535999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 536999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 537999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar ); 538999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (running) { 539999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar latch.await(timeoutInSeconds, TimeUnit.SECONDS); 540999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 541999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 542999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 543999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public VisibleChildren traverseAndFindVisibleChildren() { 544999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int childCount = getChildCount(); 545999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final VisibleChildren visibleChildren = new VisibleChildren(); 546999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int start = mOrientationHelper.getStartAfterPadding(); 547999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int end = mOrientationHelper.getEndAfterPadding(); 548999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int i = 0; i < childCount; i++) { 549999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View child = getChildAt(i); 550999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int childStart = mOrientationHelper.getDecoratedStart(child); 551999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int childEnd = mOrientationHelper.getDecoratedEnd(child); 552999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean fullyVisible = childStart >= start && childEnd <= end; 553999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean hidden = childEnd <= start || childStart >= end; 554999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (hidden) { 555999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar continue; 556999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 557999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int position = getPosition(child); 558999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (fullyVisible) { 559999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position < visibleChildren.firstFullyVisiblePosition || 560999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) { 561999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.firstFullyVisiblePosition = position; 562999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 563999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 564999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position > visibleChildren.lastFullyVisiblePosition) { 565999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.lastFullyVisiblePosition = position; 566999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 567999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 568999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 569999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position < visibleChildren.firstVisiblePosition || 570999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) { 571999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.firstVisiblePosition = position; 572999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 573999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 574999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (position > visibleChildren.lastVisiblePosition) { 575999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar visibleChildren.lastVisiblePosition = position; 576999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 577999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 578999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 579999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return visibleChildren; 580999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 581999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 582999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect getViewBounds(View view) { 583999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (getOrientation() == HORIZONTAL) { 584999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return new Rect( 585999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientationHelper.getDecoratedStart(view), 586999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation.getDecoratedStart(view), 587999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientationHelper.getDecoratedEnd(view), 588999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation.getDecoratedEnd(view)); 589999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 590999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return new Rect( 591999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation.getDecoratedStart(view), 592999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientationHelper.getDecoratedStart(view), 593999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mSecondaryOrientation.getDecoratedEnd(view), 594999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOrientationHelper.getDecoratedEnd(view)); 595999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 596999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 597999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 598999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 599999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Map<Item, Rect> collectChildCoordinates() throws Throwable { 600999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>(); 601999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar runTestOnUiThread(new Runnable() { 602999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 603999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 604999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int childCount = getChildCount(); 605999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect layoutBounds = new Rect(0, 0, 606999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.getWidth(), mLayoutManager.getHeight()); 607999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (int i = 0; i < childCount; i++) { 608999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar View child = getChildAt(i); 609999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child 610999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar .getLayoutParams(); 611999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar TestViewHolder vh = (TestViewHolder) lp.mViewHolder; 612999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Rect childBounds = getViewBounds(child); 613999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (new Rect(childBounds).intersect(layoutBounds)) { 614999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar items.put(vh.mBoundItem, childBounds); 615999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 616999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 617999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 618999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 619999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return items; 620999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 621999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 622999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 623999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 624999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar try { 625999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mOnLayoutListener != null) { 626999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOnLayoutListener.before(recycler, state); 627999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 628999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar super.onLayoutChildren(recycler, state); 629999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mOnLayoutListener != null) { 630999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mOnLayoutListener.after(recycler, state); 631999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 632999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } catch (Throwable t) { 633999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar postExceptionToInstrumentation(t); 634999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 635999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutLatch.countDown(); 636999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 637ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik 638ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik @Override 639ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) { 640ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik if (prefetchLatch != null) prefetchLatch.countDown(); 641ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik return super.gatherPrefetchIndices(dx, dy, state, outIndices); 642ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik } 643999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 644999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar} 645