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 android.view.View.MeasureSpec.AT_MOST; 20import static android.view.View.MeasureSpec.EXACTLY; 21import static android.view.View.MeasureSpec.makeMeasureSpec; 22 23import static org.hamcrest.CoreMatchers.is; 24import static org.hamcrest.CoreMatchers.notNullValue; 25import static org.hamcrest.MatcherAssert.assertThat; 26 27import android.graphics.Rect; 28import android.test.suitebuilder.annotation.MediumTest; 29import android.view.View; 30 31import org.junit.Test; 32import org.junit.runner.RunWith; 33import org.junit.runners.Parameterized; 34 35import java.util.ArrayList; 36import java.util.HashMap; 37import java.util.List; 38import java.util.Map; 39 40/** 41 * Tests whether the layout manager can keep its children positions properly after it is re-laid 42 * out with larger/smaller intermediate size but the same final size. 43 */ 44@MediumTest 45@RunWith(Parameterized.class) 46public class TestResizingRelayoutWithAutoMeasure extends BaseRecyclerViewInstrumentationTest { 47 private final RecyclerView.LayoutManager mLayoutManager; 48 private final float mWidthMultiplier; 49 private final float mHeightMultiplier; 50 51 public TestResizingRelayoutWithAutoMeasure(@SuppressWarnings("UnusedParameters") String name, 52 RecyclerView.LayoutManager layoutManager, float widthMultiplier, 53 float heightMultiplier) { 54 mLayoutManager = layoutManager; 55 mWidthMultiplier = widthMultiplier; 56 mHeightMultiplier = heightMultiplier; 57 } 58 59 @Parameterized.Parameters(name = "{0} w:{2} h:{3}") 60 public static List<Object[]> getParams() { 61 List<Object[]> params = new ArrayList<>(); 62 for (float w : new float[]{.5f, 1f, 2f}) { 63 for (float h : new float[]{.5f, 1f, 2f}) { 64 params.add( 65 new Object[]{"linear layout", new LinearLayoutManager(null), w, h} 66 ); 67 params.add( 68 new Object[]{"grid layout", new GridLayoutManager(null, 3), w, h} 69 ); 70 params.add( 71 new Object[]{"staggered", new StaggeredGridLayoutManager(3, 72 StaggeredGridLayoutManager.VERTICAL), w, h} 73 ); 74 } 75 } 76 return params; 77 } 78 79 @Test 80 public void testResizeDuringMeasurements() throws Throwable { 81 final RecyclerView recyclerView = new RecyclerView(getActivity()); 82 recyclerView.setLayoutManager(mLayoutManager); 83 recyclerView.setAdapter(new TestAdapter(500)); 84 setRecyclerView(recyclerView); 85 getInstrumentation().waitForIdleSync(); 86 assertThat("Test sanity", recyclerView.getChildCount() > 0, is(true)); 87 final int lastPosition = recyclerView.getAdapter().getItemCount() - 1; 88 smoothScrollToPosition(lastPosition); 89 assertThat("test sanity", recyclerView.findViewHolderForAdapterPosition(lastPosition), 90 notNullValue()); 91 runTestOnUiThread(new Runnable() { 92 @Override 93 public void run() { 94 int startHeight = recyclerView.getMeasuredHeight(); 95 int startWidth = recyclerView.getMeasuredWidth(); 96 Map<Integer, Rect> startPositions = capturePositions(recyclerView); 97 recyclerView.measure( 98 makeMeasureSpec((int) (startWidth * mWidthMultiplier), 99 mWidthMultiplier == 1f ? EXACTLY : AT_MOST), 100 makeMeasureSpec((int) (startHeight * mHeightMultiplier), 101 mHeightMultiplier == 1f ? EXACTLY : AT_MOST)); 102 103 recyclerView.measure( 104 makeMeasureSpec(startWidth, EXACTLY), 105 makeMeasureSpec(startHeight, EXACTLY)); 106 recyclerView.dispatchLayout(); 107 Map<Integer, Rect> endPositions = capturePositions(recyclerView); 108 assertStartItemPositions(startPositions, endPositions); 109 } 110 }); 111 } 112 113 private void assertStartItemPositions(Map<Integer, Rect> startPositions, 114 Map<Integer, Rect> endPositions) { 115 for (Map.Entry<Integer, Rect> entry : startPositions.entrySet()) { 116 Rect rect = endPositions.get(entry.getKey()); 117 assertThat("view for position " + entry.getKey() + " at" + entry.getValue(), rect, 118 notNullValue()); 119 assertThat("rect for position " + entry.getKey(), entry.getValue(), is(rect)); 120 } 121 } 122 123 private Map<Integer, Rect> capturePositions(RecyclerView recyclerView) { 124 Map<Integer, Rect> positions = new HashMap<>(); 125 for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 126 View view = mLayoutManager.getChildAt(i); 127 int childAdapterPosition = recyclerView.getChildAdapterPosition(view); 128 Rect outRect = new Rect(); 129 mLayoutManager.getDecoratedBoundsWithMargins(view, outRect); 130 // only record if outRect is visible 131 if (outRect.left >= mRecyclerView.getWidth() || 132 outRect.top >= mRecyclerView.getHeight() || 133 outRect.right < 0 || 134 outRect.bottom < 0) { 135 continue; 136 } 137 positions.put(childAdapterPosition, outRect); 138 } 139 return positions; 140 } 141}