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