RecyclerViewCacheTest.java revision c95a6f1f125ad3a7e1f9f79bccf4b2603bc40eba
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 19945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals; 200fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse; 211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals; 22dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull; 230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame; 24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue; 25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any; 26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt; 271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat; 28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock; 29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never; 30ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times; 31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify; 32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when; 33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas 3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context; 35e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build; 360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable; 370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock; 3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry; 39ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress; 40754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest; 4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4; 420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent; 4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View; 4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup; 450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout; 4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 47c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull; 48c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 49356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After; 5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before; 5136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test; 5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith; 53963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher; 5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock; 5536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer; 5636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList; 5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List; 59a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit; 6036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest 62e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 6336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class) 6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest { 65a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView mRecyclerView; 6636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Recycler mRecycler; 6736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private class TimeMockingRecyclerView extends RecyclerView { 69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private long mMockNanoTime = 0; 70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView(Context context) { 72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik super(context); 73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void registerTimePassingMs(long ms) { 76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms); 77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik long getNanoTime() { 81a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return mMockNanoTime; 82a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 83bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik 84bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 85bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 87bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 88bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 89356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 90a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 9136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Before 92356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void setup() throws Exception { 93a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView = new TimeMockingRecyclerView(getContext()); 94356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onAttachedToWindow(); 9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler = mRecyclerView.mRecycler; 9636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 9736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 98356880d3de117b067522ad452f4e3eed85ce444cChris Craik @After 99356880d3de117b067522ad452f4e3eed85ce444cChris Craik public void teardown() throws Exception { 100356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (mRecyclerView.isAttachedToWindow()) { 101356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.onDetachedFromWindow(); 102356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 103356880d3de117b067522ad452f4e3eed85ce444cChris Craik GapWorker gapWorker = GapWorker.sGapWorker.get(); 104356880d3de117b067522ad452f4e3eed85ce444cChris Craik if (gapWorker != null) { 105356880d3de117b067522ad452f4e3eed85ce444cChris Craik assertTrue(gapWorker.mRecyclerViews.isEmpty()); 106356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 107356880d3de117b067522ad452f4e3eed85ce444cChris Craik } 108356880d3de117b067522ad452f4e3eed85ce444cChris Craik 10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private Context getContext() { 11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return InstrumentationRegistry.getContext(); 11136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 113dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik private void layout(int width, int height) { 114dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.measure( 115dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), 116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); 117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.layout(0, 0, width, height); 118dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 119dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchReusesCacheItems() { 12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() { 12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.LayoutParams generateDefaultLayoutParams() { 12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik ViewGroup.LayoutParams.WRAP_CONTENT); 12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 1301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, 1313104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik LayoutPrefetchRegistry prefetchManager) { 132945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(0, 0); 133945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(1, 0); 134945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik prefetchManager.addPosition(2, 0); 13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }; 14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(prefetchingLayoutManager); 14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(10); 15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 155dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(320, 320); 15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt()); 15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onBindViewHolder( 15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Prefetch multiple times... 16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < 4; i++) { 16447d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // ...but should only see the same three items fetched/bound once each 16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onBindViewHolder( 16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 1721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2); 17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedWithInserts() { 178dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 186dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik View view = new View(getContext()); 187dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumWidth(100); 188dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik view.setMinimumHeight(100); 189dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return new RecyclerView.ViewHolder(view) {}; 19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 195dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 100); 19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 197945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax); 198dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 19947d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 200945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax); 201945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 2021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5); 20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // further views recycled, as though from scrolling, shouldn't evict prefetched views: 20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(10)); 206dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10); 20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(20)); 209dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20); 21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(30)); 212dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30); 21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(40)); 215dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40); 21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // After clearing the cache, the prefetch priorities should be cleared as well: 21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.mRecycler.recycleAndClearCachedViews(); 219dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) { 22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(i)); 22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // cache only contains most recent positions, no priority for previous prefetches: 224356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90); 22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedOnScroll() { 22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // 100x100 pixel views 23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik View view = new View(getContext()); 23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumWidth(100); 24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumHeight(100); 24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(view) {}; 24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // NOTE: requested cache size must be smaller than span count so two rows cannot fit 24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setItemViewCacheSize(2); 24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 250dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 150); 25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 75); 25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 25336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // rows 0, 1, and 2 are all attached and visible. Prefetch row 3: 255945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 25647d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 25736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 25836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is cached: 259356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Scroll so 1 falls off (though 3 is still not on screen) 26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 50); 26436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is still cached, with a couple other recycled views: 266356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11); 26736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 5); 26836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 269a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 270a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Test 271b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public void prefetchIsComputingLayout() { 272b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 273b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // 100x100 pixel views 275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Override 279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik throws Throwable { 281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik View view = new View(getContext()); 282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumWidth(100); 283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik view.setMinimumHeight(100); 284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik assertTrue(mRecyclerView.isComputingLayout()); 285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik return new RecyclerView.ViewHolder(view) {}; 286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik }); 288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.setAdapter(mockAdapter); 290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik layout(100, 100); 292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0); 294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik // prefetch an item, should still observe isComputingLayout in that create 296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0); 300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik } 301b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik 302b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik @Test 303213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public void prefetchAfterOrientationChange() { 304213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager layout = new LinearLayoutManager(getContext(), 305213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik LinearLayoutManager.VERTICAL, false); 306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setLayoutManager(layout); 307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // 100x100 pixel views 309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 3108a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 3128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 3138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik View view = new View(getContext()); 315213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumWidth(100); 316213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik view.setMinimumHeight(100); 317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik assertTrue(mRecyclerView.isComputingLayout()); 318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return new RecyclerView.ViewHolder(view) {}; 319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 3228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {} 323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Override 325213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik public int getItemCount() { 326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik return 100; 327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik }); 329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout(100, 100); 331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik layout.setOrientation(LinearLayoutManager.HORIZONTAL); 333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik // Prefetch an item after changing orientation, before layout - shouldn't crash 335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1); 336213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 337213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik } 338213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik 339213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik @Test 3400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public void prefetchDrag() { 3410951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // event dispatch requires a parent 3420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik ViewGroup parent = new FrameLayout(getContext()); 3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik parent.addView(mRecyclerView); 3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setLayoutManager( 3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); 3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // 1000x1000 pixel views 3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3528a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 3538a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3550951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik View view = new View(getContext()); 3560951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumWidth(1000); 3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik view.setMinimumHeight(1000); 3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return new RecyclerView.ViewHolder(view) {}; 3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3628a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.registerTimePassingMs(5); 3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3650951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Override 3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik public int getItemCount() { 3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik return 100; 3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik }; 3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.setAdapter(adapter); 3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik layout(1000, 1000); 3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik long time = SystemClock.uptimeMillis(); 3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0)); 3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // Consume slop 3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0)); 3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,30 3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0)); 3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 10,15 3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); 3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); 3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik // move by 0,0 - IGNORED 3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik mRecyclerView.onTouchEvent( 4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0)); 4010951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev 4020951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev 4030951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik } 4040951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik 4050951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik @Test 406a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void prefetchItemsRespectDeadline() { 407a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 408a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // 100x100 pixel views 410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 4128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 4138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 415a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik View view = new View(getContext()); 416a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumWidth(100); 417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumHeight(100); 418945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(view) {}; 419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 421a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 4228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder( 4238a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull RecyclerView.ViewHolder holder, int position) { 424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 425a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 426a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 427a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public int getItemCount() { 429a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return 100; 430a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 431a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik }; 432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setAdapter(adapter); 433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 434dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(300, 300); 435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 436356880d3de117b067522ad452f4e3eed85ce444cChris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 437356880d3de117b067522ad452f4e3eed85ce444cChris Craik mRecyclerView.scrollBy(0, 50); 438356880d3de117b067522ad452f4e3eed85ce444cChris Craik 439a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 440a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 441a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe 443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19); 444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 445a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Timed prefetch 446945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 44747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 449a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // will have enough time to inflate/bind one view, and inflate another 450a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 1); 451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 452356880d3de117b067522ad452f4e3eed85ce444cChris Craik // Note: order/view below is an implementation detail 453356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12); 454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 455a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 456a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Unbounded prefetch this time 45747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 458a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 459a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should finish all work 460a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 461a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 462356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14); 463945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 464945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 465945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Test 4661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void partialPrefetchAvoidsViewRecycledCallback() { 4671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 4681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // 100x100 pixel views 4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4728a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 4738a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(getContext()); 4761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumWidth(100); 4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setMinimumHeight(100); 4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4828a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.registerTimePassingMs(5); 4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return 100; 4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 4928a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { 4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // verify unbound view doesn't get 4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition()); 4951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }; 4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(adapter); 4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 300); 5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // offset scroll so that no prefetch-able views are directly adjacent to viewport 5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.scrollBy(0, 50); 5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind 5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // Timed prefetch 5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // will have enough time to inflate but not bind one view 5151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 5161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 5171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool() 5181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .mScrap.get(0).mScrapHeap.get(0); 5191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition()); 5201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 5211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 5221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 523945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public void prefetchStaggeredItemsPriority() { 524945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik StaggeredGridLayoutManager sglm = 525945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setLayoutManager(sglm); 527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // first view 50x100 pixels, rest are 100x100 so second column is offset 529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 5308a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 5328a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 5338a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 535945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 536945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 5388a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 539945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumWidth(100); 540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik holder.itemView.setMinimumHeight(position == 0 ? 50 : 100); 541945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik @Override 544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik public int getItemCount() { 545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik return 100; 546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik }); 548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 549dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 200); 550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 552945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 1 * 555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 556945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___4___|___3___* 557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 5 * 559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 560945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(0, sglm.getFirstChildPosition()); 563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(4, sglm.getLastChildPosition()); 564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away 566356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {5, 0}, new Integer[] {6, 50}); 568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 569945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // Prefetch upward shows nothing 570356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik mRecyclerView.scrollBy(0, 100); 573945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik /* Each row is 50 pixels: 575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ------------- * 576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 0 | 1 * 577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___2___|___1___* 578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 2 | 3 * 579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 3 * 580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 4 | 5 * 581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik *___6___|___5___* 582945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 6 | 7 * 583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * 8 | 7 * 584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik * ... * 585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik */ 586945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(5, mRecyclerView.getChildCount()); 588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(2, sglm.getFirstChildPosition()); 589945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik assertEquals(6, sglm.getLastChildPosition()); 590945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 591945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away 592356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10, 593945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {7, 0}, new Integer[] {8, 50}); 594945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik 595945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away 596356880d3de117b067522ad452f4e3eed85ce444cChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10, 597945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik new Integer[] {1, 0}, new Integer[] {0, 50}); 598945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik } 599dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 600dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Test 601695d9b8629fa048795298359d4845f0747364fafChris Craik public void prefetchStaggeredPastBoundary() { 602695d9b8629fa048795298359d4845f0747364fafChris Craik StaggeredGridLayoutManager sglm = 603695d9b8629fa048795298359d4845f0747364fafChris Craik new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 604695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setLayoutManager(sglm); 605695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.setAdapter(new RecyclerView.Adapter() { 6068a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 607695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 6088a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 6098a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 610695d9b8629fa048795298359d4845f0747364fafChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 611695d9b8629fa048795298359d4845f0747364fafChris Craik } 612695d9b8629fa048795298359d4845f0747364fafChris Craik 613695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 6148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 615695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumWidth(100); 616695d9b8629fa048795298359d4845f0747364fafChris Craik holder.itemView.setMinimumHeight(position == 0 ? 100 : 200); 617695d9b8629fa048795298359d4845f0747364fafChris Craik } 618695d9b8629fa048795298359d4845f0747364fafChris Craik 619695d9b8629fa048795298359d4845f0747364fafChris Craik @Override 620695d9b8629fa048795298359d4845f0747364fafChris Craik public int getItemCount() { 621695d9b8629fa048795298359d4845f0747364fafChris Craik return 2; 622695d9b8629fa048795298359d4845f0747364fafChris Craik } 623695d9b8629fa048795298359d4845f0747364fafChris Craik }); 624695d9b8629fa048795298359d4845f0747364fafChris Craik 625695d9b8629fa048795298359d4845f0747364fafChris Craik layout(200, 100); 626695d9b8629fa048795298359d4845f0747364fafChris Craik mRecyclerView.scrollBy(0, 50); 627695d9b8629fa048795298359d4845f0747364fafChris Craik 628695d9b8629fa048795298359d4845f0747364fafChris Craik /* Each row is 50 pixels: 629695d9b8629fa048795298359d4845f0747364fafChris Craik * ------------- * 630695d9b8629fa048795298359d4845f0747364fafChris Craik *___0___|___1___* 631695d9b8629fa048795298359d4845f0747364fafChris Craik * 0 | 1 * 632695d9b8629fa048795298359d4845f0747364fafChris Craik *_______|___1___* 633695d9b8629fa048795298359d4845f0747364fafChris Craik * | 1 * 634695d9b8629fa048795298359d4845f0747364fafChris Craik */ 635695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(2, mRecyclerView.getChildCount()); 636695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(0, sglm.getFirstChildPosition()); 637695d9b8629fa048795298359d4845f0747364fafChris Craik assertEquals(1, sglm.getLastChildPosition()); 638695d9b8629fa048795298359d4845f0747364fafChris Craik 639695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch upward gets nothing 640695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10); 641695d9b8629fa048795298359d4845f0747364fafChris Craik 642695d9b8629fa048795298359d4845f0747364fafChris Craik // prefetch downward gets nothing (and doesn't crash...) 643695d9b8629fa048795298359d4845f0747364fafChris Craik CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10); 644695d9b8629fa048795298359d4845f0747364fafChris Craik } 645695d9b8629fa048795298359d4845f0747364fafChris Craik 646695d9b8629fa048795298359d4845f0747364fafChris Craik @Test 647dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public void prefetchItemsSkipAnimations() { 648dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 649dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setLayoutManager(llm); 650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final int[] expandedPosition = new int[] {-1}; 651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik final RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 6548a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public RecyclerView.ViewHolder onCreateViewHolder( 6558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 6561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(new View(parent.getContext())) {}; 657dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 658dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 659dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 6608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int height = expandedPosition[0] == position ? 400 : 100; 662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height)); 663dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik @Override 666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik public int getItemCount() { 667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik return 10; 668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik }; 670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // make move duration long enough to be able to see the effects 672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator(); 673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik itemAnimator.setMoveDuration(10000); 674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.setAdapter(adapter); 675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik expandedPosition[0] = 1; 679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // insert payload to avoid creating a new view 680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik adapter.notifyItemChanged(1, new Object()); 681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik layout(200, 400); 684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view should be observable as hidden, uncached... 690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // ...but must not be removed for prefetch 696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount); 700dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0]; 701dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals("Prefetch must target view 2", 2, prefetchTarget); 702dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 703dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik // animating view still observable as hidden, uncached 704dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2); 705dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertNotNull("Animating view should be found, hidden", 706dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik mRecyclerView.mChildHelper.findHiddenNonRemovedView(2)); 707dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2)); 708dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik 709dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertTrue(itemAnimator.isRunning()); 710dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(2, llm.getChildCount()); 711dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik assertEquals(4, mRecyclerView.getChildCount()); 712dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik } 7131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 7151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public void viewHolderFindsNestedRecyclerViews() { 7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 7241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik throws Throwable { 7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new RecyclerView(getContext()); 7261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new RecyclerView.ViewHolder(view) {}; 7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }); 7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik when(mockAdapter.getItemCount()).thenReturn(100); 7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(mockAdapter); 7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(100, 200); 7341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 7361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik verify(mockAdapter, times(2)).onBindViewHolder( 737963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() { 7381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 739963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas public boolean matches(RecyclerView.ViewHolder holder) { 7408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return holder.itemView == holder.mNestedRecyclerView.get(); 7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik }), 7431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik anyInt(), 7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik any(List.class)); 7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7470fab45107f043a4856aad2e1fd785b396898616eChris Craik class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> { 7480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private static final int INNER_ITEM_COUNT = 20; 749f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik int mItemsBound = 0; 750f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 7510fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(View itemView) { 7531e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik InnerAdapter() {} 7581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 7610fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik View view = new View(parent.getContext()); 7631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik view.setLayoutParams(new RecyclerView.LayoutParams(100, 100)); 7641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(view); 7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7688a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 7690fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 770f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mItemsBound++; 771f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 7721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 7741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return INNER_ITEM_COUNT; 7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7790fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { 7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik private boolean mReverseInner; 7820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 7830fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 7841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik private final RecyclerView mRecyclerView; 7851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ViewHolder(RecyclerView itemView) { 7861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik super(itemView); 7871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView = itemView; 7881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 7901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik ArrayList<InnerAdapter> mAdapters = new ArrayList<>(); 7920ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 7941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 7951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik OuterAdapter() { 7960ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik this(false); 7970ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 7980ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 7990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik OuterAdapter(boolean reverseInner) { 8008123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik this(reverseInner, 10); 8018123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 8028123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter(boolean reverseInner, int itemCount) { 8040ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mReverseInner = reverseInner; 8058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < itemCount; i++) { 8061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mAdapters.add(new InnerAdapter()); 8070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.add(null); 8081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8118123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik void addItem() { 8128123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik int index = getItemCount(); 8138123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mAdapters.add(new InnerAdapter()); 8148123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mSavedStates.add(null); 8158123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik notifyItemInserted(index); 8168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 8178123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 8181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8198a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 8200fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8210fab45107f043a4856aad2e1fd785b396898616eChris Craik 822bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik RecyclerView rv = new RecyclerView(parent.getContext()) { 823bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik @Override 824bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik public int getWindowVisibility() { 825bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik // Pretend to be visible to avoid being filtered out 826bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik return View.VISIBLE; 827bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik } 828bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik }; 8291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 8300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 8311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setRecycledViewPool(mSharedPool); 8321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 8331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik return new ViewHolder(rv); 8341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8378a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 8380fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 8390fab45107f043a4856aad2e1fd785b396898616eChris Craik 8400ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Tests may rely on bound holders not being shared between inner adapters, 8410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // since we force recycle here 8420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.swapAdapter(mAdapters.get(position), true); 8430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik Parcelable savedState = mSavedStates.get(position); 8450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik if (savedState != null) { 8460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 8470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(position, null); 8480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 8500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Override 8528a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull ViewHolder holder) { 8530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mSavedStates.set(holder.getAdapterPosition(), 8540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 8551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Override 8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik public int getItemCount() { 8598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik return mAdapters.size(); 8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik @Test 8640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchSimple() { 8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 866d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 8671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setLayoutManager(llm); 8691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.setAdapter(new OuterAdapter()); 8701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik layout(200, 200); 8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 8731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 2 (default) 8751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 8771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder); 8781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik assertNotNull(holder.mNestedRecyclerView); 8798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 8808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 8811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik 8821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik // prefetch 4 8838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik ((LinearLayoutManager) innerView.getLayoutManager()) 8841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik .setInitialPrefetchItemCount(4); 8851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 8868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3); 8871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik } 8880ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 8890ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 8900fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu public void nestedPrefetchNotClearInnerStructureChangeFlag() { 8910fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu LinearLayoutManager llm = new LinearLayoutManager(getContext()); 8920fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertEquals(2, llm.getInitialPrefetchItemCount()); 8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setLayoutManager(llm); 8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.setAdapter(new OuterAdapter()); 8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch 2 (default) 9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder); 9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertNotNull(holder.mNestedRecyclerView); 9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView innerView = holder.mNestedRecyclerView.get(); 9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu RecyclerView.Adapter innerAdapter = innerView.getAdapter(); 9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // mStructureChanged is initially true before first layout pass. 9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // layout position 2 and clear mStructureChanged 9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(2); 9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.scrollToPosition(0); 9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu layout(200, 200); 9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.mState.mStructureChanged); 9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertFalse(innerView.hasPendingAdapterUpdates()); 9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // notify change on the cached innerView. 9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerAdapter.notifyDataSetChanged(); 9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch again 9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu ((LinearLayoutManager) innerView.getLayoutManager()) 9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu .setInitialPrefetchItemCount(2); 9290fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9300fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1); 9310fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9320fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // The re-prefetch is not necessary get the same inner view but we will get same Adapter 9330fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9340fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu innerView = holder.mNestedRecyclerView.get(); 9350fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertSame(innerAdapter, innerView.getAdapter()); 9360fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu // prefetch shouldn't clear the mStructureChanged flag 9370fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.mState.mStructureChanged); 9380fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu assertTrue(innerView.hasPendingAdapterUpdates()); 9390fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu } 9400fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu 9410fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu @Test 9420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchReverseInner() { 9430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true)); 9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // anchor from right side, should see last two positions 9538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19); 9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik @Test 9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik public void nestedPrefetchOffset() { 9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.setAdapter(new OuterAdapter()); 9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik layout(200, 200); 9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing 9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0); 9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner.scrollBy(550, 0); 9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition); 9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition); 9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition); 9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // scroll down 4 rows, up 3 so row 0 is adjacent but uncached 9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, 400); 9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.scrollBy(0, -300); 9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // top row no longer present 9750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0); 9760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9770ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch upward, and validate that we've gotten the top row with correct offsets 9780ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 9790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9800ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView; 9810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6); 9820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik 9830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik // prefetch 4 9840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4); 9850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 9860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8); 9870ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik } 988f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 989f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik @Test 990f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik public void nestedPrefetchNotReset() { 991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik OuterAdapter outerAdapter = new OuterAdapter(); 993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.setAdapter(outerAdapter); 994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik layout(200, 200); 996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // prefetch row 2, items 0 & 1 1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound); 1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 10038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 1004f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1005f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertNotNull(innerRecyclerView); 1006f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1); 1007f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 1008f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1009f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // new row comes on, triggers layout... 1010f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik mRecyclerView.scrollBy(0, 50); 1011f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik 1012f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // ... which shouldn't require new items to be bound, 1013f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik // as prefetch has already done that work 1014f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound); 1015f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik } 10160fab45107f043a4856aad2e1fd785b396898616eChris Craik 10170fab45107f043a4856aad2e1fd785b396898616eChris Craik static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) { 10180fab45107f043a4856aad2e1fd785b396898616eChris Craik ChildHelper childHelper = recyclerView.mChildHelper; 10190fab45107f043a4856aad2e1fd785b396898616eChris Craik 10200fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(childCount, childHelper.getUnfilteredChildCount()); 10210fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) { 10220fab45107f043a4856aad2e1fd785b396898616eChris Craik assertFalse(recyclerView.getChildViewHolder( 10230fab45107f043a4856aad2e1fd785b396898616eChris Craik childHelper.getUnfilteredChildAt(i)).isInvalid()); 10240fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10250fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10260fab45107f043a4856aad2e1fd785b396898616eChris Craik 10270fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 10280fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchCacheNotTouched() { 10290fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10300fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10310fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 10320fab45107f043a4856aad2e1fd785b396898616eChris Craik 10330fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 10340fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.scrollBy(0, 100); 10350fab45107f043a4856aad2e1fd785b396898616eChris Craik 10360fab45107f043a4856aad2e1fd785b396898616eChris Craik // item 0 is cached 10370fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10380fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0); 10398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10400fab45107f043a4856aad2e1fd785b396898616eChris Craik 10410fab45107f043a4856aad2e1fd785b396898616eChris Craik // try and prefetch it 10420fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1); 10430fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 10440fab45107f043a4856aad2e1fd785b396898616eChris Craik 10450fab45107f043a4856aad2e1fd785b396898616eChris Craik // make sure cache's inner items aren't rebound unnecessarily 10460fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound); 10478123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 10488123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik @Test 10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik public void nestedRemoveAnimatingView() { 10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik OuterAdapter outerAdapter = new OuterAdapter(false, 1); 10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.setAdapter(outerAdapter); 10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30)); 10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); 10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // Insert 3 items - only first one in viewport, so only it animates 10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (int i = 0; i < 3; i++) { 10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik outerAdapter.addItem(); 10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik layout(200, 200); // layout again to kick off animation 10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // item 1 is animating, so scroll it out of viewport 10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.scrollBy(0, 200); 10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for... 10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(1, mRecycler.mCachedViews.size()); 10728123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0); 10738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10748123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10758123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik // until animation ends 10768123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik mRecyclerView.getItemAnimator().endAnimations(); 10778123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount()); 10788123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(2, mRecycler.mCachedViews.size()); 10798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1); 10808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0)); 10818123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik 10828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) { 10838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertNotNull(viewHolder.mNestedRecyclerView); 10848123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik } 10850fab45107f043a4856aad2e1fd785b396898616eChris Craik } 10860fab45107f043a4856aad2e1fd785b396898616eChris Craik 10879ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik @Test 10889ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik public void nestedExpandCacheCorrectly() { 10899ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE; 10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik OuterAdapter outerAdapter = new OuterAdapter(); 10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.setAdapter(outerAdapter); 10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik layout(200, 200); 10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // after initial prefetch, view cache max expanded by number of inner items prefetched (2) 11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik RecyclerView innerView = holder.mNestedRecyclerView.get(); 11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik try { 11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // Note: As a hack, we not only must manually dispatch attachToWindow(), but we 11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker 11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // is registered to the wrong thread, since @setup is called on a different thread 11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // from @Test. Assert this, so this test can be fixed when setup == test thread. 11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size()); 11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.isAttachedToWindow()); 11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onAttachedToWindow(); 11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // bring prefetch view into viewport, at which point it shouldn't have cache expanded... 11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik mRecyclerView.scrollBy(0, 100); 11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11229ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik // until a valid horizontal prefetch caches an item, and expands view count by one 11239ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mPrefetchRegistry.setPrefetchVector(1, 0); 11249ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker 11259ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch); 11269ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved); 11279ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax); 11289ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } finally { 11299ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik if (innerView.isAttachedToWindow()) { 11309ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik innerView.onDetachedFromWindow(); 11319ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11329ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11339ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik } 11349ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik 11350fab45107f043a4856aad2e1fd785b396898616eChris Craik /** 11360fab45107f043a4856aad2e1fd785b396898616eChris Craik * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter 11370fab45107f043a4856aad2e1fd785b396898616eChris Craik * to update data for the inner RecyclerViews when containing ViewHolder is bound. 11380fab45107f043a4856aad2e1fd785b396898616eChris Craik */ 11390fab45107f043a4856aad2e1fd785b396898616eChris Craik class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> { 11400fab45107f043a4856aad2e1fd785b396898616eChris Craik private static final int OUTER_ITEM_COUNT = 10; 11410fab45107f043a4856aad2e1fd785b396898616eChris Craik 11420fab45107f043a4856aad2e1fd785b396898616eChris Craik private boolean mReverseInner; 11430fab45107f043a4856aad2e1fd785b396898616eChris Craik 11440fab45107f043a4856aad2e1fd785b396898616eChris Craik class ViewHolder extends RecyclerView.ViewHolder { 11450fab45107f043a4856aad2e1fd785b396898616eChris Craik private final RecyclerView mRecyclerView; 11460fab45107f043a4856aad2e1fd785b396898616eChris Craik private final InnerAdapter mAdapter; 11470fab45107f043a4856aad2e1fd785b396898616eChris Craik ViewHolder(RecyclerView itemView) { 11480fab45107f043a4856aad2e1fd785b396898616eChris Craik super(itemView); 11490fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView = itemView; 11500fab45107f043a4856aad2e1fd785b396898616eChris Craik mAdapter = new InnerAdapter(); 11510fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(mAdapter); 11520fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11530fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11540fab45107f043a4856aad2e1fd785b396898616eChris Craik 11550fab45107f043a4856aad2e1fd785b396898616eChris Craik ArrayList<Parcelable> mSavedStates = new ArrayList<>(); 11560fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); 11570fab45107f043a4856aad2e1fd785b396898616eChris Craik 11580fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter() { 11590fab45107f043a4856aad2e1fd785b396898616eChris Craik this(false); 11600fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11610fab45107f043a4856aad2e1fd785b396898616eChris Craik 11620fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter(boolean reverseInner) { 11630fab45107f043a4856aad2e1fd785b396898616eChris Craik mReverseInner = reverseInner; 11640fab45107f043a4856aad2e1fd785b396898616eChris Craik for (int i = 0; i <= OUTER_ITEM_COUNT; i++) { 11650fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.add(null); 11660fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11670fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11680fab45107f043a4856aad2e1fd785b396898616eChris Craik 11698a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 11700fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11718a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 11720fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11730fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView rv = new RecyclerView(parent.getContext()); 11740fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutManager(new LinearLayoutManager(parent.getContext(), 11750fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager.HORIZONTAL, mReverseInner)); 11760fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setRecycledViewPool(mSharedPool); 11770fab45107f043a4856aad2e1fd785b396898616eChris Craik rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100)); 11780fab45107f043a4856aad2e1fd785b396898616eChris Craik return new ViewHolder(rv); 11790fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11800fab45107f043a4856aad2e1fd785b396898616eChris Craik 11810fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11828a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 11830fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.registerTimePassingMs(5); 11840fab45107f043a4856aad2e1fd785b396898616eChris Craik // if we had actual data to put into our adapter, this is where we'd do it... 11850fab45107f043a4856aad2e1fd785b396898616eChris Craik 11860fab45107f043a4856aad2e1fd785b396898616eChris Craik // ... then notify the adapter that it has new content: 11870fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mAdapter.notifyDataSetChanged(); 11880fab45107f043a4856aad2e1fd785b396898616eChris Craik 11890fab45107f043a4856aad2e1fd785b396898616eChris Craik Parcelable savedState = mSavedStates.get(position); 11900fab45107f043a4856aad2e1fd785b396898616eChris Craik if (savedState != null) { 11910fab45107f043a4856aad2e1fd785b396898616eChris Craik holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState); 11920fab45107f043a4856aad2e1fd785b396898616eChris Craik mSavedStates.set(position, null); 11930fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11940fab45107f043a4856aad2e1fd785b396898616eChris Craik } 11950fab45107f043a4856aad2e1fd785b396898616eChris Craik 11960fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 11978a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onViewRecycled(@NonNull ViewHolder holder) { 1198570ab881350c836743247140a0953f87acc21c8dChris Craik if (holder.getAdapterPosition() >= 0) { 1199570ab881350c836743247140a0953f87acc21c8dChris Craik mSavedStates.set(holder.getAdapterPosition(), 1200570ab881350c836743247140a0953f87acc21c8dChris Craik holder.mRecyclerView.getLayoutManager().onSaveInstanceState()); 1201570ab881350c836743247140a0953f87acc21c8dChris Craik } 12020fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12030fab45107f043a4856aad2e1fd785b396898616eChris Craik 12040fab45107f043a4856aad2e1fd785b396898616eChris Craik @Override 12050fab45107f043a4856aad2e1fd785b396898616eChris Craik public int getItemCount() { 12060fab45107f043a4856aad2e1fd785b396898616eChris Craik return OUTER_ITEM_COUNT; 12070fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12080fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12090fab45107f043a4856aad2e1fd785b396898616eChris Craik 12100fab45107f043a4856aad2e1fd785b396898616eChris Craik @Test 12110fab45107f043a4856aad2e1fd785b396898616eChris Craik public void nestedPrefetchDiscardStaleChildren() { 12120fab45107f043a4856aad2e1fd785b396898616eChris Craik LinearLayoutManager llm = new LinearLayoutManager(getContext()); 1213d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik assertEquals(2, llm.getInitialPrefetchItemCount()); 12140fab45107f043a4856aad2e1fd785b396898616eChris Craik 12150fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setLayoutManager(llm); 12160fab45107f043a4856aad2e1fd785b396898616eChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 12170fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setAdapter(outerAdapter); 12180fab45107f043a4856aad2e1fd785b396898616eChris Craik 12190fab45107f043a4856aad2e1fd785b396898616eChris Craik // zero cache, so item we prefetch can't already be ready 12200fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.setItemViewCacheSize(0); 12210fab45107f043a4856aad2e1fd785b396898616eChris Craik 12220fab45107f043a4856aad2e1fd785b396898616eChris Craik // layout 3 items, then resize to 2... 12230fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 300); 12240fab45107f043a4856aad2e1fd785b396898616eChris Craik layout(200, 200); 12250fab45107f043a4856aad2e1fd785b396898616eChris Craik 12260fab45107f043a4856aad2e1fd785b396898616eChris Craik // so 1 item is evicted into the RecycledViewPool (bypassing cache) 12270fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 12280fab45107f043a4856aad2e1fd785b396898616eChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 12290fab45107f043a4856aad2e1fd785b396898616eChris Craik 12300fab45107f043a4856aad2e1fd785b396898616eChris Craik // This is a simple imitation of other behavior (namely, varied types in the outer adapter) 12310fab45107f043a4856aad2e1fd785b396898616eChris Craik // that results in the same initial state to test: items in the pool with attached children 12320fab45107f043a4856aad2e1fd785b396898616eChris Craik for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) { 12330fab45107f043a4856aad2e1fd785b396898616eChris Craik // verify that children are attached and valid, since the RVs haven't been rebound 12340fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout); 12368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2); 12370fab45107f043a4856aad2e1fd785b396898616eChris Craik } 12380fab45107f043a4856aad2e1fd785b396898616eChris Craik 12390fab45107f043a4856aad2e1fd785b396898616eChris Craik // prefetch the outer item bind, but without enough time to do any inner binds 12400fab45107f043a4856aad2e1fd785b396898616eChris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9); 12410fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 12420fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(deadlineNs); 12430fab45107f043a4856aad2e1fd785b396898616eChris Craik 12440fab45107f043a4856aad2e1fd785b396898616eChris Craik // 2 is prefetched without children 12450fab45107f043a4856aad2e1fd785b396898616eChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2); 12460fab45107f043a4856aad2e1fd785b396898616eChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2); 12470fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder); 12480fab45107f043a4856aad2e1fd785b396898616eChris Craik assertNotNull(holder.mNestedRecyclerView); 12498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount()); 12508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size()); 12510fab45107f043a4856aad2e1fd785b396898616eChris Craik 12520fab45107f043a4856aad2e1fd785b396898616eChris Craik // but if we give it more time to bind items, it'll now acquire its inner items 12530fab45107f043a4856aad2e1fd785b396898616eChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 12548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1); 12550fab45107f043a4856aad2e1fd785b396898616eChris Craik } 1256570ab881350c836743247140a0953f87acc21c8dChris Craik 1257570ab881350c836743247140a0953f87acc21c8dChris Craik 1258570ab881350c836743247140a0953f87acc21c8dChris Craik @Test 1259570ab881350c836743247140a0953f87acc21c8dChris Craik public void nestedPrefetchDiscardStalePrefetch() { 1260570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 1261570ab881350c836743247140a0953f87acc21c8dChris Craik OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter(); 1262570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setAdapter(outerAdapter); 1263570ab881350c836743247140a0953f87acc21c8dChris Craik 1264570ab881350c836743247140a0953f87acc21c8dChris Craik // zero cache, so item we prefetch can't already be ready 1265570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.setItemViewCacheSize(0); 1266570ab881350c836743247140a0953f87acc21c8dChris Craik 1267570ab881350c836743247140a0953f87acc21c8dChris Craik // layout as 2x2, starting on row index 2, with empty cache 1268570ab881350c836743247140a0953f87acc21c8dChris Craik layout(200, 200); 1269570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.scrollBy(0, 200); 1270570ab881350c836743247140a0953f87acc21c8dChris Craik 1271570ab881350c836743247140a0953f87acc21c8dChris Craik // no views cached, or previously used (so we can trust number in mItemsBound) 1272570ab881350c836743247140a0953f87acc21c8dChris Craik mRecycler.mRecyclerPool.clear(); 1273570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0)); 1274570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, mRecycler.mCachedViews.size()); 1275570ab881350c836743247140a0953f87acc21c8dChris Craik 1276570ab881350c836743247140a0953f87acc21c8dChris Craik // prefetch the outer item and its inner children 1277570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1278570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1279570ab881350c836743247140a0953f87acc21c8dChris Craik 1280570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched with 2 inner children, first two binds 1281570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1282570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4); 1283570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder); 1284570ab881350c836743247140a0953f87acc21c8dChris Craik assertNotNull(holder.mNestedRecyclerView); 1285570ab881350c836743247140a0953f87acc21c8dChris Craik RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get(); 1286570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1287570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1288570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1289570ab881350c836743247140a0953f87acc21c8dChris Craik 1290570ab881350c836743247140a0953f87acc21c8dChris Craik // notify data set changed, so any previously prefetched items invalid, and re-prefetch 1291570ab881350c836743247140a0953f87acc21c8dChris Craik innerRecyclerView.getAdapter().notifyDataSetChanged(); 1292570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1); 1293570ab881350c836743247140a0953f87acc21c8dChris Craik mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); 1294570ab881350c836743247140a0953f87acc21c8dChris Craik 1295570ab881350c836743247140a0953f87acc21c8dChris Craik // 4 is prefetched again... 1296570ab881350c836743247140a0953f87acc21c8dChris Craik CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4); 1297570ab881350c836743247140a0953f87acc21c8dChris Craik 1298570ab881350c836743247140a0953f87acc21c8dChris Craik // reusing the same instance with 2 inner children... 1299570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4)); 1300570ab881350c836743247140a0953f87acc21c8dChris Craik assertSame(innerRecyclerView, holder.mNestedRecyclerView.get()); 1301570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount()); 1302570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size()); 1303570ab881350c836743247140a0953f87acc21c8dChris Craik 1304570ab881350c836743247140a0953f87acc21c8dChris Craik // ... but there should be two new binds 1305570ab881350c836743247140a0953f87acc21c8dChris Craik assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound); 1306570ab881350c836743247140a0953f87acc21c8dChris Craik } 1307945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik} 1308