LinearLayoutManagerBaseConfigSetTest.java revision 4510b5c24adad2b94df9b84c6b73f5534ffe9b57
1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/*
2999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Copyright (C) 2015 The Android Open Source Project
3999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
4999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * you may not use this file except in compliance with the License.
6999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * You may obtain a copy of the License at
7999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
8999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
10999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Unless required by applicable law or agreed to in writing, software
11999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * See the License for the specific language governing permissions and
14999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * limitations under the License.
15999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */
16999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
17999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpackage android.support.v7.widget;
18999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
19999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
20999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport org.junit.Test;
21999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport org.junit.runner.RunWith;
22999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport org.junit.runners.Parameterized;
23999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
24999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect;
254510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport android.support.v4.view.ViewCompat;
26999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.test.suitebuilder.annotation.MediumTest;
27999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log;
28999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.view.View;
294143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport android.view.ViewGroup;
304510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport android.view.ViewParent;
31999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
324143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport java.util.ArrayList;
33999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List;
34999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map;
35999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
36999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END;
37999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START;
38999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
394143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
404510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar
414510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.hasItem;
424510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.is;
434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.not;
444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.hamcrest.CoreMatchers.sameInstance;
454143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static org.junit.Assert.assertEquals;
464143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static org.junit.Assert.assertNotNull;
474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyarimport static org.junit.Assert.assertThat;
484510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar
494143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
504143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static android.view.ViewGroup.LayoutParams.FILL_PARENT;
51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/**
53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Tests that rely on the basic configuration and does not do any additions / removals
54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */
55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar@RunWith(Parameterized.class)
564510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar@MediumTest
57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManagerTest {
58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
59999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    private final Config mConfig;
60999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public LinearLayoutManagerBaseConfigSetTest(Config config) {
62999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mConfig = config;
63999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    @Parameterized.Parameters(name = "{0}")
67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public static List<Config> configs() throws CloneNotSupportedException {
684143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        List<Config> result = new ArrayList<>();
694143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        for (Config config : createBaseVariations()) {
704143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            result.add(config);
714143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
724143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        return result;
73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    @Test
76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void scrollToPositionWithOffsetTest() throws Throwable {
77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config config = ((Config) mConfig.clone()).itemCount(300);
780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        setupByConfig(config, true);
79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OrientationHelper orientationHelper = OrientationHelper
80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                .createOrientationHelper(mLayoutManager, config.mOrientation);
81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Rect layoutBounds = getDecoratedRecyclerViewBounds();
82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // try scrolling towards head, should not affect anything
83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (config.mStackFromEnd) {
85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mLayoutManager.mOrientationHelper.getEnd() - 500);
87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        } else {
88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            scrollToPositionWithOffset(0, 20);
89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                before, mLayoutManager.collectChildCoordinates());
92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // try offsetting some visible children
93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int testCount = 10;
94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        while (testCount-- > 0) {
95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            // get middle child
96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
97999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int position = mRecyclerView.getChildLayoutPosition(child);
98999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int startOffset = config.mReverseLayout ?
99999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            .getDecoratedEnd(child)
101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            .getStartAfterPadding();
103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    : startOffset / 2;
105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mLayoutManager.expectLayouts(1);
106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            scrollToPositionWithOffset(position, scrollOffset);
107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mLayoutManager.waitForLayout(2);
108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int finalOffset = config.mReverseLayout ?
109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            .getDecoratedEnd(child)
111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            .getStartAfterPadding();
113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertEquals(config + " scroll with offset on a visible child should work fine " +
114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            + "child " + position,
116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    scrollOffset, finalOffset);
117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // try scrolling to invisible children
120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        testCount = 10;
121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // we test above and below, one by one
122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int offsetMultiplier = -1;
123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        while (testCount-- > 0) {
124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final TargetTuple target = findInvisibleTarget(config);
125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final String logPrefix = config + " " + target;
126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mLayoutManager.expectLayouts(1);
127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int offset = offsetMultiplier
128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            scrollToPositionWithOffset(target.mPosition, offset);
130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mLayoutManager.waitForLayout(2);
131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final View child = mLayoutManager.findViewByPosition(target.mPosition);
132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    + " should layout it", child);
134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Rect bounds = mLayoutManager.getViewBounds(child);
135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        + layoutBounds + " with offset " + offset);
138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (config.mReverseLayout) {
141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + "layout, its end should align with recycler view's end - offset",
143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        orientationHelper.getEndAfterPadding() - offset,
144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        orientationHelper.getDecoratedEnd(child)
145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(
148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        logPrefix + " when scrolling with offset to an invisible child in normal"
149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + " layout its start should align with recycler view's start + "
150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + "offset",
151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        orientationHelper.getStartAfterPadding() + offset,
152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        orientationHelper.getDecoratedStart(child)
153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            offsetMultiplier *= -1;
156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    @Test
160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void getFirstLastChildrenTest() throws Throwable {
161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final Config config = ((Config) mConfig.clone()).itemCount(300);
162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setupByConfig(config, true);
163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Runnable viewInBoundsTest = new Runnable() {
164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final String boundsLog = mLayoutManager.getBoundsLog();
168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(config + ":\nfirst visible child should match traversal result\n"
169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + boundsLog, visibleChildren.firstVisiblePosition,
170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.findFirstVisibleItemPosition()
171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(
173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        config + ":\nfirst fully visible child should match traversal result\n"
174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(config + ":\nlast visible child should match traversal result\n"
179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + boundsLog, visibleChildren.lastVisiblePosition,
180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.findLastVisibleItemPosition()
181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertEquals(
183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        config + ":\nlast fully visible child should match traversal result\n"
184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.findLastCompletelyVisibleItemPosition()
186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                );
187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        };
189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // case
192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mRecyclerView.smoothScrollToPosition(scrollPosition);
197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        while (mLayoutManager.isSmoothScrolling() ||
200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            runTestOnUiThread(viewInBoundsTest);
202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Thread.sleep(400);
203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // delete all items
205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(2);
206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // test empty case
209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // set a new adapter with huge items to test full bounds check
211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(1);
212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final TestAdapter newAdapter = new TestAdapter(100) {
214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void onBindViewHolder(TestViewHolder holder,
216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    int position) {
217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                super.onBindViewHolder(holder, position);
218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (config.mOrientation == HORIZONTAL) {
219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    holder.itemView.setMinimumWidth(totalSpace + 5);
220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } else {
221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    holder.itemView.setMinimumHeight(totalSpace + 5);
222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        };
225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mRecyclerView.setAdapter(newAdapter);
229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
2354510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    @Test
2364510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
2374510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final Config config = ((Config) mConfig.clone()).itemCount(1000);
2384510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        setupByConfig(config, true);
2394510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        mLayoutManager.expectLayouts(1);
2404510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollToPosition(500);
2414510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        mLayoutManager.waitForLayout(2);
2424510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
2434510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        OrientationHelper helper = mLayoutManager.mOrientationHelper;
2444510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        int gap = helper.getDecoratedStart(vh.itemView);
2454510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(gap);
2464510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        gap = helper.getDecoratedStart(vh.itemView);
2474510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat("test sanity", gap, is(0));
2484510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar
2494510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final int size = helper.getDecoratedMeasurement(vh.itemView);
2504510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
2514510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        runTestOnUiThread(new Runnable() {
2524510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            @Override
2534510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            public void run() {
2544510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                if (mConfig.mOrientation == HORIZONTAL) {
2554510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                    ViewCompat.setTranslationX(vh.itemView, size * 2);
2564510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                } else {
2574510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                    ViewCompat.setTranslationY(vh.itemView, size * 2);
2584510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                }
2594510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            }
2604510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        });
2614510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(size * 2);
2624510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
2634510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
2644510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(vh.getAdapterPosition(), is(500));
2654510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(size * 2);
2664510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
2674510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    }
2684510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar
2694510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    @Test
2704510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable {
2714510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final Config config = ((Config) mConfig.clone()).itemCount(1000);
2724510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        setupByConfig(config, true);
2734510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        mLayoutManager.expectLayouts(1);
2744510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollToPosition(500);
2754510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        mLayoutManager.waitForLayout(2);
2764510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
2774510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        OrientationHelper helper = mLayoutManager.mOrientationHelper;
2784510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
2794510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(-gap);
2804510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
2814510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat("test sanity", gap, is(0));
2824510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar
2834510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        final int size = helper.getDecoratedMeasurement(vh.itemView);
2844510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
2854510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        runTestOnUiThread(new Runnable() {
2864510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            @Override
2874510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            public void run() {
2884510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                if (mConfig.mOrientation == HORIZONTAL) {
2894510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                    ViewCompat.setTranslationX(vh.itemView, -size * 2);
2904510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                } else {
2914510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                    ViewCompat.setTranslationY(vh.itemView, -size * 2);
2924510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar                }
2934510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar            }
2944510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        });
2954510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(-size * 2);
2964510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
2974510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
2984510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(vh.getAdapterPosition(), is(500));
2994510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        scrollBy(-size * 2);
3004510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
3014510b5c24adad2b94df9b84c6b73f5534ffe9b57Yigit Boyar    }
302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    private TargetTuple findInvisibleTarget(Config config) {
304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            View child = mLayoutManager.getChildAt(i);
307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            int position = mRecyclerView.getChildLayoutPosition(child);
308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (position < minPosition) {
309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                minPosition = position;
310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (position > maxPosition) {
312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                maxPosition = position;
313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int tailTarget = maxPosition +
316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int headTarget = minPosition / 2;
318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int target;
319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // where will the child come from ?
320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        final int itemLayoutDirection;
321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            target = tailTarget;
323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        } else {
325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            target = headTarget;
326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (DEBUG) {
329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Log.d(TAG,
330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
332999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return new TargetTuple(target, itemLayoutDirection);
333999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
334999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar}
335