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 17package androidx.recyclerview.widget; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertNull; 21 22import android.content.Context; 23import android.support.test.InstrumentationRegistry; 24import android.support.test.filters.SmallTest; 25import android.view.View; 26 27import org.junit.Before; 28import org.junit.Test; 29import org.junit.runner.RunWith; 30import org.junit.runners.JUnit4; 31 32 33@SmallTest 34@RunWith(JUnit4.class) 35public class ViewBoundsCheckTest { 36 37 38 private static final String TAG = "ViewBoundsCheckTest"; 39 private Context mContext; 40 41 /** Case #1: 42 * Parent: [2.......................8] 43 Views: [-3...-1] [-1...1] [1...3] [3...5] [5...7] [7...9] [9...11] [11...13] 44 */ 45 int[] mParentBound1 = {2, 8}; 46 int[][] mChildrenBound1 = {{-3, -1}, {-1, 1}, {1, 3}, {3, 5}, {5, 7}, {7, 9}, {9, 11}, 47 {11, 13}}; 48 49 /** Case #2: 50 * Parent: [1...................7] 51 Views: [-3...-1] [-1...1][1...3] [3...5] [5...7] [7...9] [9...11] 52 */ 53 int[] mParentBound2 = {1, 7}; 54 int[][] mChildrenBound2 = {{-3, -1}, {-1, 1}, {1, 3}, {3, 5}, {5, 7}, {7, 9}, {9, 11}}; 55 56 View mParent; 57 View[] mChildren; 58 59 private final ViewBoundsCheck.Callback mBoundCheckCallback = 60 new ViewBoundsCheck.Callback() { 61 @Override 62 public int getChildCount() { 63 return mChildren.length; 64 } 65 66 @Override 67 public View getParent() { 68 return mParent; 69 } 70 71 @Override 72 public View getChildAt(int index) { 73 return mChildren[index]; 74 } 75 76 @Override 77 public int getParentStart() { 78 return mParent.getLeft(); 79 } 80 81 @Override 82 public int getParentEnd() { 83 return mParent.getRight(); 84 } 85 86 @Override 87 public int getChildStart(View view) { 88 return view.getLeft(); 89 } 90 91 @Override 92 public int getChildEnd(View view) { 93 return view.getRight(); 94 } 95 }; 96 97 ViewBoundsCheck mBoundCheck = new ViewBoundsCheck(mBoundCheckCallback); 98 99 @Before 100 public void setUp() throws Exception { 101 mContext = InstrumentationRegistry.getContext(); 102 } 103 104 private void setUpViews(int[] parentBound, int[][] childrenBound) { 105 mParent = new View(mContext); 106 mParent.setLeft(parentBound[0]); 107 mParent.setRight(parentBound[1]); 108 mChildren = new View[childrenBound.length]; 109 for (int i = 0; i < childrenBound.length; i++) { 110 mChildren[i] = new View(mContext); 111 mChildren[i].setLeft(childrenBound[i][0]); 112 mChildren[i].setRight(childrenBound[i][1]); 113 } 114 } 115 116 @Test 117 public void firstFullyVisibleChildFromStart() { 118 setUpViews(mParentBound1, mChildrenBound1); 119 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = ViewBoundsCheck.FLAG_CVS_GT_PVS 120 | ViewBoundsCheck.FLAG_CVS_EQ_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE 121 | ViewBoundsCheck.FLAG_CVE_EQ_PVE; 122 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; 123 View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length, 124 preferredBoundsFlag, acceptableBoundsFlag); 125 assertEquals("The first fully visible child from start should be returned", 3, 126 view.getLeft()); 127 assertEquals("The first fully visible child from start should be returned", 5, 128 view.getRight()); 129 } 130 131 @Test 132 public void firstFullyVisibleChildFromEnd() { 133 setUpViews(mParentBound1, mChildrenBound1); 134 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = ViewBoundsCheck.FLAG_CVS_GT_PVS 135 | ViewBoundsCheck.FLAG_CVS_EQ_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE 136 | ViewBoundsCheck.FLAG_CVE_EQ_PVE; 137 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; 138 View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1, 139 preferredBoundsFlag, acceptableBoundsFlag); 140 assertEquals("The first fully visible child from end should be returned", 5, 141 view.getLeft()); 142 assertEquals("The first fully visible child from end should be returned", 7, 143 view.getRight()); 144 } 145 146 @Test 147 public void firstPartiallyOrFullyVisibleChildFromStartWithViewBoundsNotAligned() { 148 setUpViews(mParentBound1, mChildrenBound1); 149 // These set of flags are used in LinearLayoutManager#findOneVisibleChild 150 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 151 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 152 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 153 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 154 View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length, 155 preferredBoundsFlag, acceptableBoundsFlag); 156 assertEquals("The first partially visible child from start should be returned", 1, 157 view.getLeft()); 158 assertEquals("The first partially visible child from start should be returned", 3, 159 view.getRight()); 160 } 161 162 @Test 163 public void firstPartiallyOrFullyVisibleChildFromStartWithViewBoundsAligned() { 164 setUpViews(mParentBound2, mChildrenBound2); 165 // These set of flags are used in LinearLayoutManager#findOneVisibleChild 166 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 167 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 168 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 169 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 170 View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length, 171 preferredBoundsFlag, acceptableBoundsFlag); 172 assertEquals("The first partially visible child from start should be returned", 1, 173 view.getLeft()); 174 assertEquals("The first partially visible child from start should be returned", 3, 175 view.getRight()); 176 } 177 178 @Test 179 public void firstPartiallyOrFullyVisibleChildFromEndWithViewBoundsNotAligned() { 180 setUpViews(mParentBound1, mChildrenBound1); 181 // These set of flags are used in LinearLayoutManager#findOneVisibleChild 182 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 183 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 184 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 185 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 186 View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1, 187 preferredBoundsFlag, acceptableBoundsFlag); 188 assertEquals("The first partially visible child from end should be returned", 7, 189 view.getLeft()); 190 assertEquals("The first partially visible child from end should be returned", 9, 191 view.getRight()); 192 } 193 194 @Test 195 public void firstPartiallyOrFullyVisibleChildFromEndWithViewBoundsAligned() { 196 setUpViews(mParentBound2, mChildrenBound2); 197 // These set of flags are used in LinearLayoutManager#findOneVisibleChild 198 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 199 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 200 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 201 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 202 View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1, 203 preferredBoundsFlag, acceptableBoundsFlag); 204 assertEquals("The first partially visible child from end should be returned", 5, 205 view.getLeft()); 206 assertEquals("The first partially visible child from end should be returned", 7, 207 view.getRight()); 208 } 209 210 @Test 211 public void lastFullyInvisibleChildFromStart() { 212 setUpViews(mParentBound2, mChildrenBound2); 213 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS 214 | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_GT_PVS); 215 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS 216 | ViewBoundsCheck.FLAG_CVE_LT_PVE); 217 View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length, 218 preferredBoundsFlag, acceptableBoundsFlag); 219 assertEquals("The last fully invisible child from start should be returned", -1, 220 view.getLeft()); 221 assertEquals("TThe last fully invisible child from start should be returned", 1, 222 view.getRight()); 223 } 224 225 @Test 226 public void lastFullyInvisibleChildFromEnd() { 227 setUpViews(mParentBound2, mChildrenBound2); 228 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE 229 | ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_LT_PVE); 230 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE 231 | ViewBoundsCheck.FLAG_CVS_GT_PVS); 232 View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1, 233 preferredBoundsFlag, acceptableBoundsFlag); 234 assertEquals("The last fully invisible child from end should be returned", 7, 235 view.getLeft()); 236 assertEquals("TThe last fully invisible child from end should be returned", 9, 237 view.getRight()); 238 } 239 240 @Test 241 public void noViewsFoundWithinGivenBounds() { 242 setUpViews(mParentBound1, mChildrenBound1); 243 // create a view whose bounds cover its parent. Since no such view exist in the example 244 // layout, null should be returned. 245 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS 246 | ViewBoundsCheck.FLAG_CVE_GT_PVE); 247 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = preferredBoundsFlag; 248 View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length, 249 preferredBoundsFlag, acceptableBoundsFlag); 250 assertNull("Null should be returned since no views are within the given bounds", 251 view); 252 } 253 254} 255