GridLayoutManagerSnappingTest.java revision 42e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790
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 = distFromCenter(view) / 2;
173        if (scrollDistance == 0) {
174            return;
175        }
176
177        int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
178
179        mGlm.expectIdleState(2);
180        smoothScrollBy(scrollDist);
181        mGlm.waitForSnap(10);
182    }
183
184    @Nullable View findCenterView(RecyclerView.LayoutManager layoutManager) {
185        if (layoutManager.canScrollHorizontally()) {
186            return mRecyclerView.findChildViewUnder(mRecyclerView.getWidth() / 2, 0);
187        } else {
188            return mRecyclerView.findChildViewUnder(0, mRecyclerView.getHeight() / 2);
189        }
190    }
191
192    private int getViewDimension(View view) {
193        OrientationHelper helper;
194        if (mGlm.canScrollHorizontally()) {
195            helper = OrientationHelper.createHorizontalHelper(mGlm);
196        } else {
197            helper = OrientationHelper.createVerticalHelper(mGlm);
198        }
199        return helper.getDecoratedMeasurement(view);
200    }
201
202    private void assertCenterAligned(View view) {
203        if(mGlm.canScrollHorizontally()) {
204            assertEquals("The child should align with the center of the parent",
205                    mRecyclerView.getWidth() / 2,
206                    mGlm.getDecoratedLeft(view) +
207                            mGlm.getDecoratedMeasuredWidth(view) / 2);
208        } else {
209            assertEquals("The child should align with the center of the parent",
210                    mRecyclerView.getHeight() / 2,
211                    mGlm.getDecoratedTop(view) +
212                            mGlm.getDecoratedMeasuredHeight(view) / 2);
213        }
214    }
215
216    private int distFromCenter(View view) {
217        if (mGlm.canScrollHorizontally()) {
218            return Math.abs(mRecyclerView.getWidth() / 2 - mGlm.getViewBounds(view).centerX());
219        } else {
220            return Math.abs(mRecyclerView.getHeight() / 2 - mGlm.getViewBounds(view).centerY());
221        }
222    }
223
224    private boolean fling(final int velocityX, final int velocityY)
225            throws Throwable {
226        final AtomicBoolean didStart = new AtomicBoolean(false);
227        mActivityRule.runOnUiThread(new Runnable() {
228            @Override
229            public void run() {
230                boolean result = mRecyclerView.fling(velocityX, velocityY);
231                didStart.set(result);
232            }
233        });
234        if (!didStart.get()) {
235            return false;
236        }
237        waitForIdleScroll(mRecyclerView);
238        return true;
239    }
240}
241