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 
ViewBoundsCheck(Callback callback)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 
setBounds(int rvStart, int rvEnd, int childStart, int childEnd)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 
setFlags(@iewBounds int flags, int mask)147         void setFlags(@ViewBounds int flags, int mask) {
148             mBoundFlags = (mBoundFlags & ~mask) | (flags & mask);
149         }
150 
addFlags(@iewBounds int flags)151         void addFlags(@ViewBounds int flags) {
152             mBoundFlags |= flags;
153         }
154 
resetFlags()155         void resetFlags() {
156             mBoundFlags = 0;
157         }
158 
compare(int x, int y)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 
boundsMatch()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      */
findOneViewWithinBoundFlags(int fromIndex, int toIndex, @ViewBounds int preferredBoundFlags, @ViewBounds int acceptableBoundFlags)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      */
isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags)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 {
getChildCount()267         int getChildCount();
getParent()268         View getParent();
getChildAt(int index)269         View getChildAt(int index);
getParentStart()270         int getParentStart();
getParentEnd()271         int getParentEnd();
getChildStart(View view)272         int getChildStart(View view);
getChildEnd(View view)273         int getChildEnd(View view);
274     }
275 }
276