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