1d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase/* 2d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Copyright (C) 2014 The Android Open Source Project 3d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * 4d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Licensed under the Apache License, Version 2.0 (the "License"); 5d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * you may not use this file except in compliance with the License. 6d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * You may obtain a copy of the License at 7d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * 8d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * http://www.apache.org/licenses/LICENSE-2.0 9d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * 10d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Unless required by applicable law or agreed to in writing, software 11d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * distributed under the License is distributed on an "AS IS" BASIS, 12d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * See the License for the specific language governing permissions and 14d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * limitations under the License. 15d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase */ 16d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 17d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haasepackage android.support.v7.widget; 18d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 19c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport android.graphics.Rect; 208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper; 21e2437126f0225135891544315ff72e54c7de0f59Yigit Boyarimport android.support.v4.view.ViewCompat; 22d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.test.ActivityInstrumentationTestCase2; 23d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log; 24d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View; 25d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup; 26310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout; 27d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView; 28d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 29d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList; 3090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.HashSet; 31d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List; 3290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.Set; 33d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch; 34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit; 3511b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger; 36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 37d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends 38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase ActivityInstrumentationTestCase2<TestActivity> { 39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase private static final String TAG = "RecyclerViewTest"; 41d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 42b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar private boolean mDebug; 43b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 44b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar protected RecyclerView mRecyclerView; 45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar protected AdapterHelper mAdapterHelper; 478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 487e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar Throwable mainThreadException; 497e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar 50d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public BaseRecyclerViewInstrumentationTest() { 51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar this(false); 52b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 54b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public BaseRecyclerViewInstrumentationTest(boolean debug) { 558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar super("android.support.v7.recyclerview", TestActivity.class); 56b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar mDebug = debug; 57b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 58b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 597e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar void checkForMainThreadException() throws Throwable { 607e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar if (mainThreadException != null) { 617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar throw mainThreadException; 627e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar 65e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar void setHasTransientState(final View view, final boolean value) { 66e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar try { 67e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar runTestOnUiThread(new Runnable() { 68e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar @Override 69e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar public void run() { 70e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar ViewCompat.setHasTransientState(view, value); 71e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 72e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar }); 73e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } catch (Throwable throwable) { 74e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar Log.e(TAG, "", throwable); 75e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 76e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 77e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar 780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar void setAdapter(final RecyclerView.Adapter adapter) throws Throwable { 790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar runTestOnUiThread(new Runnable() { 800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar @Override 810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar public void run() { 820b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar mRecyclerView.setAdapter(adapter); 830b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 840b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar }); 850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 860b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar 870b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar void swapAdapter(final RecyclerView.Adapter adapter, 880b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar final boolean removeAndRecycleExistingViews) throws Throwable { 890b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar runTestOnUiThread(new Runnable() { 900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar @Override 910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar public void run() { 92824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar try { 93824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews); 94824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar } catch (Throwable t) { 95824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar postExceptionToInstrumentation(t); 96824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar } 970b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 980b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar }); 99824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar checkForMainThreadException(); 1000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 1010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar 1027e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar void postExceptionToInstrumentation(Throwable t) { 1036e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (mainThreadException != null) { 1046e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar Log.e(TAG, "receiving another main thread exception. dropping.", t); 1056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 10690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar Log.e(TAG, "captured exception on main thread", t); 1076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mainThreadException = t; 1086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 1096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 1107e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar if (mRecyclerView != null && mRecyclerView 1117e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar .getLayoutManager() instanceof TestLayoutManager) { 1127e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager(); 1137e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar // finish all layouts so that we get the correct exception 1147e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar while (lm.layoutLatch.getCount() > 0) { 1157e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar lm.layoutLatch.countDown(); 1167e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 1177e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 1187e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 1197e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar 1208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar @Override 1218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar protected void tearDown() throws Exception { 1228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar if (mRecyclerView != null) { 1238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar try { 1248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar removeRecyclerView(); 1258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } catch (Throwable throwable) { 1268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar throwable.printStackTrace(); 1278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 1288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 1298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar getInstrumentation().waitForIdleSync(); 130ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar super.tearDown(); 131ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar 1326e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 1336e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar checkForMainThreadException(); 1346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Exception e) { 1356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throw e; 1366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable throwable) { 137ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar throw new Exception(throwable); 1386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 1398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 1408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 141c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public Rect getDecoratedRecyclerViewBounds() { 142c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar return new Rect( 143c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mRecyclerView.getPaddingLeft(), 144c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mRecyclerView.getPaddingTop(), 145c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(), 146c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar mRecyclerView.getPaddingTop() + mRecyclerView.getHeight() 147c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ); 148c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 149c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 150b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void removeRecyclerView() throws Throwable { 151d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar if (mRecyclerView == null) { 152d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar return; 153d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar } 15490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar if (!isMainThread()) { 15590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar getInstrumentation().waitForIdleSync(); 15690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 157b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar runTestOnUiThread(new Runnable() { 158b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar @Override 159b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void run() { 160abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar try { 161abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar final RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); 162abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar if (adapter instanceof AttachDetachCountingAdapter) { 163abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar ((AttachDetachCountingAdapter) adapter).getCounter() 164abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar .validateRemaining(mRecyclerView); 165abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar } 166abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar getActivity().mContainer.removeAllViews(); 167abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar } catch (Throwable t) { 168abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar postExceptionToInstrumentation(t); 16990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 170b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 171b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar }); 17290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mRecyclerView = null; 173b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 174b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 175504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar void waitForAnimations(int seconds) throws InterruptedException { 176504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final CountDownLatch latch = new CountDownLatch(2); 177504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar boolean running = mRecyclerView.mItemAnimator 178504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 179504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 180504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void onAnimationsFinished() { 181504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar latch.countDown(); 182504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 183504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }); 184504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (running) { 185504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar latch.await(seconds, TimeUnit.SECONDS); 186504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 187504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 188504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void setRecyclerView(final RecyclerView recyclerView) throws Throwable { 1900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar setRecyclerView(recyclerView, true); 1910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 1920b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool) 1930b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar throws Throwable { 194e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar setRecyclerView(recyclerView, true, true); 195e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar } 196e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool, 197e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar boolean addPositionCheckItemAnimator) 198e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar throws Throwable { 199b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar mRecyclerView = recyclerView; 2000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar if (assignDummyPool) { 2010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() { 2020b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar @Override 2030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar public RecyclerView.ViewHolder getRecycledView(int viewType) { 2040b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType); 2050b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar if (viewHolder == null) { 2060b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar return null; 2070b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 2080b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND); 2090b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar viewHolder.mPosition = 200; 2100b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar viewHolder.mOldPosition = 300; 2110b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar viewHolder.mPreLayoutPosition = 500; 2120b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar return viewHolder; 2138da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar } 2148da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar 2150b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar @Override 2160b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar public void putRecycledView(RecyclerView.ViewHolder scrap) { 2170b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar super.putRecycledView(scrap); 2180b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 2190b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar }; 2200b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar mRecyclerView.setRecycledViewPool(pool); 2210b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar } 222e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar if (addPositionCheckItemAnimator) { 223e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { 224e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar @Override 225e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 226e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar RecyclerView.State state) { 227e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar RecyclerView.ViewHolder vh = parent.getChildViewHolder(view); 228e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar if (!vh.isRemoved()) { 229e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar assertNotSame("If getItemOffsets is called, child should have a valid" 230e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar + " adapter position unless it is removed", 231e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar vh.getAdapterPosition(), RecyclerView.NO_POSITION); 232e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar } 233e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar } 234e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar }); 235e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar } 2368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar mAdapterHelper = recyclerView.mAdapterHelper; 237b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar runTestOnUiThread(new Runnable() { 238b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar @Override 239b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void run() { 240b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar getActivity().mContainer.addView(recyclerView); 241b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 242b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar }); 243b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 244b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 245310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar protected FrameLayout getRecyclerViewContainer() { 246310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar return getActivity().mContainer; 247310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 248310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 249e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar public void requestLayoutOnUIThread(final View view) { 250e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar try { 251e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar runTestOnUiThread(new Runnable() { 252e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar @Override 253e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar public void run() { 254e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar view.requestLayout(); 255e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 256e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar }); 257e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } catch (Throwable throwable) { 258e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar Log.e(TAG, "", throwable); 259e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 260b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 261b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 262e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar public void scrollBy(final int dt) { 263e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar try { 264e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar runTestOnUiThread(new Runnable() { 265e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar @Override 266e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar public void run() { 267e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar if (mRecyclerView.getLayoutManager().canScrollHorizontally()) { 268e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar mRecyclerView.scrollBy(dt, 0); 269e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } else { 270e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar mRecyclerView.scrollBy(0, dt); 271e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 272b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 273e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 274e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar }); 275e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } catch (Throwable throwable) { 276e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar Log.e(TAG, "", throwable); 277e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar } 278d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 2808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void scrollToPosition(final int position) throws Throwable { 2818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 2828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 2838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 2848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.getLayoutManager().scrollToPosition(position); 2858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 2868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 2878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 2888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 2898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar void smoothScrollToPosition(final int position) 2908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar throws Throwable { 2918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Log.d(TAG, "SMOOTH scrolling to " + position); 2928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar runTestOnUiThread(new Runnable() { 2938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar @Override 2948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar public void run() { 2958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.smoothScrollToPosition(position); 2968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 2978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar }); 2988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar while (mRecyclerView.getLayoutManager().isSmoothScrolling() || 2998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) { 3008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar if (mDebug) { 3018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Log.d(TAG, "SMOOTH scrolling step"); 3028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 3038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Thread.sleep(200); 3048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 3058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar Log.d(TAG, "SMOOTH scrolling done"); 306504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 3078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar } 3088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 309d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase class TestViewHolder extends RecyclerView.ViewHolder { 310d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 311115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar Item mBoundItem; 312d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 313d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public TestViewHolder(View itemView) { 314d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase super(itemView); 315310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar itemView.setFocusable(true); 316d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 317504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 318504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 319504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public String toString() { 320115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar return super.toString() + " item:" + mBoundItem; 321504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 322d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 323d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 324d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase class TestLayoutManager extends RecyclerView.LayoutManager { 325d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 326d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase CountDownLatch layoutLatch; 327d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 328d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public void expectLayouts(int count) { 329d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase layoutLatch = new CountDownLatch(count); 330d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 331d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 332b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable { 333b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit); 334d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase assertEquals("all expected layouts should be executed at the expected time", 335d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 0, layoutLatch.getCount()); 336e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar getInstrumentation().waitForIdleSync(); 337d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 338d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 339b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void assertLayoutCount(int count, String msg, long timeout) throws Throwable { 340b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar layoutLatch.await(timeout, TimeUnit.SECONDS); 341b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar assertEquals(msg, count, layoutLatch.getCount()); 342b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 343b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 344b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void assertNoLayout(String msg, long timeout) throws Throwable { 345b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar layoutLatch.await(timeout, TimeUnit.SECONDS); 346b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar assertFalse(msg, layoutLatch.getCount() == 0); 347b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 348b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 349b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void waitForLayout(long timeout) throws Throwable { 3508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS); 351b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 352b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 353d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase @Override 354d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public RecyclerView.LayoutParams generateDefaultLayoutParams() { 355d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 356d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase ViewGroup.LayoutParams.WRAP_CONTENT); 357d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 358d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 359d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase void assertVisibleItemPositions() { 360d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase int i = getChildCount(); 361d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter(); 362d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase while (i-- > 0) { 363d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase View view = getChildAt(i); 364d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams(); 365115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem; 366b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar if (mDebug) { 367d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase Log.d(TAG, "testing item " + i); 368d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 3698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar if (!lp.isItemRemoved()) { 3708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view); 3718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar assertSame("item position in LP should match adapter value :" + vh, 3728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar testAdapter.mItems.get(vh.mPosition), item); 3738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 374d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 375d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 376d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 377b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar RecyclerView.LayoutParams getLp(View v) { 378b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar return (RecyclerView.LayoutParams) v.getLayoutParams(); 379b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 380b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 381f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar void layoutRange(RecyclerView.Recycler recycler, int start, int end) { 382fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar assertScrap(recycler); 383b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar if (mDebug) { 384d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase Log.d(TAG, "will layout items from " + start + " to " + end); 385d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 3865ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar int diff = end > start ? 1 : -1; 387504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int top = 0; 3885ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar for (int i = start; i != end; i+=diff) { 389b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar if (mDebug) { 390d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase Log.d(TAG, "laying out item " + i); 391d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 392d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase View view = recycler.getViewForPosition(i); 393b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar assertNotNull("view should not be null for valid position. " 394b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar + "got null view at position " + i, view); 395c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar if (!mRecyclerView.mState.isPreLayout()) { 396c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view 397c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar .getLayoutParams(); 398c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar assertFalse("In post layout, getViewForPosition should never return a view " 399c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar + "that is removed", layoutParams != null 400c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar && layoutParams.isItemRemoved()); 401c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar 402b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 4038da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar assertEquals("getViewForPosition should return correct position", 4048da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar i, getPosition(view)); 405c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar addView(view); 406b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 407d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase measureChildWithMargins(view, 0, 0); 408504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view) 409504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar , top + getDecoratedMeasuredHeight(view)); 410504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar top += view.getMeasuredHeight(); 411d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 412d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 413fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar 414fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar private void assertScrap(RecyclerView.Recycler recycler) { 415e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar if (mRecyclerView.getAdapter() != null && 416e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar !mRecyclerView.getAdapter().hasStableIds()) { 417e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) { 418e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid()); 419e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 420fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar } 421fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar } 422bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar 423bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar @Override 424bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar public boolean canScrollHorizontally() { 425bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar return true; 426bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar } 427bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar 428bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar @Override 429bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar public boolean canScrollVertically() { 430bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar return true; 431bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar } 432bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar 433bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar @Override 434bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, 435bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar RecyclerView.State state) { 436bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar return dx; 437bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar } 438bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar 439bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar @Override 440bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, 441bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar RecyclerView.State state) { 442bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar return dy; 443bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar } 444d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 445d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 446d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase static class Item { 44711b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar final static AtomicInteger idCounter = new AtomicInteger(0); 44811b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar final public int mId = idCounter.incrementAndGet(); 449d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 4502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar int mAdapterIndex; 451d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 4522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar final String mText; 453d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 4542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Item(int adapterIndex, String text) { 4552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapterIndex = adapterIndex; 4562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mText = text; 457d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 4588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 4598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar @Override 4608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public String toString() { 4618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar return "Item{" + 4628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar "mId=" + mId + 4632d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar ", originalIndex=" + mAdapterIndex + 4642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar ", text='" + mText + '\'' + 4658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar '}'; 4668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 467d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 468d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 46990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar class TestAdapter extends RecyclerView.Adapter<TestViewHolder> 47090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar implements AttachDetachCountingAdapter { 471d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 47290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter(); 473d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase List<Item> mItems; 474d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 475d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase TestAdapter(int count) { 476d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase mItems = new ArrayList<Item>(count); 477d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase for (int i = 0; i < count; i++) { 478d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase mItems.add(new Item(i, "Item " + i)); 479d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 480d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 481d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 482d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase @Override 48390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onViewAttachedToWindow(TestViewHolder holder) { 48490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar super.onViewAttachedToWindow(holder); 48590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachmentCounter.onViewAttached(holder); 48690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 48790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 48890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar @Override 48990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onViewDetachedFromWindow(TestViewHolder holder) { 49090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar super.onViewDetachedFromWindow(holder); 49190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachmentCounter.onViewDetached(holder); 49290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 49390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 49490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar @Override 49590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onAttachedToRecyclerView(RecyclerView recyclerView) { 49690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar super.onAttachedToRecyclerView(recyclerView); 49790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachmentCounter.onAttached(recyclerView); 49890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 49990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 50090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar @Override 50190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 50290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar super.onDetachedFromRecyclerView(recyclerView); 50390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachmentCounter.onDetached(recyclerView); 50490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 50590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 50690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar @Override 507d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public TestViewHolder onCreateViewHolder(ViewGroup parent, 508d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase int viewType) { 509d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase return new TestViewHolder(new TextView(parent.getContext())); 510d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 511d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 512d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase @Override 513d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public void onBindViewHolder(TestViewHolder holder, int position) { 514d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase final Item item = mItems.get(position); 5152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")"); 516115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar holder.mBoundItem = item; 517d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 518d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 519b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void deleteAndNotify(final int start, final int count) throws Throwable { 520b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar deleteAndNotify(new int[]{start, count}); 521b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 522b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 523b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar /** 524b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * Deletes items in the given ranges. 525b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * <p> 526b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * Note that each operation affects the one after so you should offset them properly. 527b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * <p> 528b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with 529b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be 530b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar * A D E. Then it will delete 2,1 which means it will delete E. 531b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar */ 532b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void deleteAndNotify(final int[]... startCountTuples) throws Throwable { 5338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar for (int[] tuple : startCountTuples) { 5348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar tuple[1] = -tuple[1]; 5358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 5368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar new AddRemoveRunnable(startCountTuples).runOnMainThread(); 537d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 538d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 539c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar @Override 540c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar public long getItemId(int position) { 541c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar return hasStableIds() ? mItems.get(position).mId : super.getItemId(position); 542c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar } 543c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar 5442d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar public void offsetOriginalIndices(int start, int offset) { 5452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar for (int i = start; i < mItems.size(); i++) { 5462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mItems.get(i).mAdapterIndex += offset; 5472d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 5482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 5492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 550e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar /** 551e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar * @param start inclusive 552e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar * @param end exclusive 553e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar * @param offset 554e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar */ 555e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar public void offsetOriginalIndicesBetween(int start, int end, int offset) { 556e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar for (int i = start; i < end && i < mItems.size(); i++) { 557e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar mItems.get(i).mAdapterIndex += offset; 558e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 559e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 560e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar 561b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void addAndNotify(final int start, final int count) throws Throwable { 562b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar addAndNotify(new int[]{start, count}); 563b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar } 564b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 565b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar public void addAndNotify(final int[]... startCountTuples) throws Throwable { 5668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar new AddRemoveRunnable(startCountTuples).runOnMainThread(); 5678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 568b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar 569cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase public void dispatchDataSetChanged() throws Throwable { 57022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar runTestOnUiThread(new Runnable() { 57122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar @Override 57222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar public void run() { 57322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar notifyDataSetChanged(); 57422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar } 57522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar }); 57622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar } 57722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar 578cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase public void changeAndNotify(final int start, final int count) throws Throwable { 5797e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar runTestOnUiThread(new Runnable() { 5807e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar @Override 5817e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar public void run() { 5827e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar notifyItemRangeChanged(start, count); 5837e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 5847e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar }); 5857e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar } 5867e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar 587cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase public void changePositionsAndNotify(final int... positions) throws Throwable { 588cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase runTestOnUiThread(new Runnable() { 589cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase @Override 590cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase public void run() { 591cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase for (int i = 0; i < positions.length; i += 1) { 592cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase TestAdapter.super.notifyItemRangeChanged(positions[i], 1); 593cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase } 594cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase } 595cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase }); 596cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase } 597cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase 5988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar /** 5998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar * Similar to other methods but negative count means delete and position count means add. 6008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar * <p> 6018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an 6028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar * item to index 1, then remove an item from index 2 (updated index 2) 6038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar */ 6048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable { 6058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar new AddRemoveRunnable(startCountTuples).runOnMainThread(); 606d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 607d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase 608d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase @Override 609d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase public int getItemCount() { 610d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase return mItems.size(); 611d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 6128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 613115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar /** 614115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar * Uses notifyDataSetChanged 615115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar */ 616e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable { 617e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar for (int i = 0; i < fromToTuples.length; i += 1) { 618e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar int[] tuple = fromToTuples[i]; 619e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar moveItem(tuple[0], tuple[1], false); 620e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 621e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar if (notifyChange) { 622cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase dispatchDataSetChanged(); 623e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 624e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 625e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar 626115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar /** 627115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar * Uses notifyDataSetChanged 628115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar */ 629e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar public void moveItem(final int from, final int to, final boolean notifyChange) 630e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar throws Throwable { 631e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar runTestOnUiThread(new Runnable() { 632e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar @Override 633e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar public void run() { 634e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar Item item = mItems.remove(from); 635e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar mItems.add(to, item); 636e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar offsetOriginalIndices(from, to - 1); 637e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar item.mAdapterIndex = to; 638e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar if (notifyChange) { 639e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar notifyDataSetChanged(); 640e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 641e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 642e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar }); 643e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar } 644e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar 645115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar /** 646115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar * Uses notifyItemMoved 647115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar */ 648115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar public void moveAndNotify(final int from, final int to) throws Throwable { 649115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar runTestOnUiThread(new Runnable() { 650115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar @Override 651115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar public void run() { 652115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar Item item = mItems.remove(from); 653115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar mItems.add(to, item); 654115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar offsetOriginalIndices(from, to - 1); 655115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar item.mAdapterIndex = to; 656115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar notifyItemMoved(from, to); 657115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar } 658115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar }); 659115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar } 660115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar 661115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar 662115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar 66390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar @Override 66490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public ViewAttachDetachCounter getCounter() { 66590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar return mAttachmentCounter; 66690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 66790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 6688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 6698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar private class AddRemoveRunnable implements Runnable { 6708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar final int[][] mStartCountTuples; 6718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 6728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public AddRemoveRunnable(int[][] startCountTuples) { 6738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar mStartCountTuples = startCountTuples; 6748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 6768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public void runOnMainThread() throws Throwable { 6778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar if (Looper.myLooper() == Looper.getMainLooper()) { 6788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar run(); 6798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } else { 6808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar runTestOnUiThread(this); 6818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 6848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar @Override 6858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public void run() { 6868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar for (int[] tuple : mStartCountTuples) { 6878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar if (tuple[1] < 0) { 6888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar delete(tuple); 6898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } else { 6908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar add(tuple); 6918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 6948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 6958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar private void add(int[] tuple) { 6962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // offset others 6972d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar offsetOriginalIndices(tuple[0], tuple[1]); 6988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar for (int i = 0; i < tuple[1]; i++) { 6998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar mItems.add(tuple[0], new Item(i, "new item " + i)); 7008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar notifyItemRangeInserted(tuple[0], tuple[1]); 7028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 7048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar private void delete(int[] tuple) { 7052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar final int count = -tuple[1]; 7062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar offsetOriginalIndices(tuple[0] + count, tuple[1]); 7072d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar for (int i = 0; i < count; i++) { 7088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar mItems.remove(tuple[0]); 7098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar notifyItemRangeRemoved(tuple[0], count); 7118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 7148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar 71590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public boolean isMainThread() { 71690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar return Looper.myLooper() == Looper.getMainLooper(); 71790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 71890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 7198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar @Override 7208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar public void runTestOnUiThread(Runnable r) throws Throwable { 7218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar if (Looper.myLooper() == Looper.getMainLooper()) { 7228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar r.run(); 7238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } else { 7248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar super.runTestOnUiThread(r); 7258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar } 726d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase } 727c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 728c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar static class TargetTuple { 729c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 730c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int mPosition; 731c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 732c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar final int mLayoutDirection; 733c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 734c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar TargetTuple(int position, int layoutDirection) { 735c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar this.mPosition = position; 736c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar this.mLayoutDirection = layoutDirection; 737c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 738c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar 739c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar @Override 740c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar public String toString() { 741c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar return "TargetTuple{" + 742c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar "mPosition=" + mPosition + 743c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar ", mLayoutDirection=" + mLayoutDirection + 744c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar '}'; 745c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 746c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar } 74790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 74890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public interface AttachDetachCountingAdapter { 74990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 75090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar ViewAttachDetachCounter getCounter(); 75190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 75290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 75390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public class ViewAttachDetachCounter { 75490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 75590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>(); 75690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 75790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void validateRemaining(RecyclerView recyclerView) { 75890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar final int childCount = recyclerView.getChildCount(); 75990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar for (int i = 0; i < childCount; i++) { 76090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar View view = recyclerView.getChildAt(i); 76190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view); 76290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar assertTrue("remaining view should be in attached set " + vh, 76390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachedSet.contains(vh)); 76490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 76590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar assertEquals("there should not be any views left in attached set", 76690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar childCount, mAttachedSet.size()); 76790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 76890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 76990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onViewDetached(RecyclerView.ViewHolder viewHolder) { 77090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar try { 77190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar assertTrue("view holder should be in attached set", 77290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachedSet.remove(viewHolder)); 77390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } catch (Throwable t) { 77490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar postExceptionToInstrumentation(t); 77590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 77690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 77790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 77890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onViewAttached(RecyclerView.ViewHolder viewHolder) { 77990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar try { 78090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar assertTrue("view holder should not be in attached set", 78190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachedSet.add(viewHolder)); 78290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } catch (Throwable t) { 78390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar postExceptionToInstrumentation(t); 78490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 78590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 78690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 78790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onAttached(RecyclerView recyclerView) { 78890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar // when a new RV is attached, clear the set and add all view holders 78990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachedSet.clear(); 79090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar final int childCount = recyclerView.getChildCount(); 79190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar for (int i = 0; i < childCount; i ++) { 79290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar View view = recyclerView.getChildAt(i); 79390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar mAttachedSet.add(recyclerView.getChildViewHolder(view)); 79490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 79590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 79690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar 79790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar public void onDetached(RecyclerView recyclerView) { 79890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar validateRemaining(recyclerView); 79990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 80090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar } 8017a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar} 802