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