GridLayoutManagerSnappingTest.java revision 35232c6eaeb9b99f390cb8ef0ac83bf45fa0b3fa
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.LargeTest;
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@LargeTest
38@RunWith(Parameterized.class)
39public class GridLayoutManagerSnappingTest extends BaseGridLayoutManagerTest {
40
41    final Config mConfig;
42    final boolean mReverseScroll;
43
44    public GridLayoutManagerSnappingTest(Config config, boolean reverseScroll) {
45        mConfig = config;
46        mReverseScroll = reverseScroll;
47    }
48
49    @Parameterized.Parameters(name = "config:{0},reverseScroll:{1}")
50    public static List<Object[]> getParams() {
51        List<Object[]> result = new ArrayList<>();
52        List<Config> configs = createBaseVariations();
53        for (Config config : configs) {
54            for (boolean reverseScroll : new boolean[] {true, false}) {
55                result.add(new Object[]{config, reverseScroll});
56            }
57        }
58        return result;
59    }
60
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    @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    @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    @Test
133    public void snapOnFlingNextView() throws Throwable {
134        final Config config = (Config) mConfig.clone();
135        RecyclerView recyclerView = setupBasic(config);
136        waitForFirstLayout(recyclerView);
137        setupSnapHelper();
138
139        // Record the current center view.
140        View view = findCenterView(mGlm);
141        assertCenterAligned(view);
142
143        // Velocity high enough to scroll beyond the current view.
144        int velocity = (int) (0.25 * mRecyclerView.getMaxFlingVelocity());
145        int velocityDir = mReverseScroll ? -velocity : velocity;
146
147        mGlm.expectIdleState(1);
148        assertTrue(fling(velocityDir, velocityDir));
149        mGlm.waitForSnap(100);
150        getInstrumentation().waitForIdleSync();
151
152        View viewAfterFling = findCenterView(mGlm);
153
154        assertNotSame("The view should have scrolled!",
155                ((TextView) view).getText(),((TextView) viewAfterFling).getText());
156        assertCenterAligned(viewAfterFling);
157    }
158
159    private void setupSnapHelper() throws Throwable {
160        SnapHelper snapHelper = new LinearSnapHelper();
161        mGlm.expectIdleState(1);
162        snapHelper.attachToRecyclerView(mRecyclerView);
163        mGlm.waitForSnap(10);
164
165        mGlm.expectLayout(1);
166        scrollToPosition(mConfig.mItemCount / 2);
167        mGlm.waitForLayout(2);
168
169        View view = findCenterView(mGlm);
170        int scrollDistance = distFromCenter(view) / 2;
171        if (scrollDistance == 0) {
172            return;
173        }
174
175        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
176
177        mGlm.expectIdleState(2);
178        smoothScrollBy(scrollDist);
179        mGlm.waitForSnap(10);
180    }
181
182    @Nullable View findCenterView(RecyclerView.LayoutManager layoutManager) {
183        if (layoutManager.canScrollHorizontally()) {
184            return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0);
185        } else {
186            return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2);
187        }
188    }
189
190    private int getViewDimension(View view) {
191        OrientationHelper helper;
192        if (mGlm.canScrollHorizontally()) {
193            helper = OrientationHelper.createHorizontalHelper(mGlm);
194        } else {
195            helper = OrientationHelper.createVerticalHelper(mGlm);
196        }
197        return helper.getDecoratedMeasurement(view);
198    }
199
200    private void assertCenterAligned(View view) {
201        if(mGlm.canScrollHorizontally()) {
202            assertEquals("The child should align with the center of the parent",
203                    mRecyclerView.getWidth() / 2,
204                    mGlm.getDecoratedLeft(view) +
205                            mGlm.getDecoratedMeasuredWidth(view) / 2, 1);
206        } else {
207            assertEquals("The child should align with the center of the parent",
208                    mRecyclerView.getHeight() / 2,
209                    mGlm.getDecoratedTop(view) +
210                            mGlm.getDecoratedMeasuredHeight(view) / 2, 1);
211        }
212    }
213
214    private int distFromCenter(View view) {
215        if (mGlm.canScrollHorizontally()) {
216            return Math.abs(mRecyclerView.getWidth() / 2 - mGlm.getViewBounds(view).centerX());
217        } else {
218            return Math.abs(mRecyclerView.getHeight() / 2 - mGlm.getViewBounds(view).centerY());
219        }
220    }
221
222    private boolean fling(final int velocityX, final int velocityY)
223            throws Throwable {
224        final AtomicBoolean didStart = new AtomicBoolean(false);
225        mActivityRule.runOnUiThread(new Runnable() {
226            @Override
227            public void run() {
228                boolean result = mRecyclerView.fling(velocityX, velocityY);
229                didStart.set(result);
230            }
231        });
232        if (!didStart.get()) {
233            return false;
234        }
235        waitForIdleScroll(mRecyclerView);
236        return true;
237    }
238}
239