136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 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 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget; 1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.CoreMatchers.is; 20c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.MatcherAssert.assertThat; 21c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.core.IsEqual.equalTo; 22945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals; 230fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse; 241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals; 25dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull; 260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame; 27ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue; 28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any; 29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt; 301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat; 31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock; 32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never; 33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times; 34ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify; 35ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when; 36ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas 3736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context; 38e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build; 390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable; 400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock; 4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry; 42ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress; 43754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest; 4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4; 450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent; 4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View; 4736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup; 480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout; 4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 50c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull; 51c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 52356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After; 5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before; 5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test; 5536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith; 56963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher; 5736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock; 5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer; 5936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList; 6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List; 62a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit; 6336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest 65e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 6636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class) 6736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest { 68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView mRecyclerView; 6936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Recycler mRecycler; 7036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private class TimeMockingRecyclerView extends RecyclerView { 72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private long mMockNanoTime = 0; 73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView(Context context) { 75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik super(context); 76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void registerTimePassingMs(long ms) { 79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms); 80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 81a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 82a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 83a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik long getNanoTime() { 84a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return mMockNanoTime; 85a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik 87bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 88bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 89bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 90bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 91bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 92356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 93a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Before 95356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void setup() throws Exception { 96a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView = new TimeMockingRecyclerView(getContext()); 97356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onAttachedToWindow(); 9836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler = mRecyclerView.mRecycler; 9936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 10036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 101356880d3de117b067522ad452f4e3eed85ce444cChris Craik @After 102356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void teardown() throws Exception { 103356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (mRecyclerView.isAttachedToWindow()) { 104356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onDetachedFromWindow(); 105356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 106356880d3de117b067522ad452f4e3eed85ce444cChris Craik GapWorker gapWorker = GapWorker.sGapWorker.get(); 107356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (gapWorker != null) { 108356880d3de117b067522ad452f4e3eed85ce444cChris Craik assertTrue(gapWorker.mRecyclerViews.isEmpty()); 109356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 110356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 111356880d3de117b067522ad452f4e3eed85ce444cChris Craik 11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private Context getContext() { 11336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return InstrumentationRegistry.getContext(); 11436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 11536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik private void layout(int width, int height) { 117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.measure( 118dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), 119dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); 120dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.layout(0, 0, width, height); 121dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 122dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchReusesCacheItems() { 12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() { 12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.LayoutParams generateDefaultLayoutParams() { 12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik ViewGroup.LayoutParams.WRAP_CONTENT); 13036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 13136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 13236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 1331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, 1343104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik LayoutPrefetchRegistry prefetchManager) { 135945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(0, 0); 136945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(1, 0); 137945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(2, 0); 13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }; 14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(prefetchingLayoutManager); 14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(10); 15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 158dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(320, 320); 15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt()); 16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onBindViewHolder( 16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Prefetch multiple times... 16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < 4; i++) { 16747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // ...but should only see the same three items fetched/bound once each 17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onBindViewHolder( 17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 1751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2); 17636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedWithInserts() { 181dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 18636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 18736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 189dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View view = new View(getContext()); 190dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumWidth(100); 191dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumHeight(100); 192dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return new RecyclerView.ViewHolder(view) {}; 19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 19536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 19736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 198dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 100); 19936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 200945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax); 201dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 20247d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 203945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax); 204945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 2051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5); 20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // further views recycled, as though from scrolling, shouldn't evict prefetched views: 20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(10)); 209dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10); 21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(20)); 212dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20); 21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(30)); 215dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30); 21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(40)); 218dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40); 21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // After clearing the cache, the prefetch priorities should be cleared as well: 22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.mRecycler.recycleAndClearCachedViews(); 222dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) { 22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(i)); 22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // cache only contains most recent positions, no priority for previous prefetches: 227356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90); 22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedOnScroll() { 23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // 100x100 pixel views 23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik View view = new View(getContext()); 24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumWidth(100); 24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumHeight(100); 24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(view) {}; 24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // NOTE: requested cache size must be smaller than span count so two rows cannot fit 25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setItemViewCacheSize(2); 25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 253dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 150); 25436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 75); 25536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 25636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // rows 0, 1, and 2 are all attached and visible. Prefetch row 3: 258945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 25947d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is cached: 262356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 26436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Scroll so 1 falls off (though 3 is still not on screen) 26636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 50); 26736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is still cached, with a couple other recycled views: 269356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 27036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 5); 27136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 272a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 273a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Test 274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public void prefetchIsComputingLayout() { 275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // 100x100 pixel views 278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Override 282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik throws Throwable { 284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik View view = new View(getContext()); 285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumWidth(100); 286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumHeight(100); 287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik assertTrue(mRecyclerView.isComputingLayout()); 288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik return new RecyclerView.ViewHolder(view) {}; 289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik }); 291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setAdapter(mockAdapter); 293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik layout(100, 100); 295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0); 297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // prefetch an item, should still observe isComputingLayout in that create 299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 301b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 302b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0); 303b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 304b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 305b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Test 306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public void prefetchAfterOrientationChange() { 307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager layout = new LinearLayoutManager(getContext(), 308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager.VERTICAL, false); 309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setLayoutManager(layout); 310213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // 100x100 pixel views 312213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 3138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 3158a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 3168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik View view = new View(getContext()); 318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumWidth(100); 319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumHeight(100); 320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik assertTrue(mRecyclerView.isComputingLayout()); 321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return new RecyclerView.ViewHolder(view) {}; 322213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 3258a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {} 326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public int getItemCount() { 329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return 100; 330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik }); 332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout(100, 100); 334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout.setOrientation(LinearLayoutManager.HORIZONTAL); 336213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 337213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // Prefetch an item after changing orientation, before layout - shouldn't crash 338213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1); 339213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 340213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 341213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 342213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Test 3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public void prefetchDrag() { 3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // event dispatch requires a parent 3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik ViewGroup parent = new FrameLayout(getContext()); 3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik parent.addView(mRecyclerView); 3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setLayoutManager( 3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); 3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3520951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // 1000x1000 pixel views 3530951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 3568a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik View view = new View(getContext()); 3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumWidth(1000); 3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumHeight(1000); 3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return new RecyclerView.ViewHolder(view) {}; 3620951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3658a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public int getItemCount() { 3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return 100; 3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik }; 3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setAdapter(adapter); 3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik layout(1000, 1000); 3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik long time = SystemClock.uptimeMillis(); 3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0)); 3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // Consume slop 3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0)); 3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,30 3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0)); 3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 10,15 3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 4010951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,0 - IGNORED 4020951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 4030951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 4040951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev 4050951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev 4060951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 4070951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 4080951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Test 409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void prefetchItemsRespectDeadline() { 410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 412a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // 100x100 pixel views 413a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 4158a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 4168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 418a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik View view = new View(getContext()); 419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumWidth(100); 420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumHeight(100); 421945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(view) {}; 422a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 423a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 4258a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder( 4268a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull RecyclerView.ViewHolder holder, int position) { 427a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 429a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 430a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 431a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public int getItemCount() { 432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return 100; 433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 434a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik }; 435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setAdapter(adapter); 436a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 437dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 300); 438a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 439356880d3de117b067522ad452f4e3eed85ce444cChris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 440356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.scrollBy(0, 50); 441356880d3de117b067522ad452f4e3eed85ce444cChris Craik 442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 445a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe 446a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19); 447a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Timed prefetch 449945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 45047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 452a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // will have enough time to inflate/bind one view, and inflate another 453a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 1); 454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 455356880d3de117b067522ad452f4e3eed85ce444cChris Craik // Note: order/view below is an implementation detail 456356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12); 457a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 458a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 459a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Unbounded prefetch this time 46047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 461a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 462a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should finish all work 463a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 464a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 465356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14); 466945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 467945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 468945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Test 4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void partialPrefetchAvoidsViewRecycledCallback() { 4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // 100x100 pixel views 4731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4758a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 4768a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(getContext()); 4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumWidth(100); 4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumHeight(100); 4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 4821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4858a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return 100; 4921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4958a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { 4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // verify unbound view doesn't get 4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition()); 4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }; 5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(adapter); 5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 300); 5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.scrollBy(0, 50); 5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind 5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Timed prefetch 5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 5151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 5161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // will have enough time to inflate but not bind one view 5181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 5191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 5201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool() 5211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .mScrap.get(0).mScrapHeap.get(0); 5221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition()); 5231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 5241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public void prefetchStaggeredItemsPriority() { 527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik StaggeredGridLayoutManager sglm = 528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setLayoutManager(sglm); 530945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // first view 50x100 pixels, rest are 100x100 so second column is offset 532945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 5338a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 5358a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 5368a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 538945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 539945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 5418a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumWidth(100); 543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumHeight(position == 0 ? 50 : 100); 544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public int getItemCount() { 548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return 100; 549945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik }); 551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 552dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 200); 553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 556945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 1 * 558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___4___|___3___* 560945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 5 * 562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(0, sglm.getFirstChildPosition()); 566945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(4, sglm.getLastChildPosition()); 567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away 569356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 570945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {5, 0}, new Integer[] {6, 50}); 571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // Prefetch upward shows nothing 573356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.scrollBy(0, 100); 576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___2___|___1___* 581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 582945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 3 * 583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___6___|___5___* 585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 7 * 586945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 8 | 7 * 587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 589945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 590945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 591945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, sglm.getFirstChildPosition()); 592945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(6, sglm.getLastChildPosition()); 593945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 594945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away 595356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 596945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {7, 0}, new Integer[] {8, 50}); 597945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 598945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away 599356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10, 600945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {1, 0}, new Integer[] {0, 50}); 601945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 602dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 603dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Test 604695d9b8629fa048795298359d4845f0747364fafChris Craik public void prefetchStaggeredPastBoundary() { 605695d9b8629fa048795298359d4845f0747364fafChris Craik StaggeredGridLayoutManager sglm = 606695d9b8629fa048795298359d4845f0747364fafChris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 607695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setLayoutManager(sglm); 608695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 6098a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 610695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 6118a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 6128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 613695d9b8629fa048795298359d4845f0747364fafChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 614695d9b8629fa048795298359d4845f0747364fafChris Craik } 615695d9b8629fa048795298359d4845f0747364fafChris Craik 616695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 6178a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 618695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumWidth(100); 619695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumHeight(position == 0 ? 100 : 200); 620695d9b8629fa048795298359d4845f0747364fafChris Craik } 621695d9b8629fa048795298359d4845f0747364fafChris Craik 622695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 623695d9b8629fa048795298359d4845f0747364fafChris Craik public int getItemCount() { 624695d9b8629fa048795298359d4845f0747364fafChris Craik return 2; 625695d9b8629fa048795298359d4845f0747364fafChris Craik } 626695d9b8629fa048795298359d4845f0747364fafChris Craik }); 627695d9b8629fa048795298359d4845f0747364fafChris Craik 628695d9b8629fa048795298359d4845f0747364fafChris Craik layout(200, 100); 629695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.scrollBy(0, 50); 630695d9b8629fa048795298359d4845f0747364fafChris Craik 631695d9b8629fa048795298359d4845f0747364fafChris Craik /* Each row is 50 pixels: 632695d9b8629fa048795298359d4845f0747364fafChris Craik * ------------- * 633695d9b8629fa048795298359d4845f0747364fafChris Craik *___0___|___1___* 634695d9b8629fa048795298359d4845f0747364fafChris Craik * 0 | 1 * 635695d9b8629fa048795298359d4845f0747364fafChris Craik *_______|___1___* 636695d9b8629fa048795298359d4845f0747364fafChris Craik * | 1 * 637695d9b8629fa048795298359d4845f0747364fafChris Craik */ 638695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(2, mRecyclerView.getChildCount()); 639695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(0, sglm.getFirstChildPosition()); 640695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(1, sglm.getLastChildPosition()); 641695d9b8629fa048795298359d4845f0747364fafChris Craik 642695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch upward gets nothing 643695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 644695d9b8629fa048795298359d4845f0747364fafChris Craik 645695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch downward gets nothing (and doesn't crash...) 646695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10); 647695d9b8629fa048795298359d4845f0747364fafChris Craik } 648695d9b8629fa048795298359d4845f0747364fafChris Craik 649695d9b8629fa048795298359d4845f0747364fafChris Craik @Test 650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public void prefetchItemsSkipAnimations() { 651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(llm); 653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final int[] expandedPosition = new int[] {-1}; 654dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 655dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 656dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 6578a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 6588a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 6591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; 660dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 6638a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int height = expandedPosition[0] == position ? 400 : 100; 665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height)); 666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public int getItemCount() { 670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return 10; 671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik }; 673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // make move duration long enough to be able to see the effects 675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik itemAnimator.setMoveDuration(10000); 677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setAdapter(adapter); 678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik expandedPosition[0] = 1; 682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // insert payload to avoid creating a new view 683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik adapter.notifyItemChanged(1, new Object()); 684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view should be observable as hidden, uncached... 693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // ...but must not be removed for prefetch 699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 700dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 701dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 702dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount); 703dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0]; 704dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target view 2", 2, prefetchTarget); 705dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 706dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view still observable as hidden, uncached 707dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 708dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 709dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 710dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 711dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 712dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 713dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 714dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 715dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void viewHolderFindsNestedRecyclerViews() { 7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 7241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 7271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik throws Throwable { 7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new RecyclerView(getContext()); 7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }); 7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 7341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(mockAdapter); 7351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 200); 7371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 7391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onBindViewHolder( 740963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() { 7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 742963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas public boolean matches(RecyclerView.ViewHolder holder) { 7438123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return holder.itemView == holder.mNestedRecyclerView.get(); 7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }), 7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik anyInt(), 7471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik any(List.class)); 7481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7500fab45107f043a4856aad2e1fd785b396898616eChris Craik class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> { 7510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private static final int INNER_ITEM_COUNT = 20; 752f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik int mItemsBound = 0; 753f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 7540fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(View itemView) { 7561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik InnerAdapter() {} 7611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7638a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 7640fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(parent.getContext()); 7661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(view); 7681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7718a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 7720fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 773f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mItemsBound++; 774f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return INNER_ITEM_COUNT; 7791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7820fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { 7831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private boolean mReverseInner; 7850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 7860fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik private final RecyclerView mRecyclerView; 7881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(RecyclerView itemView) { 7891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView = itemView; 7911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ArrayList<InnerAdapter> mAdapters = new ArrayList<>(); 7950ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 7961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 7971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik OuterAdapter() { 7990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik this(false); 8000ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8010ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8020ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik OuterAdapter(boolean reverseInner) { 8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik this(reverseInner, 10); 8048123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 8058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 8068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter(boolean reverseInner, int itemCount) { 8070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mReverseInner = reverseInner; 8088123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < itemCount; i++) { 8091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mAdapters.add(new InnerAdapter()); 8100ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.add(null); 8111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8148123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik void addItem() { 8158123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik int index = getItemCount(); 8168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mAdapters.add(new InnerAdapter()); 8178123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mSavedStates.add(null); 8188123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik notifyItemInserted(index); 8198123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 8208123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 8211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 8230fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8240fab45107f043a4856aad2e1fd785b396898616eChris Craik 825bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik RecyclerView rv = new RecyclerView(parent.getContext()) { 826bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 827bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 828bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 829bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 830bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 831bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik }; 8321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 8330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 8341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setRecycledViewPool(mSharedPool); 8351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 8361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(rv); 8371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8408a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 8410fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8420fab45107f043a4856aad2e1fd785b396898616eChris Craik 8430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Tests may rely on bound holders not being shared between inner adapters, 8440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // since we force recycle here 8450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.swapAdapter(mAdapters.get(position), true); 8460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik Parcelable savedState = mSavedStates.get(position); 8480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik if (savedState != null) { 8490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 8500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(position, null); 8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Override 8558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull ViewHolder holder) { 8560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(holder.getAdapterPosition(), 8570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 8628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return mAdapters.size(); 8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 8670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchSimple() { 8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 869d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 8701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(new OuterAdapter()); 8731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(200, 200); 8751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 8761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 2 (default) 8781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 8801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder); 8811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder.mNestedRecyclerView); 8828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 8838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 8841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 4 8868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik ((LinearLayoutManager) innerView.getLayoutManager()) 8871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .setInitialPrefetchItemCount(4); 8881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8898123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3); 8901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8910ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8920ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu public void nestedPrefetchNotClearInnerStructureChangeFlag() { 8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu LinearLayoutManager llm = new LinearLayoutManager(getContext()); 8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertEquals(2, llm.getInitialPrefetchItemCount()); 8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setLayoutManager(llm); 8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setAdapter(new OuterAdapter()); 8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch 2 (default) 9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder); 9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder.mNestedRecyclerView); 9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView innerView = holder.mNestedRecyclerView.get(); 9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.Adapter innerAdapter = innerView.getAdapter(); 9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // mStructureChanged is initially true before first layout pass. 9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // layout position 2 and clear mStructureChanged 9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(2); 9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(0); 9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.mState.mStructureChanged); 9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.hasPendingAdapterUpdates()); 9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // notify change on the cached innerView. 9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerAdapter.notifyDataSetChanged(); 9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch again 9290fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9300fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu ((LinearLayoutManager) innerView.getLayoutManager()) 9310fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu .setInitialPrefetchItemCount(2); 9320fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9330fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 9340fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9350fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // The re-prefetch is not necessary get the same inner view but we will get same Adapter 9360fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9370fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerView = holder.mNestedRecyclerView.get(); 9380fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertSame(innerAdapter, innerView.getAdapter()); 9390fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch shouldn't clear the mStructureChanged flag 9400fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9410fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9420fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu } 9430fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9440fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu @Test 9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchReverseInner() { 9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true)); 9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // anchor from right side, should see last two positions 9568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19); 9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchOffset() { 9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter()); 9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing 9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0); 9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner.scrollBy(550, 0); 9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition); 9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition); 9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition); 9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // scroll down 4 rows, up 3 so row 0 is adjacent but uncached 9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, 400); 9750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, -300); 9760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9770ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // top row no longer present 9780ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0); 9790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9800ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch upward, and validate that we've gotten the top row with correct offsets 9810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 9820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView; 9840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6); 9850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch 4 9870ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4); 9880ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9890ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8); 9900ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik @Test 993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik public void nestedPrefetchNotReset() { 994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik OuterAdapter outerAdapter = new OuterAdapter(); 996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setAdapter(outerAdapter); 997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik layout(200, 200); 999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // prefetch row 2, items 0 & 1 1003f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound); 1004f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1005f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 10068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 1007f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1008f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertNotNull(innerRecyclerView); 1009f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1); 1010f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 1011f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1012f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // new row comes on, triggers layout... 1013f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.scrollBy(0, 50); 1014f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1015f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // ... which shouldn't require new items to be bound, 1016f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // as prefetch has already done that work 1017f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 1018f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 10190fab45107f043a4856aad2e1fd785b396898616eChris Craik 10200fab45107f043a4856aad2e1fd785b396898616eChris Craik static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) { 10210fab45107f043a4856aad2e1fd785b396898616eChris Craik ChildHelper childHelper = recyclerView.mChildHelper; 10220fab45107f043a4856aad2e1fd785b396898616eChris Craik 10230fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(childCount, childHelper.getUnfilteredChildCount()); 10240fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) { 10250fab45107f043a4856aad2e1fd785b396898616eChris Craik assertFalse(recyclerView.getChildViewHolder( 10260fab45107f043a4856aad2e1fd785b396898616eChris Craik childHelper.getUnfilteredChildAt(i)).isInvalid()); 10270fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10280fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10290fab45107f043a4856aad2e1fd785b396898616eChris Craik 10300fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 10310fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchCacheNotTouched() { 10320fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10330fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10340fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 10350fab45107f043a4856aad2e1fd785b396898616eChris Craik 10360fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 10370fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.scrollBy(0, 100); 10380fab45107f043a4856aad2e1fd785b396898616eChris Craik 10390fab45107f043a4856aad2e1fd785b396898616eChris Craik // item 0 is cached 10400fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10410fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0); 10428123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10430fab45107f043a4856aad2e1fd785b396898616eChris Craik 10440fab45107f043a4856aad2e1fd785b396898616eChris Craik // try and prefetch it 10450fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 10460fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 10470fab45107f043a4856aad2e1fd785b396898616eChris Craik 10480fab45107f043a4856aad2e1fd785b396898616eChris Craik // make sure cache's inner items aren't rebound unnecessarily 10490fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik @Test 10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik public void nestedRemoveAnimatingView() { 10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter outerAdapter = new OuterAdapter(false, 1); 10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setAdapter(outerAdapter); 10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30)); 10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); 10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // Insert 3 items - only first one in viewport, so only it animates 10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < 3; i++) { 10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik outerAdapter.addItem(); 10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); // layout again to kick off animation 10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // item 1 is animating, so scroll it out of viewport 10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.scrollBy(0, 200); 10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10728123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for... 10738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10748123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(1, mRecycler.mCachedViews.size()); 10758123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0); 10768123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10778123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10788123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // until animation ends 10798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().endAnimations(); 10808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10818123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecycler.mCachedViews.size()); 10828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1); 10838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10848123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10858123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) { 10868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertNotNull(viewHolder.mNestedRecyclerView); 10878123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10880fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10890fab45107f043a4856aad2e1fd785b396898616eChris Craik 10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik @Test 10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik public void nestedExpandCacheCorrectly() { 10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE; 10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setAdapter(outerAdapter); 10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik layout(200, 200); 10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // after initial prefetch, view cache max expanded by number of inner items prefetched (2) 11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik try { 11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // Note: As a hack, we not only must manually dispatch attachToWindow(), but we 11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker 11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // is registered to the wrong thread, since @setup is called on a different thread 11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // from @Test. Assert this, so this test can be fixed when setup == test thread. 11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size()); 11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.isAttachedToWindow()); 11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onAttachedToWindow(); 11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // bring prefetch view into viewport, at which point it shouldn't have cache expanded... 11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.scrollBy(0, 100); 11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11229ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11239ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11249ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11259ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // until a valid horizontal prefetch caches an item, and expands view count by one 11269ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mPrefetchRegistry.setPrefetchVector(1, 0); 11279ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker 11289ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11299ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11309ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11319ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } finally { 11329ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik if (innerView.isAttachedToWindow()) { 11339ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onDetachedFromWindow(); 11349ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11359ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11369ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11379ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11380fab45107f043a4856aad2e1fd785b396898616eChris Craik /** 11390fab45107f043a4856aad2e1fd785b396898616eChris Craik * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter 11400fab45107f043a4856aad2e1fd785b396898616eChris Craik * to update data for the inner RecyclerViews when containing ViewHolder is bound. 11410fab45107f043a4856aad2e1fd785b396898616eChris Craik */ 11420fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> { 11430fab45107f043a4856aad2e1fd785b396898616eChris Craik private static final int OUTER_ITEM_COUNT = 10; 11440fab45107f043a4856aad2e1fd785b396898616eChris Craik 11450fab45107f043a4856aad2e1fd785b396898616eChris Craik private boolean mReverseInner; 11460fab45107f043a4856aad2e1fd785b396898616eChris Craik 11470fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 11480fab45107f043a4856aad2e1fd785b396898616eChris Craik private final RecyclerView mRecyclerView; 11490fab45107f043a4856aad2e1fd785b396898616eChris Craik private final InnerAdapter mAdapter; 11500fab45107f043a4856aad2e1fd785b396898616eChris Craik ViewHolder(RecyclerView itemView) { 11510fab45107f043a4856aad2e1fd785b396898616eChris Craik super(itemView); 11520fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView = itemView; 11530fab45107f043a4856aad2e1fd785b396898616eChris Craik mAdapter = new InnerAdapter(); 11540fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(mAdapter); 11550fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11560fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11570fab45107f043a4856aad2e1fd785b396898616eChris Craik 11580fab45107f043a4856aad2e1fd785b396898616eChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 11590fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 11600fab45107f043a4856aad2e1fd785b396898616eChris Craik 11610fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter() { 11620fab45107f043a4856aad2e1fd785b396898616eChris Craik this(false); 11630fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11640fab45107f043a4856aad2e1fd785b396898616eChris Craik 11650fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter(boolean reverseInner) { 11660fab45107f043a4856aad2e1fd785b396898616eChris Craik mReverseInner = reverseInner; 11670fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i <= OUTER_ITEM_COUNT; i++) { 11680fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.add(null); 11690fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11700fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11710fab45107f043a4856aad2e1fd785b396898616eChris Craik 11728a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 11730fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11748a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 11750fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11760fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView rv = new RecyclerView(parent.getContext()); 11770fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 11780fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 11790fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setRecycledViewPool(mSharedPool); 11800fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 11810fab45107f043a4856aad2e1fd785b396898616eChris Craik return new ViewHolder(rv); 11820fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11830fab45107f043a4856aad2e1fd785b396898616eChris Craik 11840fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11858a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 11860fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11870fab45107f043a4856aad2e1fd785b396898616eChris Craik // if we had actual data to put into our adapter, this is where we'd do it... 11880fab45107f043a4856aad2e1fd785b396898616eChris Craik 11890fab45107f043a4856aad2e1fd785b396898616eChris Craik // ... then notify the adapter that it has new content: 11900fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mAdapter.notifyDataSetChanged(); 11910fab45107f043a4856aad2e1fd785b396898616eChris Craik 11920fab45107f043a4856aad2e1fd785b396898616eChris Craik Parcelable savedState = mSavedStates.get(position); 11930fab45107f043a4856aad2e1fd785b396898616eChris Craik if (savedState != null) { 11940fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 11950fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.set(position, null); 11960fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11970fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11980fab45107f043a4856aad2e1fd785b396898616eChris Craik 11990fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 12008a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull ViewHolder holder) { 1201570ab881350c836743247140a0953f87acc21c8dChris Craik if (holder.getAdapterPosition() >= 0) { 1202570ab881350c836743247140a0953f87acc21c8dChris Craik mSavedStates.set(holder.getAdapterPosition(), 1203570ab881350c836743247140a0953f87acc21c8dChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 1204570ab881350c836743247140a0953f87acc21c8dChris Craik } 12050fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12060fab45107f043a4856aad2e1fd785b396898616eChris Craik 12070fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 12080fab45107f043a4856aad2e1fd785b396898616eChris Craik public int getItemCount() { 12090fab45107f043a4856aad2e1fd785b396898616eChris Craik return OUTER_ITEM_COUNT; 12100fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12110fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12120fab45107f043a4856aad2e1fd785b396898616eChris Craik 12130fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 12140fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchDiscardStaleChildren() { 12150fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 1216d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 12170fab45107f043a4856aad2e1fd785b396898616eChris Craik 12180fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(llm); 12190fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 12200fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 12210fab45107f043a4856aad2e1fd785b396898616eChris Craik 12220fab45107f043a4856aad2e1fd785b396898616eChris Craik // zero cache, so item we prefetch can't already be ready 12230fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setItemViewCacheSize(0); 12240fab45107f043a4856aad2e1fd785b396898616eChris Craik 12250fab45107f043a4856aad2e1fd785b396898616eChris Craik // layout 3 items, then resize to 2... 12260fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 300); 12270fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 12280fab45107f043a4856aad2e1fd785b396898616eChris Craik 12290fab45107f043a4856aad2e1fd785b396898616eChris Craik // so 1 item is evicted into the RecycledViewPool (bypassing cache) 12300fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 12310fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 12320fab45107f043a4856aad2e1fd785b396898616eChris Craik 12330fab45107f043a4856aad2e1fd785b396898616eChris Craik // This is a simple imitation of other behavior (namely, varied types in the outer adapter) 12340fab45107f043a4856aad2e1fd785b396898616eChris Craik // that results in the same initial state to test: items in the pool with attached children 12350fab45107f043a4856aad2e1fd785b396898616eChris Craik for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) { 12360fab45107f043a4856aad2e1fd785b396898616eChris Craik // verify that children are attached and valid, since the RVs haven't been rebound 12370fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12388123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout); 12398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 12400fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12410fab45107f043a4856aad2e1fd785b396898616eChris Craik 12420fab45107f043a4856aad2e1fd785b396898616eChris Craik // prefetch the outer item bind, but without enough time to do any inner binds 12430fab45107f043a4856aad2e1fd785b396898616eChris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 12440fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 12450fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 12460fab45107f043a4856aad2e1fd785b396898616eChris Craik 12470fab45107f043a4856aad2e1fd785b396898616eChris Craik // 2 is prefetched without children 12480fab45107f043a4856aad2e1fd785b396898616eChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2); 12490fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 12500fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder); 12510fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount()); 12538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size()); 12540fab45107f043a4856aad2e1fd785b396898616eChris Craik 12550fab45107f043a4856aad2e1fd785b396898616eChris Craik // but if we give it more time to bind items, it'll now acquire its inner items 12560fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 12578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1); 12580fab45107f043a4856aad2e1fd785b396898616eChris Craik } 1259570ab881350c836743247140a0953f87acc21c8dChris Craik 1260570ab881350c836743247140a0953f87acc21c8dChris Craik 1261570ab881350c836743247140a0953f87acc21c8dChris Craik @Test 1262570ab881350c836743247140a0953f87acc21c8dChris Craik public void nestedPrefetchDiscardStalePrefetch() { 1263570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 1264570ab881350c836743247140a0953f87acc21c8dChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 1265570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setAdapter(outerAdapter); 1266570ab881350c836743247140a0953f87acc21c8dChris Craik 1267570ab881350c836743247140a0953f87acc21c8dChris Craik // zero cache, so item we prefetch can't already be ready 1268570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setItemViewCacheSize(0); 1269570ab881350c836743247140a0953f87acc21c8dChris Craik 1270570ab881350c836743247140a0953f87acc21c8dChris Craik // layout as 2x2, starting on row index 2, with empty cache 1271570ab881350c836743247140a0953f87acc21c8dChris Craik layout(200, 200); 1272570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.scrollBy(0, 200); 1273570ab881350c836743247140a0953f87acc21c8dChris Craik 1274570ab881350c836743247140a0953f87acc21c8dChris Craik // no views cached, or previously used (so we can trust number in mItemsBound) 1275570ab881350c836743247140a0953f87acc21c8dChris Craik mRecycler.mRecyclerPool.clear(); 1276570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 1277570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 1278570ab881350c836743247140a0953f87acc21c8dChris Craik 1279570ab881350c836743247140a0953f87acc21c8dChris Craik // prefetch the outer item and its inner children 1280570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1281570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1282570ab881350c836743247140a0953f87acc21c8dChris Craik 1283570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched with 2 inner children, first two binds 1284570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1285570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4); 1286570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder); 1287570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder.mNestedRecyclerView); 1288570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 1289570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1290570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1291570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1292570ab881350c836743247140a0953f87acc21c8dChris Craik 1293570ab881350c836743247140a0953f87acc21c8dChris Craik // notify data set changed, so any previously prefetched items invalid, and re-prefetch 1294570ab881350c836743247140a0953f87acc21c8dChris Craik innerRecyclerView.getAdapter().notifyDataSetChanged(); 1295570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1296570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1297570ab881350c836743247140a0953f87acc21c8dChris Craik 1298570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched again... 1299570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1300570ab881350c836743247140a0953f87acc21c8dChris Craik 1301570ab881350c836743247140a0953f87acc21c8dChris Craik // reusing the same instance with 2 inner children... 1302570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4)); 1303570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(innerRecyclerView, holder.mNestedRecyclerView.get()); 1304570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1305570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1306570ab881350c836743247140a0953f87acc21c8dChris Craik 1307570ab881350c836743247140a0953f87acc21c8dChris Craik // ... but there should be two new binds 1308570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1309570ab881350c836743247140a0953f87acc21c8dChris Craik } 1310c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1311c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard @Test 1312c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard public void setRecycledViewPool_followedByTwoSetAdapters_clearsRecycledViewPool() { 1313c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(new View(getContext())) {}; 1314c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard viewHolder.mItemViewType = 123; 1315c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.Adapter adapter = mock(RecyclerView.Adapter.class); 1316c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView recyclerView = new RecyclerView(getContext()); 1317c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool(); 1318c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recycledViewPool.putRecycledView(viewHolder); 1319c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1320c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.setRecycledViewPool(recycledViewPool); 1321c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.setAdapter(adapter); 1322c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.setAdapter(adapter); 1323c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1324c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard assertThat(recycledViewPool.getRecycledViewCount(123), is(equalTo(0))); 1325c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard } 1326c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1327c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard @Test 1328c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard public void setRecycledViewPool_followedByTwoSwapAdapters_doesntClearRecycledViewPool() { 1329c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(new View(getContext())) {}; 1330c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard viewHolder.mItemViewType = 123; 1331c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.Adapter adapter = mock(RecyclerView.Adapter.class); 1332c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView recyclerView = new RecyclerView(getContext()); 1333c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool(); 1334c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recycledViewPool.putRecycledView(viewHolder); 1335c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1336c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.setRecycledViewPool(recycledViewPool); 1337c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.swapAdapter(adapter, false); 1338c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard recyclerView.swapAdapter(adapter, false); 1339c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard 1340c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard assertThat(recycledViewPool.getRecycledViewCount(123), is(equalTo(1))); 1341c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard } 1342945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik} 1343