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