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