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