Home | History | Annotate | Download | only in widget
      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