RecyclerViewCacheTest.java revision a93a0c08b4543401d8e2df9bd4c88f26d3b72c11
136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/* 236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Copyright (C) 2016 The Android Open Source Project 336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * you may not use this file except in compliance with the License. 636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * You may obtain a copy of the License at 736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * http://www.apache.org/licenses/LICENSE-2.0 936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * 1036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Unless required by applicable law or agreed to in writing, software 1136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * distributed under the License is distributed on an "AS IS" BASIS, 1236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * See the License for the specific language governing permissions and 1436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * limitations under the License. 1536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik */ 1636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 1736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpackage android.support.v7.widget; 1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue; 20ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.fail; 21ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any; 22ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt; 23ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock; 24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never; 25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times; 26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify; 27ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when; 28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas 2936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context; 30e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build; 3136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry; 32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress; 3336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4; 3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.test.suitebuilder.annotation.SmallTest; 3536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View; 3636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup; 3736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before; 3936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test; 4036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith; 4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock; 4236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer; 4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List; 45a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit; 4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 4736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest 48e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class) 5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest { 51a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView mRecyclerView; 5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Recycler mRecycler; 5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 54a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private class TimeMockingRecyclerView extends RecyclerView { 55a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik private long mMockNanoTime = 0; 56a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 57a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik TimeMockingRecyclerView(Context context) { 58a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik super(context); 59a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 60a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 61a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void registerTimePassingMs(long ms) { 62a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms); 63a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 64a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 65a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 66a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik long getNanoTime() { 67a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return mMockNanoTime; 68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik }; 70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 7136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Before 7236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void setUp() throws Exception { 73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView = new TimeMockingRecyclerView(getContext()); 7436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler = mRecyclerView.mRecycler; 7536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 7636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 7736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private Context getContext() { 7836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return InstrumentationRegistry.getContext(); 7936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 8036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 8136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 8236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchReusesCacheItems() { 8336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() { 8436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 8536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.LayoutParams generateDefaultLayoutParams() { 8636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 8736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik ViewGroup.LayoutParams.WRAP_CONTENT); 8836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 8936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 9036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 9136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik int getItemPrefetchCount() { 9236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return 3; 9336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 9636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) { 9736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik outIndices[0] = 0; 9836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik outIndices[1] = 1; 9936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik outIndices[2] = 2; 10036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return 3; 10136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 10236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 10336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 10436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 10536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 10636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }; 10736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(prefetchingLayoutManager); 10836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 11136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 11336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 11436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 11536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 11636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 11736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 11836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(10); 11936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240); 12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.layout(0, 0, 320, 320); 12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt()); 12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, never()).onBindViewHolder( 12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Prefetch multiple times... 13036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < 4; i++) { 131a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView); 13236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 13336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // ...but should only see the same three items fetched/bound once each 13436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt()); 13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verify(mockAdapter, times(3)).onBindViewHolder( 13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik any(RecyclerView.ViewHolder.class), anyInt(), any(List.class)); 13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2); 14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private void verifyCacheContainsPosition(int position) { 14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < mRecycler.mCachedViews.size(); i++) { 14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik if (mRecycler.mCachedViews.get(i).mPosition == position) return; 14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik fail("Cache does not contain position " + position); 14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik private void verifyCacheContainsPositions(Integer... positions) { 15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i = 0; i < positions.length; i++) { 15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPosition(positions[i]); 15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedWithInserts() { 15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(new View(getContext())) {}; 16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320); 17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.layout(0, 0, 320, 320); 17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 17607b2e072ee7e8f424eb95abc77695dc2c5a786bbChris Craik mRecyclerView.mPrefetchArray = new int[] { 0, 1, 2 }; 177a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik GapWorker.layoutPrefetchImpl(Long.MAX_VALUE, mRecycler, mRecyclerView.mPrefetchArray, 3); 17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2); 17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // further views recycled, as though from scrolling, shouldn't evict prefetched views: 18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(10)); 18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2, 10); 18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(20)); 18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2, 10, 20); 18636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 18736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(30)); 18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2, 20, 30); 18936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(40)); 19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(0, 1, 2, 30, 40); 19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // After clearing the cache, the prefetch priorities should be cleared as well: 19536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.mRecycler.recycleAndClearCachedViews(); 19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik for (int i : new int[] {0, 1, 2, 50, 60, 70, 80, 90}) { 19736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecycler.recycleView(mRecycler.getViewForPosition(i)); 19836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 19936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // cache only contains most recent positions, no priority for previous prefetches: 20136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(50, 60, 70, 80, 90); 20236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 20436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Test 20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public void prefetchItemsNotEvictedOnScroll() { 20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 20936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // 100x100 pixel views 21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class); 21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt())) 21236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik .thenAnswer(new Answer<RecyclerView.ViewHolder>() { 21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik @Override 21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik public RecyclerView.ViewHolder answer(InvocationOnMock invocation) 21536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik throws Throwable { 21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik View view = new View(getContext()); 21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumWidth(100); 21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik view.setMinimumHeight(100); 21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik return new RecyclerView.ViewHolder(view) {}; 22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik }); 22236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik when(mockAdapter.getItemCount()).thenReturn(100); 22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setAdapter(mockAdapter); 22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // NOTE: requested cache size must be smaller than span count so two rows cannot fit 22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.setItemViewCacheSize(2); 22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 200); 22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.layout(0, 0, 300, 150); 23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 75); 23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.isEmpty()); 23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // rows 0, 1, and 2 are all attached and visible. Prefetch row 3: 234a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.mPrefetchDx = 0; 235a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.mPrefetchDy = 1; 236a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView); 23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is cached: 23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(9, 10, 11); 24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // Scroll so 1 falls off (though 3 is still not on screen) 24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik mRecyclerView.scrollBy(0, 50); 24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik 24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik // row 3 is still cached, with a couple other recycled views: 24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik verifyCacheContainsPositions(9, 10, 11); 24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik assertTrue(mRecycler.mCachedViews.size() == 5); 24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik } 249a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 250a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Test 251a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void prefetchItemsRespectDeadline() { 252a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); 253a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 254a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // 100x100 pixel views 255a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik RecyclerView.Adapter adapter = new RecyclerView.Adapter() { 256a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 257a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 258a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 259a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik View view = new View(getContext()); 260a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumWidth(100); 261a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik view.setMinimumHeight(100); 262a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(view) {}; 263a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return holder; 264a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 265a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 266a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 267a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 268a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.registerTimePassingMs(5); 269a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 270a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 271a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik @Override 272a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik public int getItemCount() { 273a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik return 100; 274a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 275a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik }; 276a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.setAdapter(adapter); 277a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 278a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 300); 279a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.layout(0, 0, 300, 300); 280a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 281a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 0); 282a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 283a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 284a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe 285a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19); 286a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 287a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Timed prefetch 288a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.mPrefetchDx = 0; 289a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik mRecyclerView.mPrefetchDy = 1; 290a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik GapWorker.layoutPrefetch(deadlineNs, mRecyclerView); 291a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 292a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // will have enough time to inflate/bind one view, and inflate another 293a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 1); 294a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1); 295a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik verifyCacheContainsPositions(11); // Note: order/view here is an implementation detail 296a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 297a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 298a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Unbounded prefetch this time 299a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView); 300a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik 301a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik // Should finish all work 302a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecycler.mCachedViews.size() == 3); 303a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0); 304a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik verifyCacheContainsPositions(9, 10, 11); 305a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik } 30636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik}