| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.recyclerview.widget; |
| |
| import android.view.View; |
| |
| import androidx.annotation.IntDef; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| /** |
| * A utility class used to check the boundaries of a given view within its parent view based on |
| * a set of boundary flags. |
| */ |
| class ViewBoundsCheck { |
| |
| static final int GT = 1 << 0; |
| static final int EQ = 1 << 1; |
| static final int LT = 1 << 2; |
| |
| |
| static final int CVS_PVS_POS = 0; |
| /** |
| * The child view's start should be strictly greater than parent view's start. |
| */ |
| static final int FLAG_CVS_GT_PVS = GT << CVS_PVS_POS; |
| |
| /** |
| * The child view's start can be equal to its parent view's start. This flag follows with GT |
| * or LT indicating greater (less) than or equal relation. |
| */ |
| static final int FLAG_CVS_EQ_PVS = EQ << CVS_PVS_POS; |
| |
| /** |
| * The child view's start should be strictly less than parent view's start. |
| */ |
| static final int FLAG_CVS_LT_PVS = LT << CVS_PVS_POS; |
| |
| |
| static final int CVS_PVE_POS = 4; |
| /** |
| * The child view's start should be strictly greater than parent view's end. |
| */ |
| static final int FLAG_CVS_GT_PVE = GT << CVS_PVE_POS; |
| |
| /** |
| * The child view's start can be equal to its parent view's end. This flag follows with GT |
| * or LT indicating greater (less) than or equal relation. |
| */ |
| static final int FLAG_CVS_EQ_PVE = EQ << CVS_PVE_POS; |
| |
| /** |
| * The child view's start should be strictly less than parent view's end. |
| */ |
| static final int FLAG_CVS_LT_PVE = LT << CVS_PVE_POS; |
| |
| |
| static final int CVE_PVS_POS = 8; |
| /** |
| * The child view's end should be strictly greater than parent view's start. |
| */ |
| static final int FLAG_CVE_GT_PVS = GT << CVE_PVS_POS; |
| |
| /** |
| * The child view's end can be equal to its parent view's start. This flag follows with GT |
| * or LT indicating greater (less) than or equal relation. |
| */ |
| static final int FLAG_CVE_EQ_PVS = EQ << CVE_PVS_POS; |
| |
| /** |
| * The child view's end should be strictly less than parent view's start. |
| */ |
| static final int FLAG_CVE_LT_PVS = LT << CVE_PVS_POS; |
| |
| |
| static final int CVE_PVE_POS = 12; |
| /** |
| * The child view's end should be strictly greater than parent view's end. |
| */ |
| static final int FLAG_CVE_GT_PVE = GT << CVE_PVE_POS; |
| |
| /** |
| * The child view's end can be equal to its parent view's end. This flag follows with GT |
| * or LT indicating greater (less) than or equal relation. |
| */ |
| static final int FLAG_CVE_EQ_PVE = EQ << CVE_PVE_POS; |
| |
| /** |
| * The child view's end should be strictly less than parent view's end. |
| */ |
| static final int FLAG_CVE_LT_PVE = LT << CVE_PVE_POS; |
| |
| static final int MASK = GT | EQ | LT; |
| |
| final Callback mCallback; |
| BoundFlags mBoundFlags; |
| /** |
| * The set of flags that can be passed for checking the view boundary conditions. |
| * CVS in the flag name indicates the child view, and PV indicates the parent view.\ |
| * The following S, E indicate a view's start and end points, respectively. |
| * GT and LT indicate a strictly greater and less than relationship. |
| * Greater than or equal (or less than or equal) can be specified by setting both GT and EQ (or |
| * LT and EQ) flags. |
| * For instance, setting both {@link #FLAG_CVS_GT_PVS} and {@link #FLAG_CVS_EQ_PVS} indicate the |
| * child view's start should be greater than or equal to its parent start. |
| */ |
| @IntDef(flag = true, value = { |
| FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS, |
| FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE, |
| FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS, |
| FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ViewBounds {} |
| |
| ViewBoundsCheck(Callback callback) { |
| mCallback = callback; |
| mBoundFlags = new BoundFlags(); |
| } |
| |
| static class BoundFlags { |
| int mBoundFlags = 0; |
| int mRvStart, mRvEnd, mChildStart, mChildEnd; |
| |
| void setBounds(int rvStart, int rvEnd, int childStart, int childEnd) { |
| mRvStart = rvStart; |
| mRvEnd = rvEnd; |
| mChildStart = childStart; |
| mChildEnd = childEnd; |
| } |
| |
| void setFlags(@ViewBounds int flags, int mask) { |
| mBoundFlags = (mBoundFlags & ~mask) | (flags & mask); |
| } |
| |
| void addFlags(@ViewBounds int flags) { |
| mBoundFlags |= flags; |
| } |
| |
| void resetFlags() { |
| mBoundFlags = 0; |
| } |
| |
| int compare(int x, int y) { |
| if (x > y) { |
| return GT; |
| } |
| if (x == y) { |
| return EQ; |
| } |
| return LT; |
| } |
| |
| boolean boundsMatch() { |
| if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) { |
| if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) { |
| return false; |
| } |
| } |
| |
| if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) { |
| if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) { |
| return false; |
| } |
| } |
| |
| if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) { |
| if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) { |
| return false; |
| } |
| } |
| |
| if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) { |
| if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| }; |
| |
| /** |
| * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within |
| * its parent bounds based on the provided preferredBoundFlags. If no match is found based on |
| * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose |
| * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such |
| * view is found based on either of these two flags, null is returned. |
| * @param fromIndex The view position index to start the search from. |
| * @param toIndex The view position index to end the search at. |
| * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found |
| * based on this flag, that view is returned instantly. |
| * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match |
| * is found. If so, and if acceptableBoundFlags is non-zero, the |
| * last matching acceptable view is returned. Otherwise, null is |
| * returned. |
| * @return The first view that satisfies acceptableBoundFlags or the last view satisfying |
| * acceptableBoundFlags boundary conditions. |
| */ |
| View findOneViewWithinBoundFlags(int fromIndex, int toIndex, |
| @ViewBounds int preferredBoundFlags, |
| @ViewBounds int acceptableBoundFlags) { |
| final int start = mCallback.getParentStart(); |
| final int end = mCallback.getParentEnd(); |
| final int next = toIndex > fromIndex ? 1 : -1; |
| View acceptableMatch = null; |
| for (int i = fromIndex; i != toIndex; i += next) { |
| final View child = mCallback.getChildAt(i); |
| final int childStart = mCallback.getChildStart(child); |
| final int childEnd = mCallback.getChildEnd(child); |
| mBoundFlags.setBounds(start, end, childStart, childEnd); |
| if (preferredBoundFlags != 0) { |
| mBoundFlags.resetFlags(); |
| mBoundFlags.addFlags(preferredBoundFlags); |
| if (mBoundFlags.boundsMatch()) { |
| // found a perfect match |
| return child; |
| } |
| } |
| if (acceptableBoundFlags != 0) { |
| mBoundFlags.resetFlags(); |
| mBoundFlags.addFlags(acceptableBoundFlags); |
| if (mBoundFlags.boundsMatch()) { |
| acceptableMatch = child; |
| } |
| } |
| } |
| return acceptableMatch; |
| } |
| |
| /** |
| * Returns whether the specified view lies within the boundary condition of its parent view. |
| * @param child The child view to be checked. |
| * @param boundsFlags The flag against which the child view and parent view are matched. |
| * @return True if the view meets the boundsFlag, false otherwise. |
| */ |
| boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) { |
| mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(), |
| mCallback.getChildStart(child), mCallback.getChildEnd(child)); |
| if (boundsFlags != 0) { |
| mBoundFlags.resetFlags(); |
| mBoundFlags.addFlags(boundsFlags); |
| return mBoundFlags.boundsMatch(); |
| } |
| return false; |
| } |
| |
| /** |
| * Callback provided by the user of this class in order to retrieve information about child and |
| * parent boundaries. |
| */ |
| interface Callback { |
| int getChildCount(); |
| View getParent(); |
| View getChildAt(int index); |
| int getParentStart(); |
| int getParentEnd(); |
| int getChildStart(View view); |
| int getChildEnd(View view); |
| } |
| } |