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 
addFlags(@iewBounds int flags)147         void addFlags(@ViewBounds int flags) {
148             mBoundFlags |= flags;
149         }
150 
resetFlags()151         void resetFlags() {
152             mBoundFlags = 0;
153         }
154 
compare(int x, int y)155         int compare(int x, int y) {
156             if (x > y) {
157                 return GT;
158             }
159             if (x == y) {
160                 return EQ;
161             }
162             return LT;
163         }
164 
boundsMatch()165         boolean boundsMatch() {
166             if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) {
167                 if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) {
168                     return false;
169                 }
170             }
171 
172             if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) {
173                 if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) {
174                     return false;
175                 }
176             }
177 
178             if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) {
179                 if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) {
180                     return false;
181                 }
182             }
183 
184             if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) {
185                 if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) {
186                     return false;
187                 }
188             }
189             return true;
190         }
191     };
192 
193     /**
194      * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within
195      * its parent bounds based on the provided preferredBoundFlags. If no match is found based on
196      * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose
197      * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such
198      * view is found based on either of these two flags, null is returned.
199      * @param fromIndex The view position index to start the search from.
200      * @param toIndex The view position index to end the search at.
201      * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found
202      *                            based on this flag, that view is returned instantly.
203      * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match
204      *                             is found. If so, and if acceptableBoundFlags is non-zero, the
205      *                             last matching acceptable view is returned. Otherwise, null is
206      *                             returned.
207      * @return The first view that satisfies acceptableBoundFlags or the last view satisfying
208      * acceptableBoundFlags boundary conditions.
209      */
findOneViewWithinBoundFlags(int fromIndex, int toIndex, @ViewBounds int preferredBoundFlags, @ViewBounds int acceptableBoundFlags)210     View findOneViewWithinBoundFlags(int fromIndex, int toIndex,
211             @ViewBounds int preferredBoundFlags,
212             @ViewBounds int acceptableBoundFlags) {
213         final int start = mCallback.getParentStart();
214         final int end = mCallback.getParentEnd();
215         final int next = toIndex > fromIndex ? 1 : -1;
216         View acceptableMatch = null;
217         for (int i = fromIndex; i != toIndex; i += next) {
218             final View child = mCallback.getChildAt(i);
219             final int childStart = mCallback.getChildStart(child);
220             final int childEnd = mCallback.getChildEnd(child);
221             mBoundFlags.setBounds(start, end, childStart, childEnd);
222             if (preferredBoundFlags != 0) {
223                 mBoundFlags.resetFlags();
224                 mBoundFlags.addFlags(preferredBoundFlags);
225                 if (mBoundFlags.boundsMatch()) {
226                     // found a perfect match
227                     return child;
228                 }
229             }
230             if (acceptableBoundFlags != 0) {
231                 mBoundFlags.resetFlags();
232                 mBoundFlags.addFlags(acceptableBoundFlags);
233                 if (mBoundFlags.boundsMatch()) {
234                     acceptableMatch = child;
235                 }
236             }
237         }
238         return acceptableMatch;
239     }
240 
241     /**
242      * Returns whether the specified view lies within the boundary condition of its parent view.
243      * @param child The child view to be checked.
244      * @param boundsFlags The flag against which the child view and parent view are matched.
245      * @return True if the view meets the boundsFlag, false otherwise.
246      */
isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags)247     boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) {
248         mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(),
249                 mCallback.getChildStart(child), mCallback.getChildEnd(child));
250         if (boundsFlags != 0) {
251             mBoundFlags.resetFlags();
252             mBoundFlags.addFlags(boundsFlags);
253             return mBoundFlags.boundsMatch();
254         }
255         return false;
256     }
257 
258     /**
259      * Callback provided by the user of this class in order to retrieve information about child and
260      * parent boundaries.
261      */
262     interface Callback {
getChildAt(int index)263         View getChildAt(int index);
getParentStart()264         int getParentStart();
getParentEnd()265         int getParentEnd();
getChildStart(View view)266         int getChildStart(View view);
getChildEnd(View view)267         int getChildEnd(View view);
268     }
269 }
270