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