1/* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.recyclerview.widget; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertSame; 21import static org.junit.Assert.assertTrue; 22 23import android.support.test.filters.MediumTest; 24import android.util.Log; 25import android.view.View; 26 27import org.junit.Test; 28import org.junit.runner.RunWith; 29import org.junit.runners.Parameterized; 30 31import java.util.Arrays; 32import java.util.BitSet; 33import java.util.List; 34 35@MediumTest 36@RunWith(Parameterized.class) 37public class GridLayoutManagerBaseConfigSetTest extends BaseGridLayoutManagerTest { 38 @Parameterized.Parameters(name = "{0}") 39 public static List<Config> params() { 40 return createBaseVariations(); 41 } 42 43 private final Config mConfig; 44 45 public GridLayoutManagerBaseConfigSetTest(Config config) { 46 mConfig = config; 47 } 48 49 @Test 50 public void scrollBackAndPreservePositions() throws Throwable { 51 Config config = (Config) mConfig.clone(); 52 config.mItemCount = 150; 53 scrollBackAndPreservePositionsTest(config); 54 } 55 56 public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable { 57 final RecyclerView rv = setupBasic(config); 58 for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) { 59 mAdapter.setFullSpan(i); 60 } 61 waitForFirstLayout(rv); 62 final int[] globalPositions = new int[mAdapter.getItemCount()]; 63 Arrays.fill(globalPositions, Integer.MIN_VALUE); 64 final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20) 65 * (config.mReverseLayout ? -1 : 1); 66 final String logPrefix = config.toString(); 67 final int[] globalPos = new int[1]; 68 mActivityRule.runOnUiThread(new Runnable() { 69 @Override 70 public void run() { 71 assertSame("test sanity", mRecyclerView, rv); 72 int globalScrollPosition = 0; 73 int visited = 0; 74 while (visited < mAdapter.getItemCount()) { 75 for (int i = 0; i < mRecyclerView.getChildCount(); i++) { 76 View child = mRecyclerView.getChildAt(i); 77 final int pos = mRecyclerView.getChildLayoutPosition(child); 78 if (globalPositions[pos] != Integer.MIN_VALUE) { 79 continue; 80 } 81 visited++; 82 GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) 83 child.getLayoutParams(); 84 if (config.mReverseLayout) { 85 globalPositions[pos] = globalScrollPosition + 86 mGlm.mOrientationHelper.getDecoratedEnd(child); 87 } else { 88 globalPositions[pos] = globalScrollPosition + 89 mGlm.mOrientationHelper.getDecoratedStart(child); 90 } 91 assertEquals(logPrefix + " span index should match", 92 mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()), 93 lp.getSpanIndex()); 94 } 95 int scrolled = mGlm.scrollBy(scrollStep, 96 mRecyclerView.mRecycler, mRecyclerView.mState); 97 globalScrollPosition += scrolled; 98 if (scrolled == 0) { 99 assertEquals( 100 logPrefix + " If scroll is complete, all views should be visited", 101 visited, mAdapter.getItemCount()); 102 } 103 } 104 if (DEBUG) { 105 Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions)); 106 } 107 globalPos[0] = globalScrollPosition; 108 } 109 }); 110 checkForMainThreadException(); 111 // test sanity, ensure scroll happened 112 mActivityRule.runOnUiThread(new Runnable() { 113 @Override 114 public void run() { 115 final int childCount = mGlm.getChildCount(); 116 final BitSet expectedPositions = new BitSet(); 117 for (int i = 0; i < childCount; i ++) { 118 expectedPositions.set(mAdapter.getItemCount() - i - 1); 119 } 120 for (int i = 0; i <childCount; i ++) { 121 final View view = mGlm.getChildAt(i); 122 int position = mGlm.getPosition(view); 123 assertTrue("child position should be in last page", expectedPositions.get(position)); 124 } 125 } 126 }); 127 getInstrumentation().waitForIdleSync(); 128 mActivityRule.runOnUiThread(new Runnable() { 129 @Override 130 public void run() { 131 int globalScrollPosition = globalPos[0]; 132 // now scroll back and make sure global positions match 133 BitSet shouldTest = new BitSet(mAdapter.getItemCount()); 134 shouldTest.set(0, mAdapter.getItemCount() - 1, true); 135 String assertPrefix = config 136 + " global pos must match when scrolling in reverse for position "; 137 int scrollAmount = Integer.MAX_VALUE; 138 while (!shouldTest.isEmpty() && scrollAmount != 0) { 139 for (int i = 0; i < mRecyclerView.getChildCount(); i++) { 140 View child = mRecyclerView.getChildAt(i); 141 int pos = mRecyclerView.getChildLayoutPosition(child); 142 if (!shouldTest.get(pos)) { 143 continue; 144 } 145 GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) 146 child.getLayoutParams(); 147 shouldTest.clear(pos); 148 int globalPos; 149 if (config.mReverseLayout) { 150 globalPos = globalScrollPosition + 151 mGlm.mOrientationHelper.getDecoratedEnd(child); 152 } else { 153 globalPos = globalScrollPosition + 154 mGlm.mOrientationHelper.getDecoratedStart(child); 155 } 156 assertEquals(assertPrefix + pos, 157 globalPositions[pos], globalPos); 158 assertEquals("span index should match", 159 mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()), 160 lp.getSpanIndex()); 161 } 162 scrollAmount = mGlm.scrollBy(-scrollStep, 163 mRecyclerView.mRecycler, mRecyclerView.mState); 164 globalScrollPosition += scrollAmount; 165 } 166 assertTrue("all views should be seen", shouldTest.isEmpty()); 167 } 168 }); 169 checkForMainThreadException(); 170 } 171} 172