1 /*
2  * Copyright 2018 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 
17 package androidx.recyclerview.widget;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 
22 import android.graphics.Rect;
23 import android.support.test.filters.MediumTest;
24 import android.view.View;
25 
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.Parameterized;
29 
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 
35 /**
36  * Tests dispatching no-op updates to the GLM and ensures it re-lays out items in the same location
37  */
38 @MediumTest
39 @RunWith(Parameterized.class)
40 public class GridLayoutManagerNoOpUpdateTest extends BaseGridLayoutManagerTest {
41     @Parameterized.Parameters(name = "conf:{0},rtl={1}")
getParams()42     public static List<Object[]> getParams() {
43         List<Object[]> result = new ArrayList<>();
44         for (BaseGridLayoutManagerTest.Config config : createBaseVariations()) {
45             result.add(new Object[]{
46                     config,
47                     true
48             });
49             result.add(new Object[]{
50                     config,
51                     false
52             });
53         }
54         return result;
55     }
56 
57     private final Config mConfig;
58     private final boolean mRtl;
59 
GridLayoutManagerNoOpUpdateTest(Config config, boolean rtl)60     public GridLayoutManagerNoOpUpdateTest(Config config, boolean rtl) {
61         mConfig = config;
62         mRtl = rtl;
63     }
64 
65     @Test
rtlChanges()66     public void rtlChanges() throws Throwable {
67         RecyclerView rv = createRecyclerView();
68         mGlm.setFakeRtl(mRtl);
69         waitForFirstLayout(rv);
70         Map<Long, Rect> before = takeSnapshot();
71 
72         View chosen = mGlm.findViewByPosition(1);
73         assertNotNull("test sanity", chosen);
74         mGlm.expectLayout(2);
75         mAdapter.changeAndNotify(1, 1);
76         mGlm.waitForLayout(2);
77         Map<Long, Rect> after = takeSnapshot();
78         assertSnapshotsEqual(before, after);
79     }
80 
assertSnapshotsEqual(Map<Long, Rect> before, Map<Long, Rect> after)81     private void assertSnapshotsEqual(Map<Long, Rect> before, Map<Long, Rect> after) {
82         for (Map.Entry<Long, Rect> entry : before.entrySet()) {
83             Rect newPosition = after.get(entry.getKey());
84             assertNotNull("cannot find " + entry.getKey() + " in after map", newPosition);
85             assertEquals("position should be the same", entry.getValue(), newPosition);
86         }
87         assertEquals("visible view count should be equal", before.size(), after.size());
88     }
89 
takeSnapshot()90     private Map<Long, Rect> takeSnapshot() {
91         Rect rvBounds = new Rect();
92         if (mRecyclerView.getClipToPadding()) {
93             rvBounds.set(mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
94                     mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(),
95                     mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
96         } else {
97             rvBounds.set(0, 0, mRecyclerView.getWidth(), mRecyclerView.getHeight());
98         }
99         Map<Long, Rect> positionMap = new HashMap<>();
100         for (int i = 0; i < mGlm.getChildCount(); i++) {
101             View child = mGlm.getChildAt(i);
102             Rect childBounds = getChildBounds(mRecyclerView, child, true);
103             long id = mRecyclerView.getChildViewHolder(child).getItemId();
104             if (rvBounds.intersect(childBounds)) {
105                 positionMap.put(id, childBounds);
106             }
107         }
108         return positionMap;
109     }
110 
getChildBounds(RecyclerView recyclerView, View child, boolean offset)111     private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) {
112         RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
113         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
114         Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin,
115                 layoutManager.getDecoratedTop(child) - lp.topMargin,
116                 layoutManager.getDecoratedRight(child) + lp.rightMargin,
117                 layoutManager.getDecoratedBottom(child) + lp.bottomMargin);
118         return rect;
119     }
120 
createRecyclerView()121     private RecyclerView createRecyclerView() throws Throwable {
122         GridTestAdapter adapter = new GridTestAdapter(mConfig.mItemCount);
123         adapter.setHasStableIds(true);
124         return setupBasic(mConfig, adapter);
125     }
126 }
127