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