GridLayoutManagerSnappingTest.java revision e9f9cd8d0e9008340985d17a2541ab24b3adb391
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.test.suitebuilder.annotation.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 @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 = (getViewDimension(view) / 2) + 10; 172 int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance; 173 174 mGlm.expectIdleState(2); 175 smoothScrollBy(scrollDist); 176 mGlm.waitForSnap(10); 177 } 178 179 @Nullable View findCenterView(RecyclerView.LayoutManager layoutManager) { 180 if (layoutManager.canScrollHorizontally()) { 181 return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0); 182 } else { 183 return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2); 184 } 185 } 186 187 private int getViewDimension(View view) { 188 OrientationHelper helper; 189 if (mGlm.canScrollHorizontally()) { 190 helper = OrientationHelper.createHorizontalHelper(mGlm); 191 } else { 192 helper = OrientationHelper.createVerticalHelper(mGlm); 193 } 194 return helper.getDecoratedMeasurement(view); 195 } 196 197 private void assertCenterAligned(View view) { 198 if(mGlm.canScrollHorizontally()) { 199 assertEquals("The child should align with the center of the parent", 200 mRecyclerView.getWidth() / 2, 201 mGlm.getDecoratedLeft(view) + 202 mGlm.getDecoratedMeasuredWidth(view) / 2); 203 } else { 204 assertEquals("The child should align with the center of the parent", 205 mRecyclerView.getHeight() / 2, 206 mGlm.getDecoratedTop(view) + 207 mGlm.getDecoratedMeasuredHeight(view) / 2); 208 } 209 } 210 211 private boolean fling(final int velocityX, final int velocityY) 212 throws Throwable { 213 final AtomicBoolean didStart = new AtomicBoolean(false); 214 runTestOnUiThread(new Runnable() { 215 @Override 216 public void run() { 217 boolean result = mRecyclerView.fling(velocityX, velocityY); 218 didStart.set(result); 219 } 220 }); 221 if (!didStart.get()) { 222 return false; 223 } 224 waitForIdleScroll(mRecyclerView); 225 return true; 226 } 227} 228