GridLayoutManagerSnappingTest.java revision 3911e1c2d38e301d5ffbdf11f808fdc593dd83e9
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 android.support.annotation.Nullable;
20import android.test.suitebuilder.annotation.MediumTest;
21import android.view.View;
22
23import org.junit.Test;
24import org.junit.runner.RunWith;
25import org.junit.runners.Parameterized;
26
27import java.util.ArrayList;
28import java.util.List;
29import java.util.concurrent.atomic.AtomicBoolean;
30
31import static junit.framework.Assert.assertEquals;
32import static junit.framework.Assert.assertNotSame;
33import static junit.framework.Assert.assertSame;
34import static junit.framework.Assert.assertTrue;
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 = distFromCenter(view) / 2;
172        if (scrollDistance == 0) {
173            return;
174        }
175
176        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
177
178        mGlm.expectIdleState(2);
179        smoothScrollBy(scrollDist);
180        mGlm.waitForSnap(10);
181    }
182
183    @Nullable View findCenterView(RecyclerView.LayoutManager layoutManager) {
184        if (layoutManager.canScrollHorizontally()) {
185            return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0);
186        } else {
187            return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2);
188        }
189    }
190
191    private int getViewDimension(View view) {
192        OrientationHelper helper;
193        if (mGlm.canScrollHorizontally()) {
194            helper = OrientationHelper.createHorizontalHelper(mGlm);
195        } else {
196            helper = OrientationHelper.createVerticalHelper(mGlm);
197        }
198        return helper.getDecoratedMeasurement(view);
199    }
200
201    private void assertCenterAligned(View view) {
202        if(mGlm.canScrollHorizontally()) {
203            assertEquals("The child should align with the center of the parent",
204                    mRecyclerView.getWidth() / 2,
205                    mGlm.getDecoratedLeft(view) +
206                            mGlm.getDecoratedMeasuredWidth(view) / 2);
207        } else {
208            assertEquals("The child should align with the center of the parent",
209                    mRecyclerView.getHeight() / 2,
210                    mGlm.getDecoratedTop(view) +
211                            mGlm.getDecoratedMeasuredHeight(view) / 2);
212        }
213    }
214
215    private int distFromCenter(View view) {
216        if (mGlm.canScrollHorizontally()) {
217            return Math.abs(mRecyclerView.getWidth() / 2 - mGlm.getViewBounds(view).centerX());
218        } else {
219            return Math.abs(mRecyclerView.getHeight() / 2 - mGlm.getViewBounds(view).centerY());
220        }
221    }
222
223    private boolean fling(final int velocityX, final int velocityY)
224            throws Throwable {
225        final AtomicBoolean didStart = new AtomicBoolean(false);
226        runTestOnUiThread(new Runnable() {
227            @Override
228            public void run() {
229                boolean result = mRecyclerView.fling(velocityX, velocityY);
230                didStart.set(result);
231            }
232        });
233        if (!didStart.get()) {
234            return false;
235        }
236        waitForIdleScroll(mRecyclerView);
237        return true;
238    }
239}
240