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 android.view.View; 20 21 import androidx.annotation.IntDef; 22 23 import java.lang.annotation.Retention; 24 import java.lang.annotation.RetentionPolicy; 25 26 /** 27 * A utility class used to check the boundaries of a given view within its parent view based on 28 * a set of boundary flags. 29 */ 30 class ViewBoundsCheck { 31 32 static final int GT = 1 << 0; 33 static final int EQ = 1 << 1; 34 static final int LT = 1 << 2; 35 36 37 static final int CVS_PVS_POS = 0; 38 /** 39 * The child view's start should be strictly greater than parent view's start. 40 */ 41 static final int FLAG_CVS_GT_PVS = GT << CVS_PVS_POS; 42 43 /** 44 * The child view's start can be equal to its parent view's start. This flag follows with GT 45 * or LT indicating greater (less) than or equal relation. 46 */ 47 static final int FLAG_CVS_EQ_PVS = EQ << CVS_PVS_POS; 48 49 /** 50 * The child view's start should be strictly less than parent view's start. 51 */ 52 static final int FLAG_CVS_LT_PVS = LT << CVS_PVS_POS; 53 54 55 static final int CVS_PVE_POS = 4; 56 /** 57 * The child view's start should be strictly greater than parent view's end. 58 */ 59 static final int FLAG_CVS_GT_PVE = GT << CVS_PVE_POS; 60 61 /** 62 * The child view's start can be equal to its parent view's end. This flag follows with GT 63 * or LT indicating greater (less) than or equal relation. 64 */ 65 static final int FLAG_CVS_EQ_PVE = EQ << CVS_PVE_POS; 66 67 /** 68 * The child view's start should be strictly less than parent view's end. 69 */ 70 static final int FLAG_CVS_LT_PVE = LT << CVS_PVE_POS; 71 72 73 static final int CVE_PVS_POS = 8; 74 /** 75 * The child view's end should be strictly greater than parent view's start. 76 */ 77 static final int FLAG_CVE_GT_PVS = GT << CVE_PVS_POS; 78 79 /** 80 * The child view's end can be equal to its parent view's start. This flag follows with GT 81 * or LT indicating greater (less) than or equal relation. 82 */ 83 static final int FLAG_CVE_EQ_PVS = EQ << CVE_PVS_POS; 84 85 /** 86 * The child view's end should be strictly less than parent view's start. 87 */ 88 static final int FLAG_CVE_LT_PVS = LT << CVE_PVS_POS; 89 90 91 static final int CVE_PVE_POS = 12; 92 /** 93 * The child view's end should be strictly greater than parent view's end. 94 */ 95 static final int FLAG_CVE_GT_PVE = GT << CVE_PVE_POS; 96 97 /** 98 * The child view's end can be equal to its parent view's end. This flag follows with GT 99 * or LT indicating greater (less) than or equal relation. 100 */ 101 static final int FLAG_CVE_EQ_PVE = EQ << CVE_PVE_POS; 102 103 /** 104 * The child view's end should be strictly less than parent view's end. 105 */ 106 static final int FLAG_CVE_LT_PVE = LT << CVE_PVE_POS; 107 108 static final int MASK = GT | EQ | LT; 109 110 final Callback mCallback; 111 BoundFlags mBoundFlags; 112 /** 113 * The set of flags that can be passed for checking the view boundary conditions. 114 * CVS in the flag name indicates the child view, and PV indicates the parent view.\ 115 * The following S, E indicate a view's start and end points, respectively. 116 * GT and LT indicate a strictly greater and less than relationship. 117 * Greater than or equal (or less than or equal) can be specified by setting both GT and EQ (or 118 * LT and EQ) flags. 119 * For instance, setting both {@link #FLAG_CVS_GT_PVS} and {@link #FLAG_CVS_EQ_PVS} indicate the 120 * child view's start should be greater than or equal to its parent start. 121 */ 122 @IntDef(flag = true, value = { 123 FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS, 124 FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE, 125 FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS, 126 FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE 127 }) 128 @Retention(RetentionPolicy.SOURCE) 129 public @interface ViewBounds {} 130 131 ViewBoundsCheck(Callback callback) { 132 mCallback = callback; 133 mBoundFlags = new BoundFlags(); 134 } 135 136 static class BoundFlags { 137 int mBoundFlags = 0; 138 int mRvStart, mRvEnd, mChildStart, mChildEnd; 139 140 void setBounds(int rvStart, int rvEnd, int childStart, int childEnd) { 141 mRvStart = rvStart; 142 mRvEnd = rvEnd; 143 mChildStart = childStart; 144 mChildEnd = childEnd; 145 } 146 147 void setFlags(@ViewBounds int flags, int mask) { 148 mBoundFlags = (mBoundFlags & ~mask) | (flags & mask); 149 } 150 151 void addFlags(@ViewBounds int flags) { 152 mBoundFlags |= flags; 153 } 154 155 void resetFlags() { 156 mBoundFlags = 0; 157 } 158 159 int compare(int x, int y) { 160 if (x > y) { 161 return GT; 162 } 163 if (x == y) { 164 return EQ; 165 } 166 return LT; 167 } 168 169 boolean boundsMatch() { 170 if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) { 171 if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) { 172 return false; 173 } 174 } 175 176 if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) { 177 if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) { 178 return false; 179 } 180 } 181 182 if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) { 183 if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) { 184 return false; 185 } 186 } 187 188 if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) { 189 if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) { 190 return false; 191 } 192 } 193 return true; 194 } 195 }; 196 197 /** 198 * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within 199 * its parent bounds based on the provided preferredBoundFlags. If no match is found based on 200 * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose 201 * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such 202 * view is found based on either of these two flags, null is returned. 203 * @param fromIndex The view position index to start the search from. 204 * @param toIndex The view position index to end the search at. 205 * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found 206 * based on this flag, that view is returned instantly. 207 * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match 208 * is found. If so, and if acceptableBoundFlags is non-zero, the 209 * last matching acceptable view is returned. Otherwise, null is 210 * returned. 211 * @return The first view that satisfies acceptableBoundFlags or the last view satisfying 212 * acceptableBoundFlags boundary conditions. 213 */ 214 View findOneViewWithinBoundFlags(int fromIndex, int toIndex, 215 @ViewBounds int preferredBoundFlags, 216 @ViewBounds int acceptableBoundFlags) { 217 final int start = mCallback.getParentStart(); 218 final int end = mCallback.getParentEnd(); 219 final int next = toIndex > fromIndex ? 1 : -1; 220 View acceptableMatch = null; 221 for (int i = fromIndex; i != toIndex; i += next) { 222 final View child = mCallback.getChildAt(i); 223 final int childStart = mCallback.getChildStart(child); 224 final int childEnd = mCallback.getChildEnd(child); 225 mBoundFlags.setBounds(start, end, childStart, childEnd); 226 if (preferredBoundFlags != 0) { 227 mBoundFlags.resetFlags(); 228 mBoundFlags.addFlags(preferredBoundFlags); 229 if (mBoundFlags.boundsMatch()) { 230 // found a perfect match 231 return child; 232 } 233 } 234 if (acceptableBoundFlags != 0) { 235 mBoundFlags.resetFlags(); 236 mBoundFlags.addFlags(acceptableBoundFlags); 237 if (mBoundFlags.boundsMatch()) { 238 acceptableMatch = child; 239 } 240 } 241 } 242 return acceptableMatch; 243 } 244 245 /** 246 * Returns whether the specified view lies within the boundary condition of its parent view. 247 * @param child The child view to be checked. 248 * @param boundsFlags The flag against which the child view and parent view are matched. 249 * @return True if the view meets the boundsFlag, false otherwise. 250 */ 251 boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) { 252 mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(), 253 mCallback.getChildStart(child), mCallback.getChildEnd(child)); 254 if (boundsFlags != 0) { 255 mBoundFlags.resetFlags(); 256 mBoundFlags.addFlags(boundsFlags); 257 return mBoundFlags.boundsMatch(); 258 } 259 return false; 260 } 261 262 /** 263 * Callback provided by the user of this class in order to retrieve information about child and 264 * parent boundaries. 265 */ 266 interface Callback { 267 int getChildCount(); 268 View getParent(); 269 View getChildAt(int index); 270 int getParentStart(); 271 int getParentEnd(); 272 int getChildStart(View view); 273 int getChildEnd(View view); 274 } 275 } 276