GridLayoutManagerSnappingTest.java revision 3911e1c2d38e301d5ffbdf11f808fdc593dd83e9
1/* 2 * Copyright (C) 2016 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 android.support.v7.widget; 18 19import android.support.annotation.Nullable; 20import android.test.suitebuilder.annotation.MediumTest; 21import android.view.View; 22 23import org.junit.Test; 24import org.junit.runner.RunWith; 25import org.junit.runners.Parameterized; 26 27import java.util.ArrayList; 28import java.util.List; 29import java.util.concurrent.atomic.AtomicBoolean; 30 31import static junit.framework.Assert.assertEquals; 32import static junit.framework.Assert.assertNotSame; 33import static junit.framework.Assert.assertSame; 34import static junit.framework.Assert.assertTrue; 35 36@RunWith(Parameterized.class) 37public class GridLayoutManagerSnappingTest extends BaseGridLayoutManagerTest { 38 39 final Config mConfig; 40 final boolean mReverseScroll; 41 42 public GridLayoutManagerSnappingTest(Config config, boolean reverseScroll) { 43 mConfig = config; 44 mReverseScroll = reverseScroll; 45 } 46 47 @Parameterized.Parameters(name = "config:{0}, reverseScroll:{1}") 48 public static List<Object[]> getParams() { 49 List<Object[]> result = new ArrayList<>(); 50 List<Config> configs = createBaseVariations(); 51 for (Config config : configs) { 52 for (boolean reverseScroll : new boolean[] {true, false}) { 53 result.add(new Object[]{config, reverseScroll}); 54 } 55 } 56 return result; 57 } 58 59 @MediumTest 60 @Test 61 public void snapOnScrollSameView() throws Throwable { 62 final Config config = (Config) mConfig.clone(); 63 RecyclerView recyclerView = setupBasic(config); 64 waitForFirstLayout(recyclerView); 65 setupSnapHelper(); 66 67 // Record the current center view. 68 View view = findCenterView(mGlm); 69 assertCenterAligned(view); 70 int scrollDistance = (getViewDimension(view) / 2) - 1; 71 int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance; 72 mGlm.expectIdleState(2); 73 smoothScrollBy(scrollDist); 74 mGlm.waitForSnap(10); 75 76 // Views have not changed 77 View viewAfterFling = findCenterView(mGlm); 78 assertSame("The view should have scrolled", view, viewAfterFling); 79 assertCenterAligned(viewAfterFling); 80 } 81 82 @Test 83 public void snapOnScrollNextItem() throws Throwable { 84 final Config config = (Config) mConfig.clone(); 85 RecyclerView recyclerView = setupBasic(config); 86 waitForFirstLayout(recyclerView); 87 setupSnapHelper(); 88 89 // Record the current center view. 90 View view = findCenterView(mGlm); 91 assertCenterAligned(view); 92 int scrollDistance = getViewDimension(view) + 1; 93 int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance; 94 95 smoothScrollBy(scrollDist); 96 waitForIdleScroll(mRecyclerView); 97 waitForIdleScroll(mRecyclerView); 98 99 View viewAfterScroll = findCenterView(mGlm); 100 101 assertNotSame("The view should have scrolled", view, viewAfterScroll); 102 assertCenterAligned(viewAfterScroll); 103 } 104 105 @MediumTest 106 @Test 107 public void snapOnFlingSameView() throws Throwable { 108 final Config config = (Config) mConfig.clone(); 109 RecyclerView recyclerView = setupBasic(config); 110 waitForFirstLayout(recyclerView); 111 setupSnapHelper(); 112 113 // Record the current center view. 114 View view = findCenterView(mGlm); 115 assertCenterAligned(view); 116 117 // Velocity small enough to not scroll to the next view. 118 int velocity = (int) (1.000001 * mRecyclerView.getMinFlingVelocity()); 119 int velocityDir = mReverseScroll ? -velocity : velocity; 120 mGlm.expectIdleState(2); 121 assertTrue(fling(velocityDir, velocityDir)); 122 // Wait for two settling scrolls: the initial one and the corrective one. 123 waitForIdleScroll(mRecyclerView); 124 mGlm.waitForSnap(100); 125 126 View viewAfterFling = findCenterView(mGlm); 127 128 assertSame("The view should NOT have scrolled", view, viewAfterFling); 129 assertCenterAligned(viewAfterFling); 130 } 131 132 133 @MediumTest 134 @Test 135 public void snapOnFlingNextView() throws Throwable { 136 final Config config = (Config) mConfig.clone(); 137 RecyclerView recyclerView = setupBasic(config); 138 waitForFirstLayout(recyclerView); 139 setupSnapHelper(); 140 141 // Record the current center view. 142 View view = findCenterView(mGlm); 143 assertCenterAligned(view); 144 145 // Velocity high enough to scroll beyond the current view. 146 int velocity = (int) (0.2 * mRecyclerView.getMaxFlingVelocity()); 147 int velocityDir = mReverseScroll ? -velocity : velocity; 148 149 mGlm.expectIdleState(1); 150 assertTrue(fling(velocityDir, velocityDir)); 151 mGlm.waitForSnap(100); 152 getInstrumentation().waitForIdleSync(); 153 154 View viewAfterFling = findCenterView(mGlm); 155 156 assertNotSame("The view should have scrolled", view, viewAfterFling); 157 assertCenterAligned(viewAfterFling); 158 } 159 160 private void setupSnapHelper() throws Throwable { 161 SnapHelper snapHelper = new LinearSnapHelper(); 162 mGlm.expectIdleState(1); 163 snapHelper.attachToRecyclerView(mRecyclerView); 164 mGlm.waitForSnap(10); 165 166 mGlm.expectLayout(1); 167 scrollToPosition(mConfig.mItemCount / 2); 168 mGlm.waitForLayout(2); 169 170 View view = findCenterView(mGlm); 171 int scrollDistance = distFromCenter(view) / 2; 172 if (scrollDistance == 0) { 173 return; 174 } 175 176 int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance; 177 178 mGlm.expectIdleState(2); 179 smoothScrollBy(scrollDist); 180 mGlm.waitForSnap(10); 181 } 182 183 @Nullable View findCenterView(RecyclerView.LayoutManager layoutManager) { 184 if (layoutManager.canScrollHorizontally()) { 185 return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0); 186 } else { 187 return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2); 188 } 189 } 190 191 private int getViewDimension(View view) { 192 OrientationHelper helper; 193 if (mGlm.canScrollHorizontally()) { 194 helper = OrientationHelper.createHorizontalHelper(mGlm); 195 } else { 196 helper = OrientationHelper.createVerticalHelper(mGlm); 197 } 198 return helper.getDecoratedMeasurement(view); 199 } 200 201 private void assertCenterAligned(View view) { 202 if(mGlm.canScrollHorizontally()) { 203 assertEquals("The child should align with the center of the parent", 204 mRecyclerView.getWidth() / 2, 205 mGlm.getDecoratedLeft(view) + 206 mGlm.getDecoratedMeasuredWidth(view) / 2); 207 } else { 208 assertEquals("The child should align with the center of the parent", 209 mRecyclerView.getHeight() / 2, 210 mGlm.getDecoratedTop(view) + 211 mGlm.getDecoratedMeasuredHeight(view) / 2); 212 } 213 } 214 215 private int distFromCenter(View view) { 216 if (mGlm.canScrollHorizontally()) { 217 return Math.abs(mRecyclerView.getWidth() / 2 - mGlm.getViewBounds(view).centerX()); 218 } else { 219 return Math.abs(mRecyclerView.getHeight() / 2 - mGlm.getViewBounds(view).centerY()); 220 } 221 } 222 223 private boolean fling(final int velocityX, final int velocityY) 224 throws Throwable { 225 final AtomicBoolean didStart = new AtomicBoolean(false); 226 runTestOnUiThread(new Runnable() { 227 @Override 228 public void run() { 229 boolean result = mRecyclerView.fling(velocityX, velocityY); 230 didStart.set(result); 231 } 232 }); 233 if (!didStart.get()) { 234 return false; 235 } 236 waitForIdleScroll(mRecyclerView); 237 return true; 238 } 239} 240