136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/* 236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Copyright (C) 2016 The Android Open Source Project 336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * you may not use this file except in compliance with the License. 636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * You may obtain a copy of the License at 736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * http://www.apache.org/licenses/LICENSE-2.0 936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 1036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Unless required by applicable law or agreed to in writing, software 1136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * distributed under the License is distributed on an "AS IS" BASIS, 1236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * See the License for the specific language governing permissions and 1436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * limitations under the License. 1536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik */ 1636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 1736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpackage android.support.v7.widget; 1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals; 200fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse; 211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals; 22dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull; 230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame; 24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue; 25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any; 26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt; 271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat; 28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock; 29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never; 30ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times; 31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify; 32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when; 33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas 3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context; 35e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build; 360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable; 370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock; 3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry; 39ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress; 40754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest; 4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4; 420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent; 4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View; 4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup; 450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout; 4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 47356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After; 4836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before; 4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test; 5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith; 51963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher; 5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock; 5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer; 5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList; 5636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List; 57a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit; 5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 5936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest 60e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class) 6236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest { 63a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView mRecyclerView; 6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Recycler mRecycler; 6536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 66a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private class TimeMockingRecyclerView extends RecyclerView { 67a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private long mMockNanoTime = 0; 68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView(Context context) { 70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik super(context); 71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void registerTimePassingMs(long ms) { 74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms); 75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik long getNanoTime() { 79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return mMockNanoTime; 80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 81bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik 82bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 83bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 84bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 85bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 87356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 88a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 8936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Before 90356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void setup() throws Exception { 91a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView = new TimeMockingRecyclerView(getContext()); 92356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onAttachedToWindow(); 9336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler = mRecyclerView.mRecycler; 9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 96356880d3de117b067522ad452f4e3eed85ce444cChris Craik @After 97356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void teardown() throws Exception { 98356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (mRecyclerView.isAttachedToWindow()) { 99356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onDetachedFromWindow(); 100356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 101356880d3de117b067522ad452f4e3eed85ce444cChris Craik GapWorker gapWorker = GapWorker.sGapWorker.get(); 102356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (gapWorker != null) { 103356880d3de117b067522ad452f4e3eed85ce444cChris Craik assertTrue(gapWorker.mRecyclerViews.isEmpty()); 104356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 105356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 106356880d3de117b067522ad452f4e3eed85ce444cChris Craik 10736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private Context getContext() { 10836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return InstrumentationRegistry.getContext(); 10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 111dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik private void layout(int width, int height) { 112dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.measure( 113dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), 114dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); 115dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.layout(0, 0, width, height); 116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 11836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 11936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchReusesCacheItems() { 12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() { 12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.LayoutParams generateDefaultLayoutParams() { 12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik ViewGroup.LayoutParams.WRAP_CONTENT); 12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 1281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, 1293104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik LayoutPrefetchRegistry prefetchManager) { 130945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(0, 0); 131945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(1, 0); 132945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(2, 0); 13336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 13436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }; 13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(prefetchingLayoutManager); 14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(10); 15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 153dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(320, 320); 15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt()); 15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onBindViewHolder( 15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Prefetch multiple times... 16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < 4; i++) { 16247d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // ...but should only see the same three items fetched/bound once each 16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onBindViewHolder( 16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 1701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2); 17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedWithInserts() { 176dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 184dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View view = new View(getContext()); 185dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumWidth(100); 186dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumHeight(100); 187dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return new RecyclerView.ViewHolder(view) {}; 18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 18936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 193dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 100); 19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 195945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax); 196dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 19747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 198945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax); 199945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 2001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5); 20136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // further views recycled, as though from scrolling, shouldn't evict prefetched views: 20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(10)); 204dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10); 20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(20)); 207dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20); 20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(30)); 210dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30); 21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(40)); 213dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40); 21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // After clearing the cache, the prefetch priorities should be cleared as well: 21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.mRecycler.recycleAndClearCachedViews(); 217dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) { 21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(i)); 21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // cache only contains most recent positions, no priority for previous prefetches: 222356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90); 22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedOnScroll() { 22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // 100x100 pixel views 23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik View view = new View(getContext()); 23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumWidth(100); 23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumHeight(100); 23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(view) {}; 24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // NOTE: requested cache size must be smaller than span count so two rows cannot fit 24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setItemViewCacheSize(2); 24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 248dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 150); 24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 75); 25036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // rows 0, 1, and 2 are all attached and visible. Prefetch row 3: 253945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 25447d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 25536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is cached: 257356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 25836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 25936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Scroll so 1 falls off (though 3 is still not on screen) 26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 50); 26236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is still cached, with a couple other recycled views: 264356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 5); 26636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 267a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 268a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Test 269b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public void prefetchIsComputingLayout() { 270b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 271b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 272b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // 100x100 pixel views 273b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Override 277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik throws Throwable { 279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik View view = new View(getContext()); 280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumWidth(100); 281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumHeight(100); 282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik assertTrue(mRecyclerView.isComputingLayout()); 283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik return new RecyclerView.ViewHolder(view) {}; 284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik }); 286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setAdapter(mockAdapter); 288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik layout(100, 100); 290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0); 292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // prefetch an item, should still observe isComputingLayout in that create 294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0); 298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Test 301213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public void prefetchAfterOrientationChange() { 302213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager layout = new LinearLayoutManager(getContext(), 303213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager.VERTICAL, false); 304213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setLayoutManager(layout); 305213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // 100x100 pixel views 307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 310213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik View view = new View(getContext()); 311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumWidth(100); 312213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumHeight(100); 313213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik assertTrue(mRecyclerView.isComputingLayout()); 314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return new RecyclerView.ViewHolder(view) {}; 315213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 316213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {} 319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public int getItemCount() { 322213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return 100; 323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik }); 325213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout(100, 100); 327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout.setOrientation(LinearLayoutManager.HORIZONTAL); 329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // Prefetch an item after changing orientation, before layout - shouldn't crash 331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1); 332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Test 3360951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public void prefetchDrag() { 3370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // event dispatch requires a parent 3380951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik ViewGroup parent = new FrameLayout(getContext()); 3390951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik parent.addView(mRecyclerView); 3400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3410951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setLayoutManager( 3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); 3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // 1000x1000 pixel views 3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik View view = new View(getContext()); 3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumWidth(1000); 3520951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumHeight(1000); 3530951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return new RecyclerView.ViewHolder(view) {}; 3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3550951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3560951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3620951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public int getItemCount() { 3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return 100; 3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3650951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik }; 3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setAdapter(adapter); 3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik layout(1000, 1000); 3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik long time = SystemClock.uptimeMillis(); 3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0)); 3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // Consume slop 3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0)); 3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,30 3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0)); 3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 10,15 3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,0 - IGNORED 3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev 3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev 3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Test 401a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void prefetchItemsRespectDeadline() { 402a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 403a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 404a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // 100x100 pixel views 405a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 406a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 407a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 408a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik View view = new View(getContext()); 410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumWidth(100); 411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumHeight(100); 412945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(view) {}; 413a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 415a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 416a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 418a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 421a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public int getItemCount() { 422a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return 100; 423a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik }; 425a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setAdapter(adapter); 426a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 427dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 300); 428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 429356880d3de117b067522ad452f4e3eed85ce444cChris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 430356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.scrollBy(0, 50); 431356880d3de117b067522ad452f4e3eed85ce444cChris Craik 432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 434a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe 436a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19); 437a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 438a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Timed prefetch 439945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 44047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 441a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // will have enough time to inflate/bind one view, and inflate another 443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 1); 444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 445356880d3de117b067522ad452f4e3eed85ce444cChris Craik // Note: order/view below is an implementation detail 446356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12); 447a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 449a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Unbounded prefetch this time 45047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 452a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should finish all work 453a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 455356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14); 456945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 457945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 458945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Test 4591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void partialPrefetchAvoidsViewRecycledCallback() { 4601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 4611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // 100x100 pixel views 4631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 4641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 4661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(getContext()); 4681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumWidth(100); 4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumHeight(100); 4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 4751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return 100; 4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void onViewRecycled(RecyclerView.ViewHolder holder) { 4851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // verify unbound view doesn't get 4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition()); 4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }; 4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(adapter); 4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 300); 4921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.scrollBy(0, 50); 4951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind 5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Timed prefetch 5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // will have enough time to inflate but not bind one view 5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool() 5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .mScrap.get(0).mScrapHeap.get(0); 5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition()); 5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 515945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public void prefetchStaggeredItemsPriority() { 516945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik StaggeredGridLayoutManager sglm = 517945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 518945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setLayoutManager(sglm); 519945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 520945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // first view 50x100 pixels, rest are 100x100 so second column is offset 521945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 522945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 523945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 524945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 525945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumWidth(100); 530945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumHeight(position == 0 ? 50 : 100); 531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 532945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 533945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public int getItemCount() { 535945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return 100; 536945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik }); 538945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 539dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 200); 540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 541945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 1 * 545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___4___|___3___* 547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 5 * 549945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 552945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(0, sglm.getFirstChildPosition()); 553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(4, sglm.getLastChildPosition()); 554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away 556356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {5, 0}, new Integer[] {6, 50}); 558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // Prefetch upward shows nothing 560356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.scrollBy(0, 100); 563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 566945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___2___|___1___* 568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 569945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 3 * 570945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___6___|___5___* 572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 7 * 573945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 8 | 7 * 574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, sglm.getFirstChildPosition()); 579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(6, sglm.getLastChildPosition()); 580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away 582356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {7, 0}, new Integer[] {8, 50}); 584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away 586356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10, 587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {1, 0}, new Integer[] {0, 50}); 588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 589dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 590dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Test 591695d9b8629fa048795298359d4845f0747364fafChris Craik public void prefetchStaggeredPastBoundary() { 592695d9b8629fa048795298359d4845f0747364fafChris Craik StaggeredGridLayoutManager sglm = 593695d9b8629fa048795298359d4845f0747364fafChris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 594695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setLayoutManager(sglm); 595695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 596695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 597695d9b8629fa048795298359d4845f0747364fafChris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 598695d9b8629fa048795298359d4845f0747364fafChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 599695d9b8629fa048795298359d4845f0747364fafChris Craik } 600695d9b8629fa048795298359d4845f0747364fafChris Craik 601695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 602695d9b8629fa048795298359d4845f0747364fafChris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 603695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumWidth(100); 604695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumHeight(position == 0 ? 100 : 200); 605695d9b8629fa048795298359d4845f0747364fafChris Craik } 606695d9b8629fa048795298359d4845f0747364fafChris Craik 607695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 608695d9b8629fa048795298359d4845f0747364fafChris Craik public int getItemCount() { 609695d9b8629fa048795298359d4845f0747364fafChris Craik return 2; 610695d9b8629fa048795298359d4845f0747364fafChris Craik } 611695d9b8629fa048795298359d4845f0747364fafChris Craik }); 612695d9b8629fa048795298359d4845f0747364fafChris Craik 613695d9b8629fa048795298359d4845f0747364fafChris Craik layout(200, 100); 614695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.scrollBy(0, 50); 615695d9b8629fa048795298359d4845f0747364fafChris Craik 616695d9b8629fa048795298359d4845f0747364fafChris Craik /* Each row is 50 pixels: 617695d9b8629fa048795298359d4845f0747364fafChris Craik * ------------- * 618695d9b8629fa048795298359d4845f0747364fafChris Craik *___0___|___1___* 619695d9b8629fa048795298359d4845f0747364fafChris Craik * 0 | 1 * 620695d9b8629fa048795298359d4845f0747364fafChris Craik *_______|___1___* 621695d9b8629fa048795298359d4845f0747364fafChris Craik * | 1 * 622695d9b8629fa048795298359d4845f0747364fafChris Craik */ 623695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(2, mRecyclerView.getChildCount()); 624695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(0, sglm.getFirstChildPosition()); 625695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(1, sglm.getLastChildPosition()); 626695d9b8629fa048795298359d4845f0747364fafChris Craik 627695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch upward gets nothing 628695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 629695d9b8629fa048795298359d4845f0747364fafChris Craik 630695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch downward gets nothing (and doesn't crash...) 631695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10); 632695d9b8629fa048795298359d4845f0747364fafChris Craik } 633695d9b8629fa048795298359d4845f0747364fafChris Craik 634695d9b8629fa048795298359d4845f0747364fafChris Craik @Test 635dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public void prefetchItemsSkipAnimations() { 636dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 637dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(llm); 638dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final int[] expandedPosition = new int[] {-1}; 639dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 640dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 641dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 642dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 6431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; 644dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 645dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 646dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 647dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 648dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int height = expandedPosition[0] == position ? 400 : 100; 649dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height)); 650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public int getItemCount() { 654dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return 10; 655dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 656dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik }; 657dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 658dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // make move duration long enough to be able to see the effects 659dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 660dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik itemAnimator.setMoveDuration(10000); 661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setAdapter(adapter); 662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 663dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik expandedPosition[0] = 1; 666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // insert payload to avoid creating a new view 667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik adapter.notifyItemChanged(1, new Object()); 668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view should be observable as hidden, uncached... 677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // ...but must not be removed for prefetch 683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount); 687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0]; 688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target view 2", 2, prefetchTarget); 689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view still observable as hidden, uncached 691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 7001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 7021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void viewHolderFindsNestedRecyclerViews() { 7031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 7041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 7051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 7071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 7081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 7091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 7111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik throws Throwable { 7121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new RecyclerView(getContext()); 7131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 7151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }); 7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(mockAdapter); 7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 200); 7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onBindViewHolder( 724963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() { 7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 726963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas public boolean matches(RecyclerView.ViewHolder holder) { 7278123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return holder.itemView == holder.mNestedRecyclerView.get(); 7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }), 7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik anyInt(), 7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik any(List.class)); 7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7340fab45107f043a4856aad2e1fd785b396898616eChris Craik class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> { 7350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private static final int INNER_ITEM_COUNT = 20; 736f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik int mItemsBound = 0; 737f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 7380fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(View itemView) { 7401e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik InnerAdapter() {} 7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 7480fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 7491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(parent.getContext()); 7501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7511e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(view); 7521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7531e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 755f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik public void onBindViewHolder(ViewHolder holder, int position) { 7560fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 757f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mItemsBound++; 758f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return INNER_ITEM_COUNT; 7631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7660fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { 7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private boolean mReverseInner; 7690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 7700fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik private final RecyclerView mRecyclerView; 7721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(RecyclerView itemView) { 7731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView = itemView; 7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ArrayList<InnerAdapter> mAdapters = new ArrayList<>(); 7790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 7811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik OuterAdapter() { 7830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik this(false); 7840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 7850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 7860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik OuterAdapter(boolean reverseInner) { 7878123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik this(reverseInner, 10); 7888123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 7898123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 7908123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter(boolean reverseInner, int itemCount) { 7910ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mReverseInner = reverseInner; 7928123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < itemCount; i++) { 7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mAdapters.add(new InnerAdapter()); 7940ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.add(null); 7951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7988123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik void addItem() { 7998123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik int index = getItemCount(); 8008123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mAdapters.add(new InnerAdapter()); 8018123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mSavedStates.add(null); 8028123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik notifyItemInserted(index); 8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 8048123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 8051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 8070fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8080fab45107f043a4856aad2e1fd785b396898616eChris Craik 809bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik RecyclerView rv = new RecyclerView(parent.getContext()) { 810bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 811bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 812bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 813bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 814bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 815bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik }; 8161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 8170ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 8181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setRecycledViewPool(mSharedPool); 8191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 8201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(rv); 8211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void onBindViewHolder(ViewHolder holder, int position) { 8250fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8260fab45107f043a4856aad2e1fd785b396898616eChris Craik 8270ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Tests may rely on bound holders not being shared between inner adapters, 8280ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // since we force recycle here 8290ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.swapAdapter(mAdapters.get(position), true); 8300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8310ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik Parcelable savedState = mSavedStates.get(position); 8320ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik if (savedState != null) { 8330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 8340ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(position, null); 8350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8370ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Override 8390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void onViewRecycled(ViewHolder holder) { 8400ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(holder.getAdapterPosition(), 8410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 8421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 8468123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return mAdapters.size(); 8471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchSimple() { 8521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 853d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 8541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 8561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(new OuterAdapter()); 8571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(200, 200); 8591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 2 (default) 8621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 8641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder); 8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder.mNestedRecyclerView); 8668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 8678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 4 8708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik ((LinearLayoutManager) innerView.getLayoutManager()) 8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .setInitialPrefetchItemCount(4); 8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3); 8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 8770fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu public void nestedPrefetchNotClearInnerStructureChangeFlag() { 8780fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu LinearLayoutManager llm = new LinearLayoutManager(getContext()); 8790fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertEquals(2, llm.getInitialPrefetchItemCount()); 8800fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8810fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setLayoutManager(llm); 8820fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setAdapter(new OuterAdapter()); 8830fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8840fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 8850fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 8860fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8870fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch 2 (default) 8880fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8890fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 8900fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder); 8910fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder.mNestedRecyclerView); 8920fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView innerView = holder.mNestedRecyclerView.get(); 8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.Adapter innerAdapter = innerView.getAdapter(); 8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // mStructureChanged is initially true before first layout pass. 8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // layout position 2 and clear mStructureChanged 9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(2); 9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(0); 9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.mState.mStructureChanged); 9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.hasPendingAdapterUpdates()); 9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // notify change on the cached innerView. 9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerAdapter.notifyDataSetChanged(); 9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch again 9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu ((LinearLayoutManager) innerView.getLayoutManager()) 9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu .setInitialPrefetchItemCount(2); 9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // The re-prefetch is not necessary get the same inner view but we will get same Adapter 9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerView = holder.mNestedRecyclerView.get(); 9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertSame(innerAdapter, innerView.getAdapter()); 9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch shouldn't clear the mStructureChanged flag 9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu } 9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu @Test 9290ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchReverseInner() { 9300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9310ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true)); 9320ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9340ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9370ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // anchor from right side, should see last two positions 9408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19); 9410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 9420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 9440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchOffset() { 9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter()); 9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing 9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0); 9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner.scrollBy(550, 0); 9530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition); 9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition); 9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition); 9560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // scroll down 4 rows, up 3 so row 0 is adjacent but uncached 9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, 400); 9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, -300); 9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // top row no longer present 9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0); 9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch upward, and validate that we've gotten the top row with correct offsets 9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView; 9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6); 9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch 4 9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4); 9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8); 9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 975f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 976f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik @Test 977f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik public void nestedPrefetchNotReset() { 978f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 979f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik OuterAdapter outerAdapter = new OuterAdapter(); 980f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setAdapter(outerAdapter); 981f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 982f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik layout(200, 200); 983f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 984f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 985f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 986f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // prefetch row 2, items 0 & 1 987f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound); 988f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 989f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9908123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertNotNull(innerRecyclerView); 993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1); 994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // new row comes on, triggers layout... 997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.scrollBy(0, 50); 998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // ... which shouldn't require new items to be bound, 1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // as prefetch has already done that work 1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 10030fab45107f043a4856aad2e1fd785b396898616eChris Craik 10040fab45107f043a4856aad2e1fd785b396898616eChris Craik static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) { 10050fab45107f043a4856aad2e1fd785b396898616eChris Craik ChildHelper childHelper = recyclerView.mChildHelper; 10060fab45107f043a4856aad2e1fd785b396898616eChris Craik 10070fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(childCount, childHelper.getUnfilteredChildCount()); 10080fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) { 10090fab45107f043a4856aad2e1fd785b396898616eChris Craik assertFalse(recyclerView.getChildViewHolder( 10100fab45107f043a4856aad2e1fd785b396898616eChris Craik childHelper.getUnfilteredChildAt(i)).isInvalid()); 10110fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10120fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10130fab45107f043a4856aad2e1fd785b396898616eChris Craik 10140fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 10150fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchCacheNotTouched() { 10160fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10170fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10180fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 10190fab45107f043a4856aad2e1fd785b396898616eChris Craik 10200fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 10210fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.scrollBy(0, 100); 10220fab45107f043a4856aad2e1fd785b396898616eChris Craik 10230fab45107f043a4856aad2e1fd785b396898616eChris Craik // item 0 is cached 10240fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10250fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0); 10268123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10270fab45107f043a4856aad2e1fd785b396898616eChris Craik 10280fab45107f043a4856aad2e1fd785b396898616eChris Craik // try and prefetch it 10290fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 10300fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 10310fab45107f043a4856aad2e1fd785b396898616eChris Craik 10320fab45107f043a4856aad2e1fd785b396898616eChris Craik // make sure cache's inner items aren't rebound unnecessarily 10330fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10348123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10378123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik @Test 10388123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik public void nestedRemoveAnimatingView() { 10398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter outerAdapter = new OuterAdapter(false, 1); 10418123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setAdapter(outerAdapter); 10428123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30)); 10438123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10448123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); 10458123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10468123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // Insert 3 items - only first one in viewport, so only it animates 10478123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < 3; i++) { 10488123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik outerAdapter.addItem(); 10498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); // layout again to kick off animation 10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // item 1 is animating, so scroll it out of viewport 10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.scrollBy(0, 200); 10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for... 10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(1, mRecycler.mCachedViews.size()); 10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0); 10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // until animation ends 10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().endAnimations(); 10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecycler.mCachedViews.size()); 10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1); 10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) { 10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertNotNull(viewHolder.mNestedRecyclerView); 10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10720fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10730fab45107f043a4856aad2e1fd785b396898616eChris Craik 10749ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik @Test 10759ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik public void nestedExpandCacheCorrectly() { 10769ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE; 10779ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10789ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10799ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10809ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setAdapter(outerAdapter); 10819ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10829ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik layout(200, 200); 10839ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10849ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 10859ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 10869ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10879ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // after initial prefetch, view cache max expanded by number of inner items prefetched (2) 10889ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 10899ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved); 10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik try { 10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // Note: As a hack, we not only must manually dispatch attachToWindow(), but we 10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker 10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // is registered to the wrong thread, since @setup is called on a different thread 10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // from @Test. Assert this, so this test can be fixed when setup == test thread. 10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size()); 11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.isAttachedToWindow()); 11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onAttachedToWindow(); 11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // bring prefetch view into viewport, at which point it shouldn't have cache expanded... 11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.scrollBy(0, 100); 11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // until a valid horizontal prefetch caches an item, and expands view count by one 11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mPrefetchRegistry.setPrefetchVector(1, 0); 11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker 11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } finally { 11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik if (innerView.isAttachedToWindow()) { 11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onDetachedFromWindow(); 11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11220fab45107f043a4856aad2e1fd785b396898616eChris Craik /** 11230fab45107f043a4856aad2e1fd785b396898616eChris Craik * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter 11240fab45107f043a4856aad2e1fd785b396898616eChris Craik * to update data for the inner RecyclerViews when containing ViewHolder is bound. 11250fab45107f043a4856aad2e1fd785b396898616eChris Craik */ 11260fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> { 11270fab45107f043a4856aad2e1fd785b396898616eChris Craik private static final int OUTER_ITEM_COUNT = 10; 11280fab45107f043a4856aad2e1fd785b396898616eChris Craik 11290fab45107f043a4856aad2e1fd785b396898616eChris Craik private boolean mReverseInner; 11300fab45107f043a4856aad2e1fd785b396898616eChris Craik 11310fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 11320fab45107f043a4856aad2e1fd785b396898616eChris Craik private final RecyclerView mRecyclerView; 11330fab45107f043a4856aad2e1fd785b396898616eChris Craik private final InnerAdapter mAdapter; 11340fab45107f043a4856aad2e1fd785b396898616eChris Craik ViewHolder(RecyclerView itemView) { 11350fab45107f043a4856aad2e1fd785b396898616eChris Craik super(itemView); 11360fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView = itemView; 11370fab45107f043a4856aad2e1fd785b396898616eChris Craik mAdapter = new InnerAdapter(); 11380fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(mAdapter); 11390fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11400fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11410fab45107f043a4856aad2e1fd785b396898616eChris Craik 11420fab45107f043a4856aad2e1fd785b396898616eChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 11430fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 11440fab45107f043a4856aad2e1fd785b396898616eChris Craik 11450fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter() { 11460fab45107f043a4856aad2e1fd785b396898616eChris Craik this(false); 11470fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11480fab45107f043a4856aad2e1fd785b396898616eChris Craik 11490fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter(boolean reverseInner) { 11500fab45107f043a4856aad2e1fd785b396898616eChris Craik mReverseInner = reverseInner; 11510fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i <= OUTER_ITEM_COUNT; i++) { 11520fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.add(null); 11530fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11540fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11550fab45107f043a4856aad2e1fd785b396898616eChris Craik 11560fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11570fab45107f043a4856aad2e1fd785b396898616eChris Craik public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 11580fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11590fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView rv = new RecyclerView(parent.getContext()); 11600fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 11610fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 11620fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setRecycledViewPool(mSharedPool); 11630fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 11640fab45107f043a4856aad2e1fd785b396898616eChris Craik return new ViewHolder(rv); 11650fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11660fab45107f043a4856aad2e1fd785b396898616eChris Craik 11670fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11680fab45107f043a4856aad2e1fd785b396898616eChris Craik public void onBindViewHolder(ViewHolder holder, int position) { 11690fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11700fab45107f043a4856aad2e1fd785b396898616eChris Craik // if we had actual data to put into our adapter, this is where we'd do it... 11710fab45107f043a4856aad2e1fd785b396898616eChris Craik 11720fab45107f043a4856aad2e1fd785b396898616eChris Craik // ... then notify the adapter that it has new content: 11730fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mAdapter.notifyDataSetChanged(); 11740fab45107f043a4856aad2e1fd785b396898616eChris Craik 11750fab45107f043a4856aad2e1fd785b396898616eChris Craik Parcelable savedState = mSavedStates.get(position); 11760fab45107f043a4856aad2e1fd785b396898616eChris Craik if (savedState != null) { 11770fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 11780fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.set(position, null); 11790fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11800fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11810fab45107f043a4856aad2e1fd785b396898616eChris Craik 11820fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11830fab45107f043a4856aad2e1fd785b396898616eChris Craik public void onViewRecycled(ViewHolder holder) { 1184570ab881350c836743247140a0953f87acc21c8dChris Craik if (holder.getAdapterPosition() >= 0) { 1185570ab881350c836743247140a0953f87acc21c8dChris Craik mSavedStates.set(holder.getAdapterPosition(), 1186570ab881350c836743247140a0953f87acc21c8dChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 1187570ab881350c836743247140a0953f87acc21c8dChris Craik } 11880fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11890fab45107f043a4856aad2e1fd785b396898616eChris Craik 11900fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11910fab45107f043a4856aad2e1fd785b396898616eChris Craik public int getItemCount() { 11920fab45107f043a4856aad2e1fd785b396898616eChris Craik return OUTER_ITEM_COUNT; 11930fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11940fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11950fab45107f043a4856aad2e1fd785b396898616eChris Craik 11960fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 11970fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchDiscardStaleChildren() { 11980fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 1199d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 12000fab45107f043a4856aad2e1fd785b396898616eChris Craik 12010fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(llm); 12020fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 12030fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 12040fab45107f043a4856aad2e1fd785b396898616eChris Craik 12050fab45107f043a4856aad2e1fd785b396898616eChris Craik // zero cache, so item we prefetch can't already be ready 12060fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setItemViewCacheSize(0); 12070fab45107f043a4856aad2e1fd785b396898616eChris Craik 12080fab45107f043a4856aad2e1fd785b396898616eChris Craik // layout 3 items, then resize to 2... 12090fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 300); 12100fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 12110fab45107f043a4856aad2e1fd785b396898616eChris Craik 12120fab45107f043a4856aad2e1fd785b396898616eChris Craik // so 1 item is evicted into the RecycledViewPool (bypassing cache) 12130fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 12140fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 12150fab45107f043a4856aad2e1fd785b396898616eChris Craik 12160fab45107f043a4856aad2e1fd785b396898616eChris Craik // This is a simple imitation of other behavior (namely, varied types in the outer adapter) 12170fab45107f043a4856aad2e1fd785b396898616eChris Craik // that results in the same initial state to test: items in the pool with attached children 12180fab45107f043a4856aad2e1fd785b396898616eChris Craik for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) { 12190fab45107f043a4856aad2e1fd785b396898616eChris Craik // verify that children are attached and valid, since the RVs haven't been rebound 12200fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12218123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout); 12228123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 12230fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12240fab45107f043a4856aad2e1fd785b396898616eChris Craik 12250fab45107f043a4856aad2e1fd785b396898616eChris Craik // prefetch the outer item bind, but without enough time to do any inner binds 12260fab45107f043a4856aad2e1fd785b396898616eChris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 12270fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 12280fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 12290fab45107f043a4856aad2e1fd785b396898616eChris Craik 12300fab45107f043a4856aad2e1fd785b396898616eChris Craik // 2 is prefetched without children 12310fab45107f043a4856aad2e1fd785b396898616eChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2); 12320fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 12330fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder); 12340fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount()); 12368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size()); 12370fab45107f043a4856aad2e1fd785b396898616eChris Craik 12380fab45107f043a4856aad2e1fd785b396898616eChris Craik // but if we give it more time to bind items, it'll now acquire its inner items 12390fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 12408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1); 12410fab45107f043a4856aad2e1fd785b396898616eChris Craik } 1242570ab881350c836743247140a0953f87acc21c8dChris Craik 1243570ab881350c836743247140a0953f87acc21c8dChris Craik 1244570ab881350c836743247140a0953f87acc21c8dChris Craik @Test 1245570ab881350c836743247140a0953f87acc21c8dChris Craik public void nestedPrefetchDiscardStalePrefetch() { 1246570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 1247570ab881350c836743247140a0953f87acc21c8dChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 1248570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setAdapter(outerAdapter); 1249570ab881350c836743247140a0953f87acc21c8dChris Craik 1250570ab881350c836743247140a0953f87acc21c8dChris Craik // zero cache, so item we prefetch can't already be ready 1251570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setItemViewCacheSize(0); 1252570ab881350c836743247140a0953f87acc21c8dChris Craik 1253570ab881350c836743247140a0953f87acc21c8dChris Craik // layout as 2x2, starting on row index 2, with empty cache 1254570ab881350c836743247140a0953f87acc21c8dChris Craik layout(200, 200); 1255570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.scrollBy(0, 200); 1256570ab881350c836743247140a0953f87acc21c8dChris Craik 1257570ab881350c836743247140a0953f87acc21c8dChris Craik // no views cached, or previously used (so we can trust number in mItemsBound) 1258570ab881350c836743247140a0953f87acc21c8dChris Craik mRecycler.mRecyclerPool.clear(); 1259570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 1260570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 1261570ab881350c836743247140a0953f87acc21c8dChris Craik 1262570ab881350c836743247140a0953f87acc21c8dChris Craik // prefetch the outer item and its inner children 1263570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1264570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1265570ab881350c836743247140a0953f87acc21c8dChris Craik 1266570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched with 2 inner children, first two binds 1267570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1268570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4); 1269570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder); 1270570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder.mNestedRecyclerView); 1271570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 1272570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1273570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1274570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1275570ab881350c836743247140a0953f87acc21c8dChris Craik 1276570ab881350c836743247140a0953f87acc21c8dChris Craik // notify data set changed, so any previously prefetched items invalid, and re-prefetch 1277570ab881350c836743247140a0953f87acc21c8dChris Craik innerRecyclerView.getAdapter().notifyDataSetChanged(); 1278570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1279570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1280570ab881350c836743247140a0953f87acc21c8dChris Craik 1281570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched again... 1282570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1283570ab881350c836743247140a0953f87acc21c8dChris Craik 1284570ab881350c836743247140a0953f87acc21c8dChris Craik // reusing the same instance with 2 inner children... 1285570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4)); 1286570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(innerRecyclerView, holder.mNestedRecyclerView.get()); 1287570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1288570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1289570ab881350c836743247140a0953f87acc21c8dChris Craik 1290570ab881350c836743247140a0953f87acc21c8dChris Craik // ... but there should be two new binds 1291570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1292570ab881350c836743247140a0953f87acc21c8dChris Craik } 1293945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik} 1294