LinearLayoutManagerSnappingTest.java revision c587f7dba5a337169e854e235da59f595255d6cc
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 org.junit.Test;
20import org.junit.runner.RunWith;
21import org.junit.runners.Parameterized;
22
23import android.support.annotation.Nullable;
24import android.test.suitebuilder.annotation.MediumTest;
25import android.view.View;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
30import java.util.concurrent.atomic.AtomicBoolean;
31
32import static junit.framework.Assert.assertEquals;
33import static junit.framework.Assert.assertNotSame;
34import static junit.framework.Assert.assertSame;
35import static junit.framework.Assert.assertTrue;
36
37@RunWith(Parameterized.class)
38public class LinearLayoutManagerSnappingTest extends BaseLinearLayoutManagerTest {
39
40    final Config mConfig;
41    final boolean mReverseScroll;
42
43    public LinearLayoutManagerSnappingTest(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        setupByConfig(config, true);
65        setupSnapHelper();
66
67        // Record the current center view.
68        View view = findCenterView(mLayoutManager);
69        assertCenterAligned(view);
70
71        int scrollDistance = (getViewDimension(view) / 2) - 1;
72        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
73        mLayoutManager.expectIdleState(2);
74        smoothScrollBy(scrollDist);
75        mLayoutManager.waitForSnap(10);
76
77        // Views have not changed
78        View viewAfterFling = findCenterView(mLayoutManager);
79        assertSame("The view should have scrolled", view, viewAfterFling);
80        assertCenterAligned(viewAfterFling);
81    }
82
83    @MediumTest
84    @Test
85    public void snapOnScrollNextView() throws Throwable {
86        final Config config = (Config) mConfig.clone();
87        setupByConfig(config, true);
88        setupSnapHelper();
89
90        // Record the current center view.
91        View view = findCenterView(mLayoutManager);
92        assertCenterAligned(view);
93
94        int scrollDistance = (getViewDimension(view) / 2) + 1;
95        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
96        mLayoutManager.expectIdleState(2);
97        smoothScrollBy(scrollDist);
98        mLayoutManager.waitForSnap(10);
99
100        // Views have not changed
101        View viewAfterFling = findCenterView(mLayoutManager);
102        assertNotSame("The view should have scrolled", view, viewAfterFling);
103        assertCenterAligned(viewAfterFling);
104    }
105
106    @MediumTest
107    @Test
108    public void snapOnFlingSameView() throws Throwable {
109        final Config config = (Config) mConfig.clone();
110        setupByConfig(config, true);
111        setupSnapHelper();
112
113        // Record the current center view.
114        View view = findCenterView(mLayoutManager);
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        mLayoutManager.expectIdleState(2);
121        assertTrue(fling(velocityDir, velocityDir));
122        // Wait for two settling scrolls: the initial one and the corrective one.
123        waitForIdleScroll(mRecyclerView);
124        mLayoutManager.waitForSnap(100);
125
126        View viewAfterFling = findCenterView(mLayoutManager);
127
128        assertSame("The view should NOT have scrolled", view, viewAfterFling);
129        assertCenterAligned(viewAfterFling);
130    }
131
132    @MediumTest
133    @Test
134    public void snapOnFlingNextView() throws Throwable {
135        final Config config = (Config) mConfig.clone();
136        setupByConfig(config, true);
137        setupSnapHelper();
138
139        // Record the current center view.
140        View view = findCenterView(mLayoutManager);
141        assertCenterAligned(view);
142
143        // Velocity high enough to scroll beyond the current view.
144        int velocity = (int) (0.2 * mRecyclerView.getMaxFlingVelocity());
145        int velocityDir = mReverseScroll ? -velocity : velocity;
146        mLayoutManager.expectIdleState(1);
147        assertTrue(fling(velocityDir, velocityDir));
148        mLayoutManager.waitForSnap(100);
149        getInstrumentation().waitForIdleSync();
150
151        View viewAfterFling = findCenterView(mLayoutManager);
152
153        assertNotSame("The view should have scrolled", view, viewAfterFling);
154        assertCenterAligned(viewAfterFling);
155    }
156
157    private void setupSnapHelper() throws Throwable {
158        SnapHelper snapHelper = new LinearSnapHelper();
159        mLayoutManager.expectIdleState(1);
160        snapHelper.attachToRecyclerView(mRecyclerView);
161        mLayoutManager.waitForSnap(10);
162
163        mLayoutManager.expectLayouts(1);
164        scrollToPosition(mConfig.mItemCount / 2);
165        mLayoutManager.waitForLayout(2);
166
167        View view = findCenterView(mLayoutManager);
168        int scrollDistance = (getViewDimension(view) / 2) + 10;
169        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
170
171        mLayoutManager.expectIdleState(2);
172        smoothScrollBy(scrollDist);
173        mLayoutManager.waitForSnap(10);
174    }
175
176    @Nullable private View findCenterView(RecyclerView.LayoutManager layoutManager) {
177        if (layoutManager.canScrollHorizontally()) {
178            return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0);
179        } else {
180            return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2);
181        }
182    }
183
184    private int getViewDimension(View view) {
185        OrientationHelper helper;
186        if (mLayoutManager.canScrollHorizontally()) {
187            helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
188        } else {
189            helper = OrientationHelper.createVerticalHelper(mLayoutManager);
190        }
191        return helper.getDecoratedMeasurement(view);
192    }
193
194    private void assertCenterAligned(View view) {
195        if (mLayoutManager.canScrollHorizontally()) {
196            assertEquals(mRecyclerView.getWidth() / 2,
197                    mLayoutManager.getViewBounds(view).centerX());
198        } else {
199            assertEquals(mRecyclerView.getHeight() / 2,
200                    mLayoutManager.getViewBounds(view).centerY());
201        }
202    }
203
204    private boolean fling(final int velocityX, final int velocityY) throws Throwable {
205        final AtomicBoolean didStart = new AtomicBoolean(false);
206        runTestOnUiThread(new Runnable() {
207            @Override
208            public void run() {
209                boolean result = mRecyclerView.fling(velocityX, velocityY);
210                didStart.set(result);
211            }
212        });
213        if (!didStart.get()) {
214            return false;
215        }
216        waitForIdleScroll(mRecyclerView);
217        return true;
218    }
219}
220