/* * Copyright (C) 2017 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 android.support.v7.widget; import android.support.annotation.IntDef; import android.view.View; 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_EQ_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); } }