| /* |
| * 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.coordinatorlayout.widget; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.Region; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Build; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.SystemClock; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.view.Gravity; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewParent; |
| import android.view.ViewTreeObserver; |
| |
| import androidx.annotation.AttrRes; |
| import androidx.annotation.ColorInt; |
| import androidx.annotation.DrawableRes; |
| import androidx.annotation.FloatRange; |
| import androidx.annotation.IdRes; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.coordinatorlayout.R; |
| import androidx.core.content.ContextCompat; |
| import androidx.core.graphics.drawable.DrawableCompat; |
| import androidx.core.util.ObjectsCompat; |
| import androidx.core.util.Pools; |
| import androidx.core.view.GravityCompat; |
| import androidx.core.view.NestedScrollingParent; |
| import androidx.core.view.NestedScrollingParent2; |
| import androidx.core.view.NestedScrollingParent3; |
| import androidx.core.view.NestedScrollingParentHelper; |
| import androidx.core.view.ViewCompat; |
| import androidx.core.view.ViewCompat.NestedScrollType; |
| import androidx.core.view.ViewCompat.ScrollAxis; |
| import androidx.core.view.WindowInsetsCompat; |
| import androidx.customview.view.AbsSavedState; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}. |
| * |
| * <p>CoordinatorLayout is intended for two primary use cases:</p> |
| * <ol> |
| * <li>As a top-level application decor or chrome layout</li> |
| * <li>As a container for a specific interaction with one or more child views</li> |
| * </ol> |
| * |
| * <p>By specifying {@link Behavior Behaviors} for child views of a |
| * CoordinatorLayout you can provide many different interactions within a single parent and those |
| * views can also interact with one another. View classes can specify a default behavior when |
| * used as a child of a CoordinatorLayout using the |
| * {@link DefaultBehavior} annotation.</p> |
| * |
| * <p>Behaviors may be used to implement a variety of interactions and additional layout |
| * modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons |
| * that stick to other elements as they move and animate.</p> |
| * |
| * <p>Children of a CoordinatorLayout may have an |
| * {@link LayoutParams#setAnchorId(int) anchor}. This view id must correspond |
| * to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself |
| * or a descendant of the anchored child. This can be used to place floating views relative to |
| * other arbitrary content panes.</p> |
| * |
| * <p>Children can specify {@link LayoutParams#insetEdge} to describe how the |
| * view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by |
| * {@link LayoutParams#dodgeInsetEdges} will be moved appropriately so that the |
| * views do not overlap.</p> |
| */ |
| public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2, |
| NestedScrollingParent3 { |
| static final String TAG = "CoordinatorLayout"; |
| static final String WIDGET_PACKAGE_NAME; |
| |
| static { |
| final Package pkg = CoordinatorLayout.class.getPackage(); |
| WIDGET_PACKAGE_NAME = pkg != null ? pkg.getName() : null; |
| } |
| |
| private static final int TYPE_ON_INTERCEPT = 0; |
| private static final int TYPE_ON_TOUCH = 1; |
| |
| static { |
| if (Build.VERSION.SDK_INT >= 21) { |
| TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator(); |
| } else { |
| TOP_SORTED_CHILDREN_COMPARATOR = null; |
| } |
| } |
| |
| static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] { |
| Context.class, |
| AttributeSet.class |
| }; |
| |
| static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors = |
| new ThreadLocal<>(); |
| |
| static final int EVENT_PRE_DRAW = 0; |
| static final int EVENT_NESTED_SCROLL = 1; |
| static final int EVENT_VIEW_REMOVED = 2; |
| |
| /** @hide */ |
| @RestrictTo(LIBRARY_GROUP_PREFIX) |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED}) |
| public @interface DispatchChangeEvent {} |
| |
| static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR; |
| private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12); |
| |
| @NonNull |
| private static Rect acquireTempRect() { |
| Rect rect = sRectPool.acquire(); |
| if (rect == null) { |
| rect = new Rect(); |
| } |
| return rect; |
| } |
| |
| private static void releaseTempRect(@NonNull Rect rect) { |
| rect.setEmpty(); |
| sRectPool.release(rect); |
| } |
| |
| private final List<View> mDependencySortedChildren = new ArrayList<>(); |
| private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>(); |
| |
| private final List<View> mTempList1 = new ArrayList<>(); |
| private final List<View> mTempDependenciesList = new ArrayList<>(); |
| private Paint mScrimPaint; |
| |
| // Array to be mutated by calls to nested scrolling related methods of Behavior to satisfy the |
| // 'consumed' parameter. This only exist to prevent GC and object instantiation costs that are |
| // present before API 21. |
| private final int[] mBehaviorConsumed = new int[2]; |
| |
| // Array to be used for calls from v2 version of onNestedScroll to v3 version of onNestedScroll. |
| // This only exist to prevent GC and object instantiation costs that are present before API 21. |
| private final int[] mNestedScrollingV2ConsumedCompat = new int[2]; |
| |
| private boolean mDisallowInterceptReset; |
| |
| private boolean mIsAttachedToWindow; |
| |
| private int[] mKeylines; |
| |
| private View mBehaviorTouchView; |
| private View mNestedScrollingTarget; |
| |
| private OnPreDrawListener mOnPreDrawListener; |
| private boolean mNeedsPreDrawListener; |
| |
| private WindowInsetsCompat mLastInsets; |
| private boolean mDrawStatusBarBackground; |
| private Drawable mStatusBarBackground; |
| |
| OnHierarchyChangeListener mOnHierarchyChangeListener; |
| private androidx.core.view.OnApplyWindowInsetsListener mApplyWindowInsetsListener; |
| |
| private final NestedScrollingParentHelper mNestedScrollingParentHelper = |
| new NestedScrollingParentHelper(this); |
| |
| public CoordinatorLayout(@NonNull Context context) { |
| this(context, null); |
| } |
| |
| public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) { |
| this(context, attrs, R.attr.coordinatorLayoutStyle); |
| } |
| |
| public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs, |
| @AttrRes int defStyleAttr) { |
| super(context, attrs, defStyleAttr); |
| |
| final TypedArray a = (defStyleAttr == 0) |
| ? context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout, |
| 0, R.style.Widget_Support_CoordinatorLayout) |
| : context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout, |
| defStyleAttr, 0); |
| if (Build.VERSION.SDK_INT >= 29) { |
| if (defStyleAttr == 0) { |
| saveAttributeDataForStyleable( |
| context, R.styleable.CoordinatorLayout, attrs, a, 0, |
| R.style.Widget_Support_CoordinatorLayout); |
| } else { |
| saveAttributeDataForStyleable( |
| context, R.styleable.CoordinatorLayout, attrs, a, defStyleAttr, 0); |
| } |
| } |
| |
| final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0); |
| if (keylineArrayRes != 0) { |
| final Resources res = context.getResources(); |
| mKeylines = res.getIntArray(keylineArrayRes); |
| final float density = res.getDisplayMetrics().density; |
| final int count = mKeylines.length; |
| for (int i = 0; i < count; i++) { |
| mKeylines[i] = (int) (mKeylines[i] * density); |
| } |
| } |
| mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground); |
| a.recycle(); |
| |
| setupForInsets(); |
| super.setOnHierarchyChangeListener(new HierarchyChangeListener()); |
| } |
| |
| @Override |
| public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) { |
| mOnHierarchyChangeListener = onHierarchyChangeListener; |
| } |
| |
| @Override |
| public void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| resetTouchBehaviors(false); |
| if (mNeedsPreDrawListener) { |
| if (mOnPreDrawListener == null) { |
| mOnPreDrawListener = new OnPreDrawListener(); |
| } |
| final ViewTreeObserver vto = getViewTreeObserver(); |
| vto.addOnPreDrawListener(mOnPreDrawListener); |
| } |
| if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) { |
| // We're set to fitSystemWindows but we haven't had any insets yet... |
| // We should request a new dispatch of window insets |
| ViewCompat.requestApplyInsets(this); |
| } |
| mIsAttachedToWindow = true; |
| } |
| |
| @Override |
| public void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| resetTouchBehaviors(false); |
| if (mNeedsPreDrawListener && mOnPreDrawListener != null) { |
| final ViewTreeObserver vto = getViewTreeObserver(); |
| vto.removeOnPreDrawListener(mOnPreDrawListener); |
| } |
| if (mNestedScrollingTarget != null) { |
| onStopNestedScroll(mNestedScrollingTarget); |
| } |
| mIsAttachedToWindow = false; |
| } |
| |
| /** |
| * Set a drawable to draw in the insets area for the status bar. |
| * Note that this will only be activated if this DrawerLayout fitsSystemWindows. |
| * |
| * @param bg Background drawable to draw behind the status bar |
| */ |
| public void setStatusBarBackground(@Nullable final Drawable bg) { |
| if (mStatusBarBackground != bg) { |
| if (mStatusBarBackground != null) { |
| mStatusBarBackground.setCallback(null); |
| } |
| mStatusBarBackground = bg != null ? bg.mutate() : null; |
| if (mStatusBarBackground != null) { |
| if (mStatusBarBackground.isStateful()) { |
| mStatusBarBackground.setState(getDrawableState()); |
| } |
| DrawableCompat.setLayoutDirection(mStatusBarBackground, |
| ViewCompat.getLayoutDirection(this)); |
| mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false); |
| mStatusBarBackground.setCallback(this); |
| } |
| ViewCompat.postInvalidateOnAnimation(this); |
| } |
| } |
| |
| /** |
| * Gets the drawable used to draw in the insets area for the status bar. |
| * |
| * @return The status bar background drawable, or null if none set |
| */ |
| @Nullable |
| public Drawable getStatusBarBackground() { |
| return mStatusBarBackground; |
| } |
| |
| @Override |
| protected void drawableStateChanged() { |
| super.drawableStateChanged(); |
| |
| final int[] state = getDrawableState(); |
| boolean changed = false; |
| |
| Drawable d = mStatusBarBackground; |
| if (d != null && d.isStateful()) { |
| changed |= d.setState(state); |
| } |
| |
| if (changed) { |
| invalidate(); |
| } |
| } |
| |
| @Override |
| protected boolean verifyDrawable(Drawable who) { |
| return super.verifyDrawable(who) || who == mStatusBarBackground; |
| } |
| |
| @Override |
| public void setVisibility(int visibility) { |
| super.setVisibility(visibility); |
| |
| final boolean visible = visibility == VISIBLE; |
| if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) { |
| mStatusBarBackground.setVisible(visible, false); |
| } |
| } |
| |
| /** |
| * Set a drawable to draw in the insets area for the status bar. |
| * Note that this will only be activated if this DrawerLayout fitsSystemWindows. |
| * |
| * @param resId Resource id of a background drawable to draw behind the status bar |
| */ |
| public void setStatusBarBackgroundResource(@DrawableRes int resId) { |
| setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null); |
| } |
| |
| /** |
| * Set a drawable to draw in the insets area for the status bar. |
| * Note that this will only be activated if this DrawerLayout fitsSystemWindows. |
| * |
| * @param color Color to use as a background drawable to draw behind the status bar |
| * in 0xAARRGGBB format. |
| */ |
| public void setStatusBarBackgroundColor(@ColorInt int color) { |
| setStatusBarBackground(new ColorDrawable(color)); |
| } |
| |
| final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) { |
| if (!ObjectsCompat.equals(mLastInsets, insets)) { |
| mLastInsets = insets; |
| mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0; |
| setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null); |
| |
| // Now dispatch to the Behaviors |
| insets = dispatchApplyWindowInsetsToBehaviors(insets); |
| requestLayout(); |
| } |
| return insets; |
| } |
| |
| /** |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP_PREFIX) |
| public final WindowInsetsCompat getLastWindowInsets() { |
| return mLastInsets; |
| } |
| |
| /** |
| * Reset all Behavior-related tracking records either to clean up or in preparation |
| * for a new event stream. This should be called when attached or detached from a window, |
| * in response to an UP or CANCEL event, when intercept is request-disallowed |
| * and similar cases where an event stream in progress will be aborted. |
| */ |
| @SuppressWarnings("unchecked") |
| private void resetTouchBehaviors(boolean notifyOnInterceptTouchEvent) { |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = getChildAt(i); |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior b = lp.getBehavior(); |
| if (b != null) { |
| final long now = SystemClock.uptimeMillis(); |
| final MotionEvent cancelEvent = MotionEvent.obtain(now, now, |
| MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); |
| if (notifyOnInterceptTouchEvent) { |
| b.onInterceptTouchEvent(this, child, cancelEvent); |
| } else { |
| b.onTouchEvent(this, child, cancelEvent); |
| } |
| cancelEvent.recycle(); |
| } |
| } |
| |
| for (int i = 0; i < childCount; i++) { |
| final View child = getChildAt(i); |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| lp.resetTouchBehaviorTracking(); |
| } |
| mBehaviorTouchView = null; |
| mDisallowInterceptReset = false; |
| } |
| |
| /** |
| * Populate a list with the current child views, sorted such that the topmost views |
| * in z-order are at the front of the list. Useful for hit testing and event dispatch. |
| */ |
| private void getTopSortedChildren(List<View> out) { |
| out.clear(); |
| |
| final boolean useCustomOrder = isChildrenDrawingOrderEnabled(); |
| final int childCount = getChildCount(); |
| for (int i = childCount - 1; i >= 0; i--) { |
| final int childIndex = useCustomOrder ? getChildDrawingOrder(childCount, i) : i; |
| final View child = getChildAt(childIndex); |
| out.add(child); |
| } |
| |
| if (TOP_SORTED_CHILDREN_COMPARATOR != null) { |
| Collections.sort(out, TOP_SORTED_CHILDREN_COMPARATOR); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private boolean performIntercept(MotionEvent ev, final int type) { |
| boolean intercepted = false; |
| boolean newBlock = false; |
| |
| MotionEvent cancelEvent = null; |
| |
| final int action = ev.getActionMasked(); |
| |
| final List<View> topmostChildList = mTempList1; |
| getTopSortedChildren(topmostChildList); |
| |
| // Let topmost child views inspect first |
| final int childCount = topmostChildList.size(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = topmostChildList.get(i); |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior b = lp.getBehavior(); |
| |
| if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) { |
| // Cancel all behaviors beneath the one that intercepted. |
| // If the event is "down" then we don't have anything to cancel yet. |
| if (b != null) { |
| if (cancelEvent == null) { |
| final long now = SystemClock.uptimeMillis(); |
| cancelEvent = MotionEvent.obtain(now, now, |
| MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); |
| } |
| switch (type) { |
| case TYPE_ON_INTERCEPT: |
| b.onInterceptTouchEvent(this, child, cancelEvent); |
| break; |
| case TYPE_ON_TOUCH: |
| b.onTouchEvent(this, child, cancelEvent); |
| break; |
| } |
| } |
| continue; |
| } |
| |
| if (!intercepted && b != null) { |
| switch (type) { |
| case TYPE_ON_INTERCEPT: |
| intercepted = b.onInterceptTouchEvent(this, child, ev); |
| break; |
| case TYPE_ON_TOUCH: |
| intercepted = b.onTouchEvent(this, child, ev); |
| break; |
| } |
| if (intercepted) { |
| mBehaviorTouchView = child; |
| } |
| } |
| |
| // Don't keep going if we're not allowing interaction below this. |
| // Setting newBlock will make sure we cancel the rest of the behaviors. |
| final boolean wasBlocking = lp.didBlockInteraction(); |
| final boolean isBlocking = lp.isBlockingInteractionBelow(this, child); |
| newBlock = isBlocking && !wasBlocking; |
| if (isBlocking && !newBlock) { |
| // Stop here since we don't have anything more to cancel - we already did |
| // when the behavior first started blocking things below this point. |
| break; |
| } |
| } |
| |
| topmostChildList.clear(); |
| |
| return intercepted; |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| final int action = ev.getActionMasked(); |
| |
| // Make sure we reset in case we had missed a previous important event. |
| if (action == MotionEvent.ACTION_DOWN) { |
| resetTouchBehaviors(true); |
| } |
| |
| final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT); |
| |
| if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |
| resetTouchBehaviors(true); |
| } |
| |
| return intercepted; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean onTouchEvent(MotionEvent ev) { |
| boolean handled = false; |
| boolean cancelSuper = false; |
| MotionEvent cancelEvent = null; |
| |
| final int action = ev.getActionMasked(); |
| |
| if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) { |
| // Safe since performIntercept guarantees that |
| // mBehaviorTouchView != null if it returns true |
| final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams(); |
| final Behavior b = lp.getBehavior(); |
| if (b != null) { |
| handled = b.onTouchEvent(this, mBehaviorTouchView, ev); |
| } |
| } |
| |
| // Keep the super implementation correct |
| if (mBehaviorTouchView == null) { |
| handled |= super.onTouchEvent(ev); |
| } else if (cancelSuper) { |
| if (cancelEvent == null) { |
| final long now = SystemClock.uptimeMillis(); |
| cancelEvent = MotionEvent.obtain(now, now, |
| MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); |
| } |
| super.onTouchEvent(cancelEvent); |
| } |
| |
| if (cancelEvent != null) { |
| cancelEvent.recycle(); |
| } |
| |
| if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |
| resetTouchBehaviors(false); |
| } |
| |
| return handled; |
| } |
| |
| @Override |
| public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
| super.requestDisallowInterceptTouchEvent(disallowIntercept); |
| if (disallowIntercept && !mDisallowInterceptReset) { |
| resetTouchBehaviors(false); |
| mDisallowInterceptReset = true; |
| } |
| } |
| |
| private int getKeyline(int index) { |
| if (mKeylines == null) { |
| Log.e(TAG, "No keylines defined for " + this + " - attempted index lookup " + index); |
| return 0; |
| } |
| |
| if (index < 0 || index >= mKeylines.length) { |
| Log.e(TAG, "Keyline index " + index + " out of range for " + this); |
| return 0; |
| } |
| |
| return mKeylines[index]; |
| } |
| |
| @SuppressWarnings("unchecked") |
| static Behavior parseBehavior(Context context, AttributeSet attrs, String name) { |
| if (TextUtils.isEmpty(name)) { |
| return null; |
| } |
| |
| final String fullName; |
| if (name.startsWith(".")) { |
| // Relative to the app package. Prepend the app package name. |
| fullName = context.getPackageName() + name; |
| } else if (name.indexOf('.') >= 0) { |
| // Fully qualified package name. |
| fullName = name; |
| } else { |
| // Assume stock behavior in this package (if we have one) |
| fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME) |
| ? (WIDGET_PACKAGE_NAME + '.' + name) |
| : name; |
| } |
| |
| try { |
| Map<String, Constructor<Behavior>> constructors = sConstructors.get(); |
| if (constructors == null) { |
| constructors = new HashMap<>(); |
| sConstructors.set(constructors); |
| } |
| Constructor<Behavior> c = constructors.get(fullName); |
| if (c == null) { |
| final Class<Behavior> clazz = |
| (Class<Behavior>) Class.forName(fullName, false, context.getClassLoader()); |
| c = clazz.getConstructor(CONSTRUCTOR_PARAMS); |
| c.setAccessible(true); |
| constructors.put(fullName, c); |
| } |
| return c.newInstance(context, attrs); |
| } catch (Exception e) { |
| throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e); |
| } |
| } |
| |
| LayoutParams getResolvedLayoutParams(View child) { |
| final LayoutParams result = (LayoutParams) child.getLayoutParams(); |
| if (!result.mBehaviorResolved) { |
| if (child instanceof AttachedBehavior) { |
| Behavior attachedBehavior = ((AttachedBehavior) child).getBehavior(); |
| if (attachedBehavior == null) { |
| Log.e(TAG, "Attached behavior class is null"); |
| } |
| result.setBehavior(attachedBehavior); |
| result.mBehaviorResolved = true; |
| } else { |
| // The deprecated path that looks up the attached behavior based on annotation |
| Class<?> childClass = child.getClass(); |
| DefaultBehavior defaultBehavior = null; |
| while (childClass != null |
| && (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) |
| == null) { |
| childClass = childClass.getSuperclass(); |
| } |
| if (defaultBehavior != null) { |
| try { |
| result.setBehavior( |
| defaultBehavior.value().getDeclaredConstructor().newInstance()); |
| } catch (Exception e) { |
| Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() |
| + " could not be instantiated. Did you forget" |
| + " a default constructor?", e); |
| } |
| } |
| result.mBehaviorResolved = true; |
| } |
| } |
| return result; |
| } |
| |
| private void prepareChildren() { |
| mDependencySortedChildren.clear(); |
| mChildDag.clear(); |
| |
| for (int i = 0, count = getChildCount(); i < count; i++) { |
| final View view = getChildAt(i); |
| |
| final LayoutParams lp = getResolvedLayoutParams(view); |
| lp.findAnchorView(this, view); |
| |
| mChildDag.addNode(view); |
| |
| // Now iterate again over the other children, adding any dependencies to the graph |
| for (int j = 0; j < count; j++) { |
| if (j == i) { |
| continue; |
| } |
| final View other = getChildAt(j); |
| if (lp.dependsOn(this, view, other)) { |
| if (!mChildDag.contains(other)) { |
| // Make sure that the other node is added |
| mChildDag.addNode(other); |
| } |
| // Now add the dependency to the graph |
| mChildDag.addEdge(other, view); |
| } |
| } |
| } |
| |
| // Finally add the sorted graph list to our list |
| mDependencySortedChildren.addAll(mChildDag.getSortedList()); |
| // We also need to reverse the result since we want the start of the list to contain |
| // Views which have no dependencies, then dependent views after that |
| Collections.reverse(mDependencySortedChildren); |
| } |
| |
| /** |
| * Retrieve the transformed bounding rect of an arbitrary descendant view. |
| * This does not need to be a direct child. |
| * |
| * @param descendant descendant view to reference |
| * @param out rect to set to the bounds of the descendant view |
| */ |
| void getDescendantRect(View descendant, Rect out) { |
| ViewGroupUtils.getDescendantRect(this, descendant, out); |
| } |
| |
| @Override |
| protected int getSuggestedMinimumWidth() { |
| return Math.max(super.getSuggestedMinimumWidth(), getPaddingLeft() + getPaddingRight()); |
| } |
| |
| @Override |
| protected int getSuggestedMinimumHeight() { |
| return Math.max(super.getSuggestedMinimumHeight(), getPaddingTop() + getPaddingBottom()); |
| } |
| |
| /** |
| * Called to measure each individual child view unless a |
| * {@link Behavior Behavior} is present. The Behavior may choose to delegate |
| * child measurement to this method. |
| * |
| * @param child the child to measure |
| * @param parentWidthMeasureSpec the width requirements for this view |
| * @param widthUsed extra space that has been used up by the parent |
| * horizontally (possibly by other children of the parent) |
| * @param parentHeightMeasureSpec the height requirements for this view |
| * @param heightUsed extra space that has been used up by the parent |
| * vertically (possibly by other children of the parent) |
| */ |
| public void onMeasureChild(View child, int parentWidthMeasureSpec, int widthUsed, |
| int parentHeightMeasureSpec, int heightUsed) { |
| measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, |
| parentHeightMeasureSpec, heightUsed); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| prepareChildren(); |
| ensurePreDrawListener(); |
| |
| final int paddingLeft = getPaddingLeft(); |
| final int paddingTop = getPaddingTop(); |
| final int paddingRight = getPaddingRight(); |
| final int paddingBottom = getPaddingBottom(); |
| final int layoutDirection = ViewCompat.getLayoutDirection(this); |
| final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL; |
| final int widthMode = MeasureSpec.getMode(widthMeasureSpec); |
| final int widthSize = MeasureSpec.getSize(widthMeasureSpec); |
| final int heightMode = MeasureSpec.getMode(heightMeasureSpec); |
| final int heightSize = MeasureSpec.getSize(heightMeasureSpec); |
| |
| final int widthPadding = paddingLeft + paddingRight; |
| final int heightPadding = paddingTop + paddingBottom; |
| int widthUsed = getSuggestedMinimumWidth(); |
| int heightUsed = getSuggestedMinimumHeight(); |
| int childState = 0; |
| |
| final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); |
| |
| final int childCount = mDependencySortedChildren.size(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = mDependencySortedChildren.get(i); |
| if (child.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| |
| int keylineWidthUsed = 0; |
| if (lp.keyline >= 0 && widthMode != MeasureSpec.UNSPECIFIED) { |
| final int keylinePos = getKeyline(lp.keyline); |
| final int keylineGravity = GravityCompat.getAbsoluteGravity( |
| resolveKeylineGravity(lp.gravity), layoutDirection) |
| & Gravity.HORIZONTAL_GRAVITY_MASK; |
| if ((keylineGravity == Gravity.LEFT && !isRtl) |
| || (keylineGravity == Gravity.RIGHT && isRtl)) { |
| keylineWidthUsed = Math.max(0, widthSize - paddingRight - keylinePos); |
| } else if ((keylineGravity == Gravity.RIGHT && !isRtl) |
| || (keylineGravity == Gravity.LEFT && isRtl)) { |
| keylineWidthUsed = Math.max(0, keylinePos - paddingLeft); |
| } |
| } |
| |
| int childWidthMeasureSpec = widthMeasureSpec; |
| int childHeightMeasureSpec = heightMeasureSpec; |
| if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) { |
| // We're set to handle insets but this child isn't, so we will measure the |
| // child as if there are no insets |
| final int horizInsets = mLastInsets.getSystemWindowInsetLeft() |
| + mLastInsets.getSystemWindowInsetRight(); |
| final int vertInsets = mLastInsets.getSystemWindowInsetTop() |
| + mLastInsets.getSystemWindowInsetBottom(); |
| |
| childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| widthSize - horizInsets, widthMode); |
| childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( |
| heightSize - vertInsets, heightMode); |
| } |
| |
| final Behavior b = lp.getBehavior(); |
| if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed, |
| childHeightMeasureSpec, 0)) { |
| onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed, |
| childHeightMeasureSpec, 0); |
| } |
| |
| widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() + |
| lp.leftMargin + lp.rightMargin); |
| |
| heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() + |
| lp.topMargin + lp.bottomMargin); |
| childState = View.combineMeasuredStates(childState, child.getMeasuredState()); |
| } |
| |
| final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec, |
| childState & View.MEASURED_STATE_MASK); |
| final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec, |
| childState << View.MEASURED_HEIGHT_STATE_SHIFT); |
| setMeasuredDimension(width, height); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) { |
| if (insets.isConsumed()) { |
| return insets; |
| } |
| |
| for (int i = 0, z = getChildCount(); i < z; i++) { |
| final View child = getChildAt(i); |
| if (ViewCompat.getFitsSystemWindows(child)) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior b = lp.getBehavior(); |
| |
| if (b != null) { |
| // If the view has a behavior, let it try first |
| insets = b.onApplyWindowInsets(this, child, insets); |
| if (insets.isConsumed()) { |
| // If it consumed the insets, break |
| break; |
| } |
| } |
| } |
| } |
| |
| return insets; |
| } |
| |
| /** |
| * Called to lay out each individual child view unless a |
| * {@link Behavior Behavior} is present. The Behavior may choose to |
| * delegate child measurement to this method. |
| * |
| * @param child child view to lay out |
| * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as |
| * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or |
| * {@link ViewCompat#LAYOUT_DIRECTION_RTL}. |
| */ |
| public void onLayoutChild(@NonNull View child, int layoutDirection) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (lp.checkAnchorChanged()) { |
| throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout" |
| + " measurement begins before layout is complete."); |
| } |
| if (lp.mAnchorView != null) { |
| layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection); |
| } else if (lp.keyline >= 0) { |
| layoutChildWithKeyline(child, lp.keyline, layoutDirection); |
| } else { |
| layoutChild(child, layoutDirection); |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| final int layoutDirection = ViewCompat.getLayoutDirection(this); |
| final int childCount = mDependencySortedChildren.size(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = mDependencySortedChildren.get(i); |
| if (child.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior behavior = lp.getBehavior(); |
| |
| if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) { |
| onLayoutChild(child, layoutDirection); |
| } |
| } |
| } |
| |
| @Override |
| public void onDraw(Canvas c) { |
| super.onDraw(c); |
| if (mDrawStatusBarBackground && mStatusBarBackground != null) { |
| final int inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; |
| if (inset > 0) { |
| mStatusBarBackground.setBounds(0, 0, getWidth(), inset); |
| mStatusBarBackground.draw(c); |
| } |
| } |
| } |
| |
| @Override |
| public void setFitsSystemWindows(boolean fitSystemWindows) { |
| super.setFitsSystemWindows(fitSystemWindows); |
| setupForInsets(); |
| } |
| |
| /** |
| * Mark the last known child position rect for the given child view. |
| * This will be used when checking if a child view's position has changed between frames. |
| * The rect used here should be one returned by |
| * {@link #getChildRect(View, boolean, Rect)}, with translation |
| * disabled. |
| * |
| * @param child child view to set for |
| * @param r rect to set |
| */ |
| void recordLastChildRect(View child, Rect r) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| lp.setLastChildRect(r); |
| } |
| |
| /** |
| * Get the last known child rect recorded by |
| * {@link #recordLastChildRect(View, Rect)}. |
| * |
| * @param child child view to retrieve from |
| * @param out rect to set to the outpur values |
| */ |
| void getLastChildRect(View child, Rect out) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| out.set(lp.getLastChildRect()); |
| } |
| |
| /** |
| * Get the position rect for the given child. If the child has currently requested layout |
| * or has a visibility of GONE. |
| * |
| * @param child child view to check |
| * @param transform true to include transformation in the output rect, false to |
| * only account for the base position |
| * @param out rect to set to the output values |
| */ |
| void getChildRect(View child, boolean transform, Rect out) { |
| if (child.isLayoutRequested() || child.getVisibility() == View.GONE) { |
| out.setEmpty(); |
| return; |
| } |
| if (transform) { |
| getDescendantRect(child, out); |
| } else { |
| out.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); |
| } |
| } |
| |
| private void getDesiredAnchoredChildRectWithoutConstraints(View child, int layoutDirection, |
| Rect anchorRect, Rect out, LayoutParams lp, int childWidth, int childHeight) { |
| final int absGravity = GravityCompat.getAbsoluteGravity( |
| resolveAnchoredChildGravity(lp.gravity), layoutDirection); |
| final int absAnchorGravity = GravityCompat.getAbsoluteGravity( |
| resolveGravity(lp.anchorGravity), |
| layoutDirection); |
| |
| final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK; |
| final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK; |
| final int anchorHgrav = absAnchorGravity & Gravity.HORIZONTAL_GRAVITY_MASK; |
| final int anchorVgrav = absAnchorGravity & Gravity.VERTICAL_GRAVITY_MASK; |
| |
| int left; |
| int top; |
| |
| // Align to the anchor. This puts us in an assumed right/bottom child view gravity. |
| // If this is not the case we will subtract out the appropriate portion of |
| // the child size below. |
| switch (anchorHgrav) { |
| default: |
| case Gravity.LEFT: |
| left = anchorRect.left; |
| break; |
| case Gravity.RIGHT: |
| left = anchorRect.right; |
| break; |
| case Gravity.CENTER_HORIZONTAL: |
| left = anchorRect.left + anchorRect.width() / 2; |
| break; |
| } |
| |
| switch (anchorVgrav) { |
| default: |
| case Gravity.TOP: |
| top = anchorRect.top; |
| break; |
| case Gravity.BOTTOM: |
| top = anchorRect.bottom; |
| break; |
| case Gravity.CENTER_VERTICAL: |
| top = anchorRect.top + anchorRect.height() / 2; |
| break; |
| } |
| |
| // Offset by the child view's gravity itself. The above assumed right/bottom gravity. |
| switch (hgrav) { |
| default: |
| case Gravity.LEFT: |
| left -= childWidth; |
| break; |
| case Gravity.RIGHT: |
| // Do nothing, we're already in position. |
| break; |
| case Gravity.CENTER_HORIZONTAL: |
| left -= childWidth / 2; |
| break; |
| } |
| |
| switch (vgrav) { |
| default: |
| case Gravity.TOP: |
| top -= childHeight; |
| break; |
| case Gravity.BOTTOM: |
| // Do nothing, we're already in position. |
| break; |
| case Gravity.CENTER_VERTICAL: |
| top -= childHeight / 2; |
| break; |
| } |
| |
| out.set(left, top, left + childWidth, top + childHeight); |
| } |
| |
| private void constrainChildRect(LayoutParams lp, Rect out, int childWidth, int childHeight) { |
| final int width = getWidth(); |
| final int height = getHeight(); |
| |
| // Obey margins and padding |
| int left = Math.max(getPaddingLeft() + lp.leftMargin, |
| Math.min(out.left, |
| width - getPaddingRight() - childWidth - lp.rightMargin)); |
| int top = Math.max(getPaddingTop() + lp.topMargin, |
| Math.min(out.top, |
| height - getPaddingBottom() - childHeight - lp.bottomMargin)); |
| |
| out.set(left, top, left + childWidth, top + childHeight); |
| } |
| |
| /** |
| * Calculate the desired child rect relative to an anchor rect, respecting both |
| * gravity and anchorGravity. |
| * |
| * @param child child view to calculate a rect for |
| * @param layoutDirection the desired layout direction for the CoordinatorLayout |
| * @param anchorRect rect in CoordinatorLayout coordinates of the anchor view area |
| * @param out rect to set to the output values |
| */ |
| void getDesiredAnchoredChildRect(View child, int layoutDirection, Rect anchorRect, Rect out) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final int childWidth = child.getMeasuredWidth(); |
| final int childHeight = child.getMeasuredHeight(); |
| getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect, out, lp, |
| childWidth, childHeight); |
| constrainChildRect(lp, out, childWidth, childHeight); |
| } |
| |
| /** |
| * CORE ASSUMPTION: anchor has been laid out by the time this is called for a given child view. |
| * |
| * @param child child to lay out |
| * @param anchor view to anchor child relative to; already laid out. |
| * @param layoutDirection ViewCompat constant for layout direction |
| */ |
| private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) { |
| final Rect anchorRect = acquireTempRect(); |
| final Rect childRect = acquireTempRect(); |
| try { |
| getDescendantRect(anchor, anchorRect); |
| getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect); |
| child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); |
| } finally { |
| releaseTempRect(anchorRect); |
| releaseTempRect(childRect); |
| } |
| } |
| |
| /** |
| * Lay out a child view with respect to a keyline. |
| * |
| * <p>The keyline represents a horizontal offset from the unpadded starting edge of |
| * the CoordinatorLayout. The child's gravity will affect how it is positioned with |
| * respect to the keyline.</p> |
| * |
| * @param child child to lay out |
| * @param keyline offset from the starting edge in pixels of the keyline to align with |
| * @param layoutDirection ViewCompat constant for layout direction |
| */ |
| private void layoutChildWithKeyline(View child, int keyline, int layoutDirection) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final int absGravity = GravityCompat.getAbsoluteGravity( |
| resolveKeylineGravity(lp.gravity), layoutDirection); |
| |
| final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK; |
| final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK; |
| final int width = getWidth(); |
| final int height = getHeight(); |
| final int childWidth = child.getMeasuredWidth(); |
| final int childHeight = child.getMeasuredHeight(); |
| |
| if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) { |
| keyline = width - keyline; |
| } |
| |
| int left = getKeyline(keyline) - childWidth; |
| int top = 0; |
| |
| switch (hgrav) { |
| default: |
| case Gravity.LEFT: |
| // Nothing to do. |
| break; |
| case Gravity.RIGHT: |
| left += childWidth; |
| break; |
| case Gravity.CENTER_HORIZONTAL: |
| left += childWidth / 2; |
| break; |
| } |
| |
| switch (vgrav) { |
| default: |
| case Gravity.TOP: |
| // Do nothing, we're already in position. |
| break; |
| case Gravity.BOTTOM: |
| top += childHeight; |
| break; |
| case Gravity.CENTER_VERTICAL: |
| top += childHeight / 2; |
| break; |
| } |
| |
| // Obey margins and padding |
| left = Math.max(getPaddingLeft() + lp.leftMargin, |
| Math.min(left, |
| width - getPaddingRight() - childWidth - lp.rightMargin)); |
| top = Math.max(getPaddingTop() + lp.topMargin, |
| Math.min(top, |
| height - getPaddingBottom() - childHeight - lp.bottomMargin)); |
| |
| child.layout(left, top, left + childWidth, top + childHeight); |
| } |
| |
| /** |
| * Lay out a child view with no special handling. This will position the child as |
| * if it were within a FrameLayout or similar simple frame. |
| * |
| * @param child child view to lay out |
| * @param layoutDirection ViewCompat constant for the desired layout direction |
| */ |
| private void layoutChild(View child, int layoutDirection) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Rect parent = acquireTempRect(); |
| parent.set(getPaddingLeft() + lp.leftMargin, |
| getPaddingTop() + lp.topMargin, |
| getWidth() - getPaddingRight() - lp.rightMargin, |
| getHeight() - getPaddingBottom() - lp.bottomMargin); |
| |
| if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this) |
| && !ViewCompat.getFitsSystemWindows(child)) { |
| // If we're set to handle insets but this child isn't, then it has been measured as |
| // if there are no insets. We need to lay it out to match. |
| parent.left += mLastInsets.getSystemWindowInsetLeft(); |
| parent.top += mLastInsets.getSystemWindowInsetTop(); |
| parent.right -= mLastInsets.getSystemWindowInsetRight(); |
| parent.bottom -= mLastInsets.getSystemWindowInsetBottom(); |
| } |
| |
| final Rect out = acquireTempRect(); |
| GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), |
| child.getMeasuredHeight(), parent, out, layoutDirection); |
| child.layout(out.left, out.top, out.right, out.bottom); |
| |
| releaseTempRect(parent); |
| releaseTempRect(out); |
| } |
| |
| /** |
| * Return the given gravity value, but if either or both of the axes doesn't have any gravity |
| * specified, the default value (start or top) is specified. This should be used for children |
| * that are not anchored to another view or a keyline. |
| */ |
| private static int resolveGravity(int gravity) { |
| if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) { |
| gravity |= GravityCompat.START; |
| } |
| if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) { |
| gravity |= Gravity.TOP; |
| } |
| return gravity; |
| } |
| |
| /** |
| * Return the given gravity value or the default if the passed value is NO_GRAVITY. |
| * This should be used for children that are positioned relative to a keyline. |
| */ |
| private static int resolveKeylineGravity(int gravity) { |
| return gravity == Gravity.NO_GRAVITY ? GravityCompat.END | Gravity.TOP : gravity; |
| } |
| |
| /** |
| * Return the given gravity value or the default if the passed value is NO_GRAVITY. |
| * This should be used for children that are anchored to another view. |
| */ |
| private static int resolveAnchoredChildGravity(int gravity) { |
| return gravity == Gravity.NO_GRAVITY ? Gravity.CENTER : gravity; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected boolean drawChild(Canvas canvas, View child, long drawingTime) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (lp.mBehavior != null) { |
| final float scrimAlpha = lp.mBehavior.getScrimOpacity(this, child); |
| if (scrimAlpha > 0f) { |
| if (mScrimPaint == null) { |
| mScrimPaint = new Paint(); |
| } |
| mScrimPaint.setColor(lp.mBehavior.getScrimColor(this, child)); |
| mScrimPaint.setAlpha(clamp(Math.round(255 * scrimAlpha), 0, 255)); |
| |
| final int saved = canvas.save(); |
| if (child.isOpaque()) { |
| // If the child is opaque, there is no need to draw behind it so we'll inverse |
| // clip the canvas |
| canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(), |
| child.getBottom(), Region.Op.DIFFERENCE); |
| } |
| // Now draw the rectangle for the scrim |
| canvas.drawRect(getPaddingLeft(), getPaddingTop(), |
| getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(), |
| mScrimPaint); |
| canvas.restoreToCount(saved); |
| } |
| } |
| return super.drawChild(canvas, child, drawingTime); |
| } |
| |
| private static int clamp(int value, int min, int max) { |
| if (value < min) { |
| return min; |
| } else if (value > max) { |
| return max; |
| } |
| return value; |
| } |
| |
| /** |
| * Dispatch any dependent view changes to the relevant {@link Behavior} instances. |
| * |
| * Usually run as part of the pre-draw step when at least one child view has a reported |
| * dependency on another view. This allows CoordinatorLayout to account for layout |
| * changes and animations that occur outside of the normal layout pass. |
| * |
| * It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting |
| * is completed within the correct coordinate window. |
| * |
| * The offsetting behavior implemented here does not store the computed offset in |
| * the LayoutParams; instead it expects that the layout process will always reconstruct |
| * the proper positioning. |
| * |
| * @param type the type of event which has caused this call |
| */ |
| @SuppressWarnings("unchecked") |
| final void onChildViewsChanged(@DispatchChangeEvent final int type) { |
| final int layoutDirection = ViewCompat.getLayoutDirection(this); |
| final int childCount = mDependencySortedChildren.size(); |
| final Rect inset = acquireTempRect(); |
| final Rect drawRect = acquireTempRect(); |
| final Rect lastDrawRect = acquireTempRect(); |
| |
| for (int i = 0; i < childCount; i++) { |
| final View child = mDependencySortedChildren.get(i); |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) { |
| // Do not try to update GONE child views in pre draw updates. |
| continue; |
| } |
| |
| // Check child views before for anchor |
| for (int j = 0; j < i; j++) { |
| final View checkChild = mDependencySortedChildren.get(j); |
| |
| if (lp.mAnchorDirectChild == checkChild) { |
| offsetChildToAnchor(child, layoutDirection); |
| } |
| } |
| |
| // Get the current draw rect of the view |
| getChildRect(child, true, drawRect); |
| |
| // Accumulate inset sizes |
| if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) { |
| final int absInsetEdge = GravityCompat.getAbsoluteGravity( |
| lp.insetEdge, layoutDirection); |
| switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) { |
| case Gravity.TOP: |
| inset.top = Math.max(inset.top, drawRect.bottom); |
| break; |
| case Gravity.BOTTOM: |
| inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top); |
| break; |
| } |
| switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) { |
| case Gravity.LEFT: |
| inset.left = Math.max(inset.left, drawRect.right); |
| break; |
| case Gravity.RIGHT: |
| inset.right = Math.max(inset.right, getWidth() - drawRect.left); |
| break; |
| } |
| } |
| |
| // Dodge inset edges if necessary |
| if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) { |
| offsetChildByInset(child, inset, layoutDirection); |
| } |
| |
| if (type != EVENT_VIEW_REMOVED) { |
| // Did it change? if not continue |
| getLastChildRect(child, lastDrawRect); |
| if (lastDrawRect.equals(drawRect)) { |
| continue; |
| } |
| recordLastChildRect(child, drawRect); |
| } |
| |
| // Update any behavior-dependent views for the change |
| for (int j = i + 1; j < childCount; j++) { |
| final View checkChild = mDependencySortedChildren.get(j); |
| final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); |
| final Behavior b = checkLp.getBehavior(); |
| |
| if (b != null && b.layoutDependsOn(this, checkChild, child)) { |
| if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { |
| // If this is from a pre-draw and we have already been changed |
| // from a nested scroll, skip the dispatch and reset the flag |
| checkLp.resetChangedAfterNestedScroll(); |
| continue; |
| } |
| |
| final boolean handled; |
| switch (type) { |
| case EVENT_VIEW_REMOVED: |
| // EVENT_VIEW_REMOVED means that we need to dispatch |
| // onDependentViewRemoved() instead |
| b.onDependentViewRemoved(this, checkChild, child); |
| handled = true; |
| break; |
| default: |
| // Otherwise we dispatch onDependentViewChanged() |
| handled = b.onDependentViewChanged(this, checkChild, child); |
| break; |
| } |
| |
| if (type == EVENT_NESTED_SCROLL) { |
| // If this is from a nested scroll, set the flag so that we may skip |
| // any resulting onPreDraw dispatch (if needed) |
| checkLp.setChangedAfterNestedScroll(handled); |
| } |
| } |
| } |
| } |
| |
| releaseTempRect(inset); |
| releaseTempRect(drawRect); |
| releaseTempRect(lastDrawRect); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) { |
| if (!ViewCompat.isLaidOut(child)) { |
| // The view has not been laid out yet, so we can't obtain its bounds. |
| return; |
| } |
| |
| if (child.getWidth() <= 0 || child.getHeight() <= 0) { |
| // Bounds are empty so there is nothing to dodge against, skip... |
| return; |
| } |
| |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior behavior = lp.getBehavior(); |
| final Rect dodgeRect = acquireTempRect(); |
| final Rect bounds = acquireTempRect(); |
| bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); |
| |
| if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) { |
| // Make sure that the rect is within the view's bounds |
| if (!bounds.contains(dodgeRect)) { |
| throw new IllegalArgumentException("Rect should be within the child's bounds." |
| + " Rect:" + dodgeRect.toShortString() |
| + " | Bounds:" + bounds.toShortString()); |
| } |
| } else { |
| dodgeRect.set(bounds); |
| } |
| |
| // We can release the bounds rect now |
| releaseTempRect(bounds); |
| |
| if (dodgeRect.isEmpty()) { |
| // Rect is empty so there is nothing to dodge against, skip... |
| releaseTempRect(dodgeRect); |
| return; |
| } |
| |
| final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges, |
| layoutDirection); |
| |
| boolean offsetY = false; |
| if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) { |
| int distance = dodgeRect.top - lp.topMargin - lp.mInsetOffsetY; |
| if (distance < inset.top) { |
| setInsetOffsetY(child, inset.top - distance); |
| offsetY = true; |
| } |
| } |
| if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) { |
| int distance = getHeight() - dodgeRect.bottom - lp.bottomMargin + lp.mInsetOffsetY; |
| if (distance < inset.bottom) { |
| setInsetOffsetY(child, distance - inset.bottom); |
| offsetY = true; |
| } |
| } |
| if (!offsetY) { |
| setInsetOffsetY(child, 0); |
| } |
| |
| boolean offsetX = false; |
| if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) { |
| int distance = dodgeRect.left - lp.leftMargin - lp.mInsetOffsetX; |
| if (distance < inset.left) { |
| setInsetOffsetX(child, inset.left - distance); |
| offsetX = true; |
| } |
| } |
| if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) { |
| int distance = getWidth() - dodgeRect.right - lp.rightMargin + lp.mInsetOffsetX; |
| if (distance < inset.right) { |
| setInsetOffsetX(child, distance - inset.right); |
| offsetX = true; |
| } |
| } |
| if (!offsetX) { |
| setInsetOffsetX(child, 0); |
| } |
| |
| releaseTempRect(dodgeRect); |
| } |
| |
| private void setInsetOffsetX(View child, int offsetX) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (lp.mInsetOffsetX != offsetX) { |
| final int dx = offsetX - lp.mInsetOffsetX; |
| ViewCompat.offsetLeftAndRight(child, dx); |
| lp.mInsetOffsetX = offsetX; |
| } |
| } |
| |
| private void setInsetOffsetY(View child, int offsetY) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (lp.mInsetOffsetY != offsetY) { |
| final int dy = offsetY - lp.mInsetOffsetY; |
| ViewCompat.offsetTopAndBottom(child, dy); |
| lp.mInsetOffsetY = offsetY; |
| } |
| } |
| |
| /** |
| * Allows the caller to manually dispatch |
| * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated |
| * {@link Behavior} instances of views which depend on the provided {@link View}. |
| * |
| * <p>You should not normally need to call this method as the it will be automatically done |
| * when the view has changed. |
| * |
| * @param view the View to find dependents of to dispatch the call. |
| */ |
| @SuppressWarnings("unchecked") |
| public void dispatchDependentViewsChanged(@NonNull View view) { |
| final List<View> dependents = mChildDag.getIncomingEdges(view); |
| if (dependents != null && !dependents.isEmpty()) { |
| for (int i = 0; i < dependents.size(); i++) { |
| final View child = dependents.get(i); |
| LayoutParams lp = (LayoutParams) |
| child.getLayoutParams(); |
| Behavior b = lp.getBehavior(); |
| if (b != null) { |
| b.onDependentViewChanged(this, child, view); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the list of views which the provided view depends on. Do not store this list as its |
| * contents may not be valid beyond the caller. |
| * |
| * @param child the view to find dependencies for. |
| * |
| * @return the list of views which {@code child} depends on. |
| */ |
| @NonNull |
| public List<View> getDependencies(@NonNull View child) { |
| final List<View> dependencies = mChildDag.getOutgoingEdges(child); |
| mTempDependenciesList.clear(); |
| if (dependencies != null) { |
| mTempDependenciesList.addAll(dependencies); |
| } |
| return mTempDependenciesList; |
| } |
| |
| /** |
| * Returns the list of views which depend on the provided view. Do not store this list as its |
| * contents may not be valid beyond the caller. |
| * |
| * @param child the view to find dependents of. |
| * |
| * @return the list of views which depend on {@code child}. |
| */ |
| @NonNull |
| @SuppressWarnings("unchecked") |
| public List<View> getDependents(@NonNull View child) { |
| final List<View> edges = mChildDag.getIncomingEdges(child); |
| mTempDependenciesList.clear(); |
| if (edges != null) { |
| mTempDependenciesList.addAll(edges); |
| } |
| return mTempDependenciesList; |
| } |
| |
| @VisibleForTesting |
| final List<View> getDependencySortedChildren() { |
| prepareChildren(); |
| return Collections.unmodifiableList(mDependencySortedChildren); |
| } |
| |
| /** |
| * Add or remove the pre-draw listener as necessary. |
| */ |
| void ensurePreDrawListener() { |
| boolean hasDependencies = false; |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View child = getChildAt(i); |
| if (hasDependencies(child)) { |
| hasDependencies = true; |
| break; |
| } |
| } |
| |
| if (hasDependencies != mNeedsPreDrawListener) { |
| if (hasDependencies) { |
| addPreDrawListener(); |
| } else { |
| removePreDrawListener(); |
| } |
| } |
| } |
| |
| /** |
| * Check if the given child has any layout dependencies on other child views. |
| */ |
| private boolean hasDependencies(View child) { |
| return mChildDag.hasOutgoingEdges(child); |
| } |
| |
| /** |
| * Add the pre-draw listener if we're attached to a window and mark that we currently |
| * need it when attached. |
| */ |
| void addPreDrawListener() { |
| if (mIsAttachedToWindow) { |
| // Add the listener |
| if (mOnPreDrawListener == null) { |
| mOnPreDrawListener = new OnPreDrawListener(); |
| } |
| final ViewTreeObserver vto = getViewTreeObserver(); |
| vto.addOnPreDrawListener(mOnPreDrawListener); |
| } |
| |
| // Record that we need the listener regardless of whether or not we're attached. |
| // We'll add the real listener when we become attached. |
| mNeedsPreDrawListener = true; |
| } |
| |
| /** |
| * Remove the pre-draw listener if we're attached to a window and mark that we currently |
| * do not need it when attached. |
| */ |
| void removePreDrawListener() { |
| if (mIsAttachedToWindow) { |
| if (mOnPreDrawListener != null) { |
| final ViewTreeObserver vto = getViewTreeObserver(); |
| vto.removeOnPreDrawListener(mOnPreDrawListener); |
| } |
| } |
| mNeedsPreDrawListener = false; |
| } |
| |
| /** |
| * Adjust the child left, top, right, bottom rect to the correct anchor view position, |
| * respecting gravity and anchor gravity. |
| * |
| * Note that child translation properties are ignored in this process, allowing children |
| * to be animated away from their anchor. However, if the anchor view is animated, |
| * the child will be offset to match the anchor's translated position. |
| */ |
| @SuppressWarnings("unchecked") |
| void offsetChildToAnchor(View child, int layoutDirection) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| if (lp.mAnchorView != null) { |
| final Rect anchorRect = acquireTempRect(); |
| final Rect childRect = acquireTempRect(); |
| final Rect desiredChildRect = acquireTempRect(); |
| |
| getDescendantRect(lp.mAnchorView, anchorRect); |
| getChildRect(child, false, childRect); |
| |
| int childWidth = child.getMeasuredWidth(); |
| int childHeight = child.getMeasuredHeight(); |
| getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect, |
| desiredChildRect, lp, childWidth, childHeight); |
| boolean changed = desiredChildRect.left != childRect.left || |
| desiredChildRect.top != childRect.top; |
| constrainChildRect(lp, desiredChildRect, childWidth, childHeight); |
| |
| final int dx = desiredChildRect.left - childRect.left; |
| final int dy = desiredChildRect.top - childRect.top; |
| |
| if (dx != 0) { |
| ViewCompat.offsetLeftAndRight(child, dx); |
| } |
| if (dy != 0) { |
| ViewCompat.offsetTopAndBottom(child, dy); |
| } |
| |
| if (changed) { |
| // If we have needed to move, make sure to notify the child's Behavior |
| final Behavior b = lp.getBehavior(); |
| if (b != null) { |
| b.onDependentViewChanged(this, child, lp.mAnchorView); |
| } |
| } |
| |
| releaseTempRect(anchorRect); |
| releaseTempRect(childRect); |
| releaseTempRect(desiredChildRect); |
| } |
| } |
| |
| /** |
| * Check if a given point in the CoordinatorLayout's coordinates are within the view bounds |
| * of the given direct child view. |
| * |
| * @param child child view to test |
| * @param x X coordinate to test, in the CoordinatorLayout's coordinate system |
| * @param y Y coordinate to test, in the CoordinatorLayout's coordinate system |
| * @return true if the point is within the child view's bounds, false otherwise |
| */ |
| public boolean isPointInChildBounds(@NonNull View child, int x, int y) { |
| final Rect r = acquireTempRect(); |
| getDescendantRect(child, r); |
| try { |
| return r.contains(x, y); |
| } finally { |
| releaseTempRect(r); |
| } |
| } |
| |
| /** |
| * Check whether two views overlap each other. The views need to be descendants of this |
| * {@link CoordinatorLayout} in the view hierarchy. |
| * |
| * @param first first child view to test |
| * @param second second child view to test |
| * @return true if both views are visible and overlap each other |
| */ |
| public boolean doViewsOverlap(@NonNull View first, @NonNull View second) { |
| if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) { |
| final Rect firstRect = acquireTempRect(); |
| getChildRect(first, first.getParent() != this, firstRect); |
| final Rect secondRect = acquireTempRect(); |
| getChildRect(second, second.getParent() != this, secondRect); |
| try { |
| return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom |
| || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top); |
| } finally { |
| releaseTempRect(firstRect); |
| releaseTempRect(secondRect); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public LayoutParams generateLayoutParams(AttributeSet attrs) { |
| return new LayoutParams(getContext(), attrs); |
| } |
| |
| @Override |
| protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { |
| if (p instanceof LayoutParams) { |
| return new LayoutParams((LayoutParams) p); |
| } else if (p instanceof MarginLayoutParams) { |
| return new LayoutParams((MarginLayoutParams) p); |
| } |
| return new LayoutParams(p); |
| } |
| |
| @Override |
| protected LayoutParams generateDefaultLayoutParams() { |
| return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); |
| } |
| |
| @Override |
| protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { |
| return p instanceof LayoutParams && super.checkLayoutParams(p); |
| } |
| |
| @Override |
| public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { |
| return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean onStartNestedScroll(View child, View target, int axes, int type) { |
| boolean handled = false; |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| if (view.getVisibility() == View.GONE) { |
| // If it's GONE, don't dispatch |
| continue; |
| } |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, |
| target, axes, type); |
| handled |= accepted; |
| lp.setNestedScrollAccepted(type, accepted); |
| } else { |
| lp.setNestedScrollAccepted(type, false); |
| } |
| } |
| return handled; |
| } |
| |
| @Override |
| public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { |
| onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes, int type) { |
| mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes, type); |
| mNestedScrollingTarget = target; |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(type)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| viewBehavior.onNestedScrollAccepted(this, view, child, target, |
| nestedScrollAxes, type); |
| } |
| } |
| } |
| |
| @Override |
| public void onStopNestedScroll(View target) { |
| onStopNestedScroll(target, ViewCompat.TYPE_TOUCH); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void onStopNestedScroll(View target, int type) { |
| mNestedScrollingParentHelper.onStopNestedScroll(target, type); |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(type)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| viewBehavior.onStopNestedScroll(this, view, target, type); |
| } |
| lp.resetNestedScroll(type); |
| lp.resetChangedAfterNestedScroll(); |
| } |
| mNestedScrollingTarget = null; |
| } |
| |
| @Override |
| public void onNestedScroll(View target, int dxConsumed, int dyConsumed, |
| int dxUnconsumed, int dyUnconsumed) { |
| onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, |
| ViewCompat.TYPE_TOUCH); |
| } |
| |
| @Override |
| public void onNestedScroll(View target, int dxConsumed, int dyConsumed, |
| int dxUnconsumed, int dyUnconsumed, int type) { |
| onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, |
| ViewCompat.TYPE_TOUCH, mNestedScrollingV2ConsumedCompat); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, |
| int dxUnconsumed, int dyUnconsumed, @ViewCompat.NestedScrollType int type, |
| @NonNull int[] consumed) { |
| final int childCount = getChildCount(); |
| boolean accepted = false; |
| int xConsumed = 0; |
| int yConsumed = 0; |
| |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| if (view.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(type)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| |
| mBehaviorConsumed[0] = 0; |
| mBehaviorConsumed[1] = 0; |
| |
| viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed, |
| dxUnconsumed, dyUnconsumed, type, mBehaviorConsumed); |
| |
| xConsumed = dxUnconsumed > 0 ? Math.max(xConsumed, mBehaviorConsumed[0]) |
| : Math.min(xConsumed, mBehaviorConsumed[0]); |
| yConsumed = dyUnconsumed > 0 ? Math.max(yConsumed, mBehaviorConsumed[1]) |
| : Math.min(yConsumed, mBehaviorConsumed[1]); |
| |
| accepted = true; |
| } |
| } |
| |
| consumed[0] += xConsumed; |
| consumed[1] += yConsumed; |
| |
| if (accepted) { |
| onChildViewsChanged(EVENT_NESTED_SCROLL); |
| } |
| } |
| |
| @Override |
| public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { |
| onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) { |
| int xConsumed = 0; |
| int yConsumed = 0; |
| boolean accepted = false; |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| if (view.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(type)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| mBehaviorConsumed[0] = 0; |
| mBehaviorConsumed[1] = 0; |
| viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mBehaviorConsumed, type); |
| |
| xConsumed = dx > 0 ? Math.max(xConsumed, mBehaviorConsumed[0]) |
| : Math.min(xConsumed, mBehaviorConsumed[0]); |
| yConsumed = dy > 0 ? Math.max(yConsumed, mBehaviorConsumed[1]) |
| : Math.min(yConsumed, mBehaviorConsumed[1]); |
| |
| accepted = true; |
| } |
| } |
| |
| consumed[0] = xConsumed; |
| consumed[1] = yConsumed; |
| |
| if (accepted) { |
| onChildViewsChanged(EVENT_NESTED_SCROLL); |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { |
| boolean handled = false; |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| if (view.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY, |
| consumed); |
| } |
| } |
| if (handled) { |
| onChildViewsChanged(EVENT_NESTED_SCROLL); |
| } |
| return handled; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean onNestedPreFling(View target, float velocityX, float velocityY) { |
| boolean handled = false; |
| |
| final int childCount = getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| final View view = getChildAt(i); |
| if (view.getVisibility() == GONE) { |
| // If the child is GONE, skip... |
| continue; |
| } |
| |
| final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) { |
| continue; |
| } |
| |
| final Behavior viewBehavior = lp.getBehavior(); |
| if (viewBehavior != null) { |
| handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY); |
| } |
| } |
| return handled; |
| } |
| |
| @Override |
| public int getNestedScrollAxes() { |
| return mNestedScrollingParentHelper.getNestedScrollAxes(); |
| } |
| |
| class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener { |
| @Override |
| public boolean onPreDraw() { |
| onChildViewsChanged(EVENT_PRE_DRAW); |
| return true; |
| } |
| } |
| |
| /** |
| * Sorts child views with higher Z values to the beginning of a collection. |
| */ |
| static class ViewElevationComparator implements Comparator<View> { |
| @Override |
| public int compare(View lhs, View rhs) { |
| final float lz = ViewCompat.getZ(lhs); |
| final float rz = ViewCompat.getZ(rhs); |
| if (lz > rz) { |
| return -1; |
| } else if (lz < rz) { |
| return 1; |
| } |
| return 0; |
| } |
| } |
| |
| /** |
| * Defines the default {@link Behavior} of a {@link View} class. |
| * |
| * <p>When writing a custom view, use this annotation to define the default behavior |
| * when used as a direct child of an {@link CoordinatorLayout}. The default behavior |
| * can be overridden using {@link LayoutParams#setBehavior}.</p> |
| * |
| * <p>Example: <code>@DefaultBehavior(MyBehavior.class)</code></p> |
| * @deprecated Use {@link AttachedBehavior} instead |
| */ |
| @Deprecated |
| @Retention(RetentionPolicy.RUNTIME) |
| public @interface DefaultBehavior { |
| Class<? extends Behavior> value(); |
| } |
| |
| /** |
| * Defines the default attached {@link Behavior} of a {@link View} class |
| * |
| * <p>When writing a custom view, implement this interface to return the default behavior |
| * when used as a direct child of an {@link CoordinatorLayout}. The default behavior |
| * can be overridden using {@link LayoutParams#setBehavior}.</p> |
| */ |
| public interface AttachedBehavior { |
| /** |
| * Returns the behavior associated with the matching {@link View} class. |
| * |
| * @return The behavior associated with the matching {@link View} class. Must be |
| * non-null. |
| */ |
| @NonNull Behavior getBehavior(); |
| } |
| |
| /** |
| * Interaction behavior plugin for child views of {@link CoordinatorLayout}. |
| * |
| * <p>A Behavior implements one or more interactions that a user can take on a child view. |
| * These interactions may include drags, swipes, flings, or any other gestures.</p> |
| * |
| * @param <V> The View type that this Behavior operates on |
| */ |
| public static abstract class Behavior<V extends View> { |
| |
| /** |
| * Default constructor for instantiating Behaviors. |
| */ |
| public Behavior() { |
| } |
| |
| /** |
| * Default constructor for inflating Behaviors from layout. The Behavior will have |
| * the opportunity to parse specially defined layout parameters. These parameters will |
| * appear on the child view tag. |
| * |
| * @param context |
| * @param attrs |
| */ |
| public Behavior(Context context, AttributeSet attrs) { |
| } |
| |
| /** |
| * Called when the Behavior has been attached to a LayoutParams instance. |
| * |
| * <p>This will be called after the LayoutParams has been instantiated and can be |
| * modified.</p> |
| * |
| * @param params the LayoutParams instance that this Behavior has been attached to |
| */ |
| public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) { |
| } |
| |
| /** |
| * Called when the Behavior has been detached from its holding LayoutParams instance. |
| * |
| * <p>This will only be called if the Behavior has been explicitly removed from the |
| * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be |
| * called if the associated view is removed from the CoordinatorLayout or similar.</p> |
| */ |
| public void onDetachedFromLayoutParams() { |
| } |
| |
| /** |
| * Respond to CoordinatorLayout touch events before they are dispatched to child views. |
| * |
| * <p>Behaviors can use this to monitor inbound touch events until one decides to |
| * intercept the rest of the event stream to take an action on its associated child view. |
| * This method will return false until it detects the proper intercept conditions, then |
| * return true once those conditions have occurred.</p> |
| * |
| * <p>Once a Behavior intercepts touch events, the rest of the event stream will |
| * be sent to the {@link #onTouchEvent} method.</p> |
| * |
| * <p>This method will be called regardless of the visibility of the associated child |
| * of the behavior. If you only wish to handle touch events when the child is visible, you |
| * should add a check to {@link View#isShown()} on the given child.</p> |
| * |
| * <p>The default implementation of this method always returns false.</p> |
| * |
| * @param parent the parent view currently receiving this touch event |
| * @param child the child view associated with this Behavior |
| * @param ev the MotionEvent describing the touch event being processed |
| * @return true if this Behavior would like to intercept and take over the event stream. |
| * The default always returns false. |
| */ |
| public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull MotionEvent ev) { |
| return false; |
| } |
| |
| /** |
| * Respond to CoordinatorLayout touch events after this Behavior has started |
| * {@link #onInterceptTouchEvent intercepting} them. |
| * |
| * <p>Behaviors may intercept touch events in order to help the CoordinatorLayout |
| * manipulate its child views. For example, a Behavior may allow a user to drag a |
| * UI pane open or closed. This method should perform actual mutations of view |
| * layout state.</p> |
| * |
| * <p>This method will be called regardless of the visibility of the associated child |
| * of the behavior. If you only wish to handle touch events when the child is visible, you |
| * should add a check to {@link View#isShown()} on the given child.</p> |
| * |
| * @param parent the parent view currently receiving this touch event |
| * @param child the child view associated with this Behavior |
| * @param ev the MotionEvent describing the touch event being processed |
| * @return true if this Behavior handled this touch event and would like to continue |
| * receiving events in this stream. The default always returns false. |
| */ |
| public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull MotionEvent ev) { |
| return false; |
| } |
| |
| /** |
| * Supply a scrim color that will be painted behind the associated child view. |
| * |
| * <p>A scrim may be used to indicate that the other elements beneath it are not currently |
| * interactive or actionable, drawing user focus and attention to the views above the scrim. |
| * </p> |
| * |
| * <p>The default implementation returns {@link Color#BLACK}.</p> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view above the scrim |
| * @return the desired scrim color in 0xAARRGGBB format. The default return value is |
| * {@link Color#BLACK}. |
| * @see #getScrimOpacity(CoordinatorLayout, View) |
| */ |
| @ColorInt |
| public int getScrimColor(@NonNull CoordinatorLayout parent, @NonNull V child) { |
| return Color.BLACK; |
| } |
| |
| /** |
| * Determine the current opacity of the scrim behind a given child view |
| * |
| * <p>A scrim may be used to indicate that the other elements beneath it are not currently |
| * interactive or actionable, drawing user focus and attention to the views above the scrim. |
| * </p> |
| * |
| * <p>The default implementation returns 0.0f.</p> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view above the scrim |
| * @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f. |
| */ |
| @FloatRange(from = 0, to = 1) |
| public float getScrimOpacity(@NonNull CoordinatorLayout parent, @NonNull V child) { |
| return 0.f; |
| } |
| |
| /** |
| * Determine whether interaction with views behind the given child in the child order |
| * should be blocked. |
| * |
| * <p>The default implementation returns true if |
| * {@link #getScrimOpacity(CoordinatorLayout, View)} would return > 0.0f.</p> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view to test |
| * @return true if {@link #getScrimOpacity(CoordinatorLayout, View)} would |
| * return > 0.0f. |
| */ |
| public boolean blocksInteractionBelow(@NonNull CoordinatorLayout parent, @NonNull V child) { |
| return getScrimOpacity(parent, child) > 0.f; |
| } |
| |
| /** |
| * Determine whether the supplied child view has another specific sibling view as a |
| * layout dependency. |
| * |
| * <p>This method will be called at least once in response to a layout request. If it |
| * returns true for a given child and dependency view pair, the parent CoordinatorLayout |
| * will:</p> |
| * <ol> |
| * <li>Always lay out this child after the dependent child is laid out, regardless |
| * of child order.</li> |
| * <li>Call {@link #onDependentViewChanged} when the dependency view's layout or |
| * position changes.</li> |
| * </ol> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view to test |
| * @param dependency the proposed dependency of child |
| * @return true if child's layout depends on the proposed dependency's layout, |
| * false otherwise |
| * |
| * @see #onDependentViewChanged(CoordinatorLayout, View, View) |
| */ |
| public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull View dependency) { |
| return false; |
| } |
| |
| /** |
| * Respond to a change in a child's dependent view |
| * |
| * <p>This method is called whenever a dependent view changes in size or position outside |
| * of the standard layout flow. A Behavior may use this method to appropriately update |
| * the child view in response.</p> |
| * |
| * <p>A view's dependency is determined by |
| * {@link #layoutDependsOn(CoordinatorLayout, View, View)} or |
| * if {@code child} has set another view as it's anchor.</p> |
| * |
| * <p>Note that if a Behavior changes the layout of a child via this method, it should |
| * also be able to reconstruct the correct position in |
| * {@link #onLayoutChild(CoordinatorLayout, View, int) onLayoutChild}. |
| * <code>onDependentViewChanged</code> will not be called during normal layout since |
| * the layout of each child view will always happen in dependency order.</p> |
| * |
| * <p>If the Behavior changes the child view's size or position, it should return true. |
| * The default implementation returns false.</p> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view to manipulate |
| * @param dependency the dependent view that changed |
| * @return true if the Behavior changed the child view's size or position, false otherwise |
| */ |
| public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull View dependency) { |
| return false; |
| } |
| |
| /** |
| * Respond to a child's dependent view being removed. |
| * |
| * <p>This method is called after a dependent view has been removed from the parent. |
| * A Behavior may use this method to appropriately update the child view in response.</p> |
| * |
| * <p>A view's dependency is determined by |
| * {@link #layoutDependsOn(CoordinatorLayout, View, View)} or |
| * if {@code child} has set another view as it's anchor.</p> |
| * |
| * @param parent the parent view of the given child |
| * @param child the child view to manipulate |
| * @param dependency the dependent view that has been removed |
| */ |
| public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull View dependency) { |
| } |
| |
| /** |
| * Called when the parent CoordinatorLayout is about to measure the given child view. |
| * |
| * <p>This method can be used to perform custom or modified measurement of a child view |
| * in place of the default child measurement behavior. The Behavior's implementation |
| * can delegate to the standard CoordinatorLayout measurement behavior by calling |
| * {@link CoordinatorLayout#onMeasureChild(View, int, int, int, int) |
| * parent.onMeasureChild}.</p> |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child the child to measure |
| * @param parentWidthMeasureSpec the width requirements for this view |
| * @param widthUsed extra space that has been used up by the parent |
| * horizontally (possibly by other children of the parent) |
| * @param parentHeightMeasureSpec the height requirements for this view |
| * @param heightUsed extra space that has been used up by the parent |
| * vertically (possibly by other children of the parent) |
| * @return true if the Behavior measured the child view, false if the CoordinatorLayout |
| * should perform its default measurement |
| */ |
| public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child, |
| int parentWidthMeasureSpec, int widthUsed, |
| int parentHeightMeasureSpec, int heightUsed) { |
| return false; |
| } |
| |
| /** |
| * Called when the parent CoordinatorLayout is about the lay out the given child view. |
| * |
| * <p>This method can be used to perform custom or modified layout of a child view |
| * in place of the default child layout behavior. The Behavior's implementation can |
| * delegate to the standard CoordinatorLayout measurement behavior by calling |
| * {@link CoordinatorLayout#onLayoutChild(View, int) |
| * parent.onLayoutChild}.</p> |
| * |
| * <p>If a Behavior implements |
| * {@link #onDependentViewChanged(CoordinatorLayout, View, View)} |
| * to change the position of a view in response to a dependent view changing, it |
| * should also implement <code>onLayoutChild</code> in such a way that respects those |
| * dependent views. <code>onLayoutChild</code> will always be called for a dependent view |
| * <em>after</em> its dependency has been laid out.</p> |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child child view to lay out |
| * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as |
| * {@link ViewCompat#LAYOUT_DIRECTION_LTR} or |
| * {@link ViewCompat#LAYOUT_DIRECTION_RTL}. |
| * @return true if the Behavior performed layout of the child view, false to request |
| * default layout behavior |
| */ |
| public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, |
| int layoutDirection) { |
| return false; |
| } |
| |
| // Utility methods for accessing child-specific, behavior-modifiable properties. |
| |
| /** |
| * Associate a Behavior-specific tag object with the given child view. |
| * This object will be stored with the child view's LayoutParams. |
| * |
| * @param child child view to set tag with |
| * @param tag tag object to set |
| */ |
| public static void setTag(@NonNull View child, @Nullable Object tag) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| lp.mBehaviorTag = tag; |
| } |
| |
| /** |
| * Get the behavior-specific tag object with the given child view. |
| * This object is stored with the child view's LayoutParams. |
| * |
| * @param child child view to get tag with |
| * @return the previously stored tag object |
| */ |
| @Nullable |
| public static Object getTag(@NonNull View child) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| return lp.mBehaviorTag; |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onStartNestedScroll(CoordinatorLayout, View, View, View, int, int)}. This |
| * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}. |
| */ |
| @Deprecated |
| public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View directTargetChild, @NonNull View target, |
| @ScrollAxis int axes) { |
| return false; |
| } |
| |
| /** |
| * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll. |
| * |
| * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond |
| * to this event and return true to indicate that the CoordinatorLayout should act as |
| * a nested scrolling parent for this scroll. Only Behaviors that return true from |
| * this method will receive subsequent nested scroll events.</p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param directTargetChild the child view of the CoordinatorLayout that either is or |
| * contains the target of the nested scroll operation |
| * @param target the descendant view of the CoordinatorLayout initiating the nested scroll |
| * @param axes the axes that this nested scroll applies to. See |
| * {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, |
| * {@link ViewCompat#SCROLL_AXIS_VERTICAL} |
| * @param type the type of input which cause this scroll event |
| * @return true if the Behavior wishes to accept this nested scroll |
| * |
| * @see NestedScrollingParent2#onStartNestedScroll(View, View, int, int) |
| */ |
| public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View directTargetChild, @NonNull View target, |
| @ScrollAxis int axes, @NestedScrollType int type) { |
| if (type == ViewCompat.TYPE_TOUCH) { |
| return onStartNestedScroll(coordinatorLayout, child, directTargetChild, |
| target, axes); |
| } |
| return false; |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onNestedScrollAccepted(CoordinatorLayout, View, View, View, int, int)}. This |
| * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}. |
| */ |
| @Deprecated |
| public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View directTargetChild, @NonNull View target, |
| @ScrollAxis int axes) { |
| // Do nothing |
| } |
| |
| /** |
| * Called when a nested scroll has been accepted by the CoordinatorLayout. |
| * |
| * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param directTargetChild the child view of the CoordinatorLayout that either is or |
| * contains the target of the nested scroll operation |
| * @param target the descendant view of the CoordinatorLayout initiating the nested scroll |
| * @param axes the axes that this nested scroll applies to. See |
| * {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, |
| * {@link ViewCompat#SCROLL_AXIS_VERTICAL} |
| * @param type the type of input which cause this scroll event |
| * |
| * @see NestedScrollingParent2#onNestedScrollAccepted(View, View, int, int) |
| */ |
| public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View directTargetChild, @NonNull View target, |
| @ScrollAxis int axes, @NestedScrollType int type) { |
| if (type == ViewCompat.TYPE_TOUCH) { |
| onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, |
| target, axes); |
| } |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onStopNestedScroll(CoordinatorLayout, View, View, int)}. This method will still |
| * continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}. |
| */ |
| @Deprecated |
| public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target) { |
| // Do nothing |
| } |
| |
| /** |
| * Called when a nested scroll has ended. |
| * |
| * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event |
| * sequence. This is a good place to clean up any state related to the nested scroll. |
| * </p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param target the descendant view of the CoordinatorLayout that initiated |
| * the nested scroll |
| * @param type the type of input which cause this scroll event |
| * |
| * @see NestedScrollingParent2#onStopNestedScroll(View, int) |
| */ |
| public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target, @NestedScrollType int type) { |
| if (type == ViewCompat.TYPE_TOUCH) { |
| onStopNestedScroll(coordinatorLayout, child, target); |
| } |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int, int[])}. |
| * This method will still continue to be called if neither |
| * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int, int[])} |
| * nor {@link #onNestedScroll(View, int, int, int, int, int)} are overridden and the type is |
| * {@link ViewCompat#TYPE_TOUCH}. |
| */ |
| @Deprecated |
| public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, |
| @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, |
| int dyUnconsumed) { |
| // Do nothing |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int, int[])}. |
| * This method will still continue to be called if |
| * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int, int[])} |
| * is not overridden. |
| */ |
| @Deprecated |
| public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, |
| @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, |
| int dyUnconsumed, @NestedScrollType int type) { |
| if (type == ViewCompat.TYPE_TOUCH) { |
| onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, |
| dxUnconsumed, dyUnconsumed); |
| } |
| } |
| |
| /** |
| * Called when a nested scroll in progress has updated and the target has scrolled or |
| * attempted to scroll. |
| * |
| * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the |
| * nested scrolling child, with both consumed and unconsumed components of the scroll |
| * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the |
| * same values.</em> |
| * </p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param target the descendant view of the CoordinatorLayout performing the nested scroll |
| * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation |
| * @param dyConsumed vertical pixels consumed by the target's own scrolling operation |
| * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling |
| * operation, but requested by the user |
| * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation, |
| * but requested by the user |
| * @param type the type of input which cause this scroll event |
| * @param consumed output. Upon this method returning, should contain the scroll |
| * distances consumed by this Behavior |
| * |
| * @see NestedScrollingParent3#onNestedScroll(View, int, int, int, int, int, int[]) |
| */ |
| public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, |
| @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, |
| int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed) { |
| // In the case that this nested scrolling v3 version is not implemented, we call the v2 |
| // version in case the v2 version is. We Also consume all of the unconsumed scroll |
| // distances. |
| consumed[0] += dxUnconsumed; |
| consumed[1] += dyUnconsumed; |
| onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, |
| dxUnconsumed, dyUnconsumed, type); |
| } |
| |
| /** |
| * @deprecated You should now override |
| * {@link #onNestedPreScroll(CoordinatorLayout, View, View, int, int, int[], int)}. |
| * This method will still continue to be called if the type is |
| * {@link ViewCompat#TYPE_TOUCH}. |
| */ |
| @Deprecated |
| public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) { |
| // Do nothing |
| } |
| |
| /** |
| * Called when a nested scroll in progress is about to update, before the target has |
| * consumed any of the scrolled distance. |
| * |
| * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated |
| * by the nested scrolling child, before the nested scrolling child has consumed the scroll |
| * distance itself. <em>Each Behavior responding to the nested scroll will receive the |
| * same values.</em> The CoordinatorLayout will report as consumed the maximum number |
| * of pixels in either direction that any Behavior responding to the nested scroll reported |
| * as consumed.</p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param target the descendant view of the CoordinatorLayout performing the nested scroll |
| * @param dx the raw horizontal number of pixels that the user attempted to scroll |
| * @param dy the raw vertical number of pixels that the user attempted to scroll |
| * @param consumed out parameter. consumed[0] should be set to the distance of dx that |
| * was consumed, consumed[1] should be set to the distance of dy that |
| * was consumed |
| * @param type the type of input which cause this scroll event |
| * |
| * @see NestedScrollingParent2#onNestedPreScroll(View, int, int, int[], int) |
| */ |
| public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, |
| @NestedScrollType int type) { |
| if (type == ViewCompat.TYPE_TOUCH) { |
| onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); |
| } |
| } |
| |
| /** |
| * Called when a nested scrolling child is starting a fling or an action that would |
| * be a fling. |
| * |
| * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * <p><code>onNestedFling</code> is called when the current nested scrolling child view |
| * detects the proper conditions for a fling. It reports if the child itself consumed |
| * the fling. If it did not, the child is expected to show some sort of overscroll |
| * indication. This method should return true if it consumes the fling, so that a child |
| * that did not itself take an action in response can choose not to show an overfling |
| * indication.</p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param target the descendant view of the CoordinatorLayout performing the nested scroll |
| * @param velocityX horizontal velocity of the attempted fling |
| * @param velocityY vertical velocity of the attempted fling |
| * @param consumed true if the nested child view consumed the fling |
| * @return true if the Behavior consumed the fling |
| * |
| * @see NestedScrollingParent#onNestedFling(View, float, float, boolean) |
| */ |
| public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target, float velocityX, float velocityY, |
| boolean consumed) { |
| return false; |
| } |
| |
| /** |
| * Called when a nested scrolling child is about to start a fling. |
| * |
| * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect |
| * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior |
| * that returned true will receive subsequent nested scroll events for that nested scroll. |
| * </p> |
| * |
| * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view |
| * detects the proper conditions for a fling, but it has not acted on it yet. A |
| * Behavior can return true to indicate that it consumed the fling. If at least one |
| * Behavior returns true, the fling should not be acted upon by the child.</p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param target the descendant view of the CoordinatorLayout performing the nested scroll |
| * @param velocityX horizontal velocity of the attempted fling |
| * @param velocityY vertical velocity of the attempted fling |
| * @return true if the Behavior consumed the fling |
| * |
| * @see NestedScrollingParent#onNestedPreFling(View, float, float) |
| */ |
| public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull View target, float velocityX, float velocityY) { |
| return false; |
| } |
| |
| /** |
| * Called when the window insets have changed. |
| * |
| * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect |
| * to handle the window inset change on behalf of it's associated view. |
| * </p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param insets the new window insets. |
| * |
| * @return The insets supplied, minus any insets that were consumed |
| */ |
| @NonNull |
| public WindowInsetsCompat onApplyWindowInsets(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull WindowInsetsCompat insets) { |
| return insets; |
| } |
| |
| /** |
| * Called when a child of the view associated with this behavior wants a particular |
| * rectangle to be positioned onto the screen. |
| * |
| * <p>The contract for this method is the same as |
| * {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}.</p> |
| * |
| * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is |
| * associated with |
| * @param rectangle The rectangle which the child wishes to be on the screen |
| * in the child's coordinates |
| * @param immediate true to forbid animated or delayed scrolling, false otherwise |
| * @return true if the Behavior handled the request |
| * @see ViewParent#requestChildRectangleOnScreen(View, Rect, boolean) |
| */ |
| public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordinatorLayout, |
| @NonNull V child, @NonNull Rect rectangle, boolean immediate) { |
| return false; |
| } |
| |
| /** |
| * Hook allowing a behavior to re-apply a representation of its internal state that had |
| * previously been generated by {@link #onSaveInstanceState}. This function will never |
| * be called with a null state. |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child child view to restore from |
| * @param state The frozen state that had previously been returned by |
| * {@link #onSaveInstanceState}. |
| * |
| * @see #onSaveInstanceState() |
| */ |
| public void onRestoreInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull Parcelable state) { |
| // no-op |
| } |
| |
| /** |
| * Hook allowing a behavior to generate a representation of its internal state |
| * that can later be used to create a new instance with that same state. |
| * This state should only contain information that is not persistent or can |
| * not be reconstructed later. |
| * |
| * <p>Behavior state is only saved when both the parent {@link CoordinatorLayout} and |
| * a view using this behavior have valid IDs set.</p> |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child child view to restore from |
| * |
| * @return Returns a Parcelable object containing the behavior's current dynamic |
| * state. |
| * |
| * @see #onRestoreInstanceState(Parcelable) |
| * @see View#onSaveInstanceState() |
| */ |
| @Nullable |
| public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child) { |
| return BaseSavedState.EMPTY_STATE; |
| } |
| |
| /** |
| * Called when a view is set to dodge view insets. |
| * |
| * <p>This method allows a behavior to update the rectangle that should be dodged. |
| * The rectangle should be in the parent's coordinate system and within the child's |
| * bounds. If not, a {@link IllegalArgumentException} is thrown.</p> |
| * |
| * @param parent the CoordinatorLayout parent of the view this Behavior is |
| * associated with |
| * @param child the child view of the CoordinatorLayout this Behavior is associated with |
| * @param rect the rect to update with the dodge rectangle |
| * @return true the rect was updated, false if we should use the child's bounds |
| */ |
| public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child, |
| @NonNull Rect rect) { |
| return false; |
| } |
| } |
| |
| /** |
| * Parameters describing the desired layout for a child of a {@link CoordinatorLayout}. |
| */ |
| public static class LayoutParams extends MarginLayoutParams { |
| /** |
| * A {@link Behavior} that the child view should obey. |
| */ |
| Behavior mBehavior; |
| |
| boolean mBehaviorResolved = false; |
| |
| /** |
| * A {@link Gravity} value describing how this child view should lay out. |
| * If either or both of the axes are not specified, they are treated by CoordinatorLayout |
| * as {@link Gravity#TOP} or {@link GravityCompat#START}. If an |
| * {@link #setAnchorId(int) anchor} is also specified, the gravity describes how this child |
| * view should be positioned relative to its anchored position. |
| */ |
| public int gravity = Gravity.NO_GRAVITY; |
| |
| /** |
| * A {@link Gravity} value describing which edge of a child view's |
| * {@link #getAnchorId() anchor} view the child should position itself relative to. |
| */ |
| public int anchorGravity = Gravity.NO_GRAVITY; |
| |
| /** |
| * The index of the horizontal keyline specified to the parent CoordinatorLayout that this |
| * child should align relative to. If an {@link #setAnchorId(int) anchor} is present the |
| * keyline will be ignored. |
| */ |
| public int keyline = -1; |
| |
| /** |
| * A {@link View#getId() view id} of a descendant view of the CoordinatorLayout that |
| * this child should position relative to. |
| */ |
| int mAnchorId = View.NO_ID; |
| |
| /** |
| * A {@link Gravity} value describing how this child view insets the CoordinatorLayout. |
| * Other child views which are set to dodge the same inset edges will be moved appropriately |
| * so that the views do not overlap. |
| */ |
| public int insetEdge = Gravity.NO_GRAVITY; |
| |
| /** |
| * A {@link Gravity} value describing how this child view dodges any inset child views in |
| * the CoordinatorLayout. Any views which are inset on the same edge as this view is set to |
| * dodge will result in this view being moved so that the views do not overlap. |
| */ |
| public int dodgeInsetEdges = Gravity.NO_GRAVITY; |
| |
| int mInsetOffsetX; |
| int mInsetOffsetY; |
| |
| View mAnchorView; |
| View mAnchorDirectChild; |
| |
| private boolean mDidBlockInteraction; |
| private boolean mDidAcceptNestedScrollTouch; |
| private boolean mDidAcceptNestedScrollNonTouch; |
| private boolean mDidChangeAfterNestedScroll; |
| |
| final Rect mLastChildRect = new Rect(); |
| |
| Object mBehaviorTag; |
| |
| public LayoutParams(int width, int height) { |
| super(width, height); |
| } |
| |
| LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) { |
| super(context, attrs); |
| |
| final TypedArray a = context.obtainStyledAttributes(attrs, |
| R.styleable.CoordinatorLayout_Layout); |
| |
| this.gravity = a.getInteger( |
| R.styleable.CoordinatorLayout_Layout_android_layout_gravity, |
| Gravity.NO_GRAVITY); |
| mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor, |
| View.NO_ID); |
| this.anchorGravity = a.getInteger( |
| R.styleable.CoordinatorLayout_Layout_layout_anchorGravity, |
| Gravity.NO_GRAVITY); |
| |
| this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline, |
| -1); |
| |
| insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0); |
| dodgeInsetEdges = a.getInt( |
| R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0); |
| mBehaviorResolved = a.hasValue( |
| R.styleable.CoordinatorLayout_Layout_layout_behavior); |
| if (mBehaviorResolved) { |
| mBehavior = parseBehavior(context, attrs, a.getString( |
| R.styleable.CoordinatorLayout_Layout_layout_behavior)); |
| } |
| a.recycle(); |
| |
| if (mBehavior != null) { |
| // If we have a Behavior, dispatch that it has been attached |
| mBehavior.onAttachedToLayoutParams(this); |
| } |
| } |
| |
| public LayoutParams(LayoutParams p) { |
| super(p); |
| } |
| |
| public LayoutParams(MarginLayoutParams p) { |
| super(p); |
| } |
| |
| public LayoutParams(ViewGroup.LayoutParams p) { |
| super(p); |
| } |
| |
| /** |
| * Get the id of this view's anchor. |
| * |
| * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor |
| */ |
| @IdRes |
| public int getAnchorId() { |
| return mAnchorId; |
| } |
| |
| /** |
| * Set the id of this view's anchor. |
| * |
| * <p>The view with this id must be a descendant of the CoordinatorLayout containing |
| * the child view this LayoutParams belongs to. It may not be the child view with |
| * this LayoutParams or a descendant of it.</p> |
| * |
| * @param id The {@link View#getId() view id} of the anchor or |
| * {@link View#NO_ID} if there is no anchor |
| */ |
| public void setAnchorId(@IdRes int id) { |
| invalidateAnchor(); |
| mAnchorId = id; |
| } |
| |
| /** |
| * Get the behavior governing the layout and interaction of the child view within |
| * a parent CoordinatorLayout. |
| * |
| * @return The current behavior or null if no behavior is specified |
| */ |
| @Nullable |
| public Behavior getBehavior() { |
| return mBehavior; |
| } |
| |
| /** |
| * Set the behavior governing the layout and interaction of the child view within |
| * a parent CoordinatorLayout. |
| * |
| * <p>Setting a new behavior will remove any currently associated |
| * {@link Behavior#setTag(View, Object) Behavior tag}.</p> |
| * |
| * @param behavior The behavior to set or null for no special behavior |
| */ |
| public void setBehavior(@Nullable Behavior behavior) { |
| if (mBehavior != behavior) { |
| if (mBehavior != null) { |
| // First detach any old behavior |
| mBehavior.onDetachedFromLayoutParams(); |
| } |
| |
| mBehavior = behavior; |
| mBehaviorTag = null; |
| mBehaviorResolved = true; |
| |
| if (behavior != null) { |
| // Now dispatch that the Behavior has been attached |
| behavior.onAttachedToLayoutParams(this); |
| } |
| } |
| } |
| |
| /** |
| * Set the last known position rect for this child view |
| * @param r the rect to set |
| */ |
| void setLastChildRect(Rect r) { |
| mLastChildRect.set(r); |
| } |
| |
| /** |
| * Get the last known position rect for this child view. |
| * Note: do not mutate the result of this call. |
| */ |
| Rect getLastChildRect() { |
| return mLastChildRect; |
| } |
| |
| /** |
| * Returns true if the anchor id changed to another valid view id since the anchor view |
| * was resolved. |
| */ |
| boolean checkAnchorChanged() { |
| return mAnchorView == null && mAnchorId != View.NO_ID; |
| } |
| |
| /** |
| * Returns true if the associated Behavior previously blocked interaction with other views |
| * below the associated child since the touch behavior tracking was last |
| * {@link #resetTouchBehaviorTracking() reset}. |
| * |
| * @see #isBlockingInteractionBelow(CoordinatorLayout, View) |
| */ |
| boolean didBlockInteraction() { |
| if (mBehavior == null) { |
| mDidBlockInteraction = false; |
| } |
| return mDidBlockInteraction; |
| } |
| |
| /** |
| * Check if the associated Behavior wants to block interaction below the given child |
| * view. The given child view should be the child this LayoutParams is associated with. |
| * |
| * <p>Once interaction is blocked, it will remain blocked until touch interaction tracking |
| * is {@link #resetTouchBehaviorTracking() reset}.</p> |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child the child view this LayoutParams is associated with |
| * @return true to block interaction below the given child |
| */ |
| @SuppressWarnings("unchecked") |
| boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) { |
| if (mDidBlockInteraction) { |
| return true; |
| } |
| |
| return mDidBlockInteraction |= mBehavior != null |
| ? mBehavior.blocksInteractionBelow(parent, child) |
| : false; |
| } |
| |
| /** |
| * Reset tracking of Behavior-specific touch interactions. This includes |
| * interaction blocking. |
| * |
| * @see #isBlockingInteractionBelow(CoordinatorLayout, View) |
| * @see #didBlockInteraction() |
| */ |
| void resetTouchBehaviorTracking() { |
| mDidBlockInteraction = false; |
| } |
| |
| void resetNestedScroll(int type) { |
| setNestedScrollAccepted(type, false); |
| } |
| |
| void setNestedScrollAccepted(int type, boolean accept) { |
| switch (type) { |
| case ViewCompat.TYPE_TOUCH: |
| mDidAcceptNestedScrollTouch = accept; |
| break; |
| case ViewCompat.TYPE_NON_TOUCH: |
| mDidAcceptNestedScrollNonTouch = accept; |
| break; |
| } |
| } |
| |
| boolean isNestedScrollAccepted(int type) { |
| switch (type) { |
| case ViewCompat.TYPE_TOUCH: |
| return mDidAcceptNestedScrollTouch; |
| case ViewCompat.TYPE_NON_TOUCH: |
| return mDidAcceptNestedScrollNonTouch; |
| } |
| return false; |
| } |
| |
| boolean getChangedAfterNestedScroll() { |
| return mDidChangeAfterNestedScroll; |
| } |
| |
| void setChangedAfterNestedScroll(boolean changed) { |
| mDidChangeAfterNestedScroll = changed; |
| } |
| |
| void resetChangedAfterNestedScroll() { |
| mDidChangeAfterNestedScroll = false; |
| } |
| |
| /** |
| * Check if an associated child view depends on another child view of the CoordinatorLayout. |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param child the child to check |
| * @param dependency the proposed dependency to check |
| * @return true if child depends on dependency |
| */ |
| @SuppressWarnings("unchecked") |
| boolean dependsOn(CoordinatorLayout parent, View child, View dependency) { |
| return dependency == mAnchorDirectChild |
| || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent)) |
| || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency)); |
| } |
| |
| /** |
| * Invalidate the cached anchor view and direct child ancestor of that anchor. |
| * The anchor will need to be |
| * {@link #findAnchorView(CoordinatorLayout, View) found} before |
| * being used again. |
| */ |
| void invalidateAnchor() { |
| mAnchorView = mAnchorDirectChild = null; |
| } |
| |
| /** |
| * Locate the appropriate anchor view by the current {@link #setAnchorId(int) anchor id} |
| * or return the cached anchor view if already known. |
| * |
| * @param parent the parent CoordinatorLayout |
| * @param forChild the child this LayoutParams is associated with |
| * @return the located descendant anchor view, or null if the anchor id is |
| * {@link View#NO_ID}. |
| */ |
| View findAnchorView(CoordinatorLayout parent, View forChild) { |
| if (mAnchorId == View.NO_ID) { |
| mAnchorView = mAnchorDirectChild = null; |
| return null; |
| } |
| |
| if (mAnchorView == null || !verifyAnchorView(forChild, parent)) { |
| resolveAnchorView(forChild, parent); |
| } |
| return mAnchorView; |
| } |
| |
| /** |
| * Determine the anchor view for the child view this LayoutParams is assigned to. |
| * Assumes mAnchorId is valid. |
| */ |
| private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) { |
| mAnchorView = parent.findViewById(mAnchorId); |
| if (mAnchorView != null) { |
| if (mAnchorView == parent) { |
| if (parent.isInEditMode()) { |
| mAnchorView = mAnchorDirectChild = null; |
| return; |
| } |
| throw new IllegalStateException( |
| "View can not be anchored to the the parent CoordinatorLayout"); |
| } |
| |
| View directChild = mAnchorView; |
| for (ViewParent p = mAnchorView.getParent(); |
| p != parent && p != null; |
| p = p.getParent()) { |
| if (p == forChild) { |
| if (parent.isInEditMode()) { |
| mAnchorView = mAnchorDirectChild = null; |
| return; |
| } |
| throw new IllegalStateException( |
| "Anchor must not be a descendant of the anchored view"); |
| } |
| if (p instanceof View) { |
| directChild = (View) p; |
| } |
| } |
| mAnchorDirectChild = directChild; |
| } else { |
| if (parent.isInEditMode()) { |
| mAnchorView = mAnchorDirectChild = null; |
| return; |
| } |
| throw new IllegalStateException("Could not find CoordinatorLayout descendant view" |
| + " with id " + parent.getResources().getResourceName(mAnchorId) |
| + " to anchor view " + forChild); |
| } |
| } |
| |
| /** |
| * Verify that the previously resolved anchor view is still valid - that it is still |
| * a descendant of the expected parent view, it is not the child this LayoutParams |
| * is assigned to or a descendant of it, and it has the expected id. |
| */ |
| private boolean verifyAnchorView(View forChild, CoordinatorLayout parent) { |
| if (mAnchorView.getId() != mAnchorId) { |
| return false; |
| } |
| |
| View directChild = mAnchorView; |
| for (ViewParent p = mAnchorView.getParent(); |
| p != parent; |
| p = p.getParent()) { |
| if (p == null || p == forChild) { |
| mAnchorView = mAnchorDirectChild = null; |
| return false; |
| } |
| if (p instanceof View) { |
| directChild = (View) p; |
| } |
| } |
| mAnchorDirectChild = directChild; |
| return true; |
| } |
| |
| /** |
| * Checks whether the view with this LayoutParams should dodge the specified view. |
| */ |
| private boolean shouldDodge(View other, int layoutDirection) { |
| LayoutParams lp = (LayoutParams) other.getLayoutParams(); |
| final int absInset = GravityCompat.getAbsoluteGravity(lp.insetEdge, layoutDirection); |
| return absInset != Gravity.NO_GRAVITY && (absInset & |
| GravityCompat.getAbsoluteGravity(dodgeInsetEdges, layoutDirection)) == absInset; |
| } |
| } |
| |
| private class HierarchyChangeListener implements OnHierarchyChangeListener { |
| HierarchyChangeListener() { |
| } |
| |
| @Override |
| public void onChildViewAdded(View parent, View child) { |
| if (mOnHierarchyChangeListener != null) { |
| mOnHierarchyChangeListener.onChildViewAdded(parent, child); |
| } |
| } |
| |
| @Override |
| public void onChildViewRemoved(View parent, View child) { |
| onChildViewsChanged(EVENT_VIEW_REMOVED); |
| |
| if (mOnHierarchyChangeListener != null) { |
| mOnHierarchyChangeListener.onChildViewRemoved(parent, child); |
| } |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected void onRestoreInstanceState(Parcelable state) { |
| if (!(state instanceof SavedState)) { |
| super.onRestoreInstanceState(state); |
| return; |
| } |
| |
| final SavedState ss = (SavedState) state; |
| super.onRestoreInstanceState(ss.getSuperState()); |
| |
| final SparseArray<Parcelable> behaviorStates = ss.behaviorStates; |
| |
| for (int i = 0, count = getChildCount(); i < count; i++) { |
| final View child = getChildAt(i); |
| final int childId = child.getId(); |
| final LayoutParams lp = getResolvedLayoutParams(child); |
| final Behavior b = lp.getBehavior(); |
| |
| if (childId != NO_ID && b != null) { |
| Parcelable savedState = behaviorStates.get(childId); |
| if (savedState != null) { |
| b.onRestoreInstanceState(this, child, savedState); |
| } |
| } |
| } |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected Parcelable onSaveInstanceState() { |
| final SavedState ss = new SavedState(super.onSaveInstanceState()); |
| |
| final SparseArray<Parcelable> behaviorStates = new SparseArray<>(); |
| for (int i = 0, count = getChildCount(); i < count; i++) { |
| final View child = getChildAt(i); |
| final int childId = child.getId(); |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior b = lp.getBehavior(); |
| |
| if (childId != NO_ID && b != null) { |
| // If the child has an ID and a Behavior, let it save some state... |
| Parcelable state = b.onSaveInstanceState(this, child); |
| if (state != null) { |
| behaviorStates.append(childId, state); |
| } |
| } |
| } |
| ss.behaviorStates = behaviorStates; |
| return ss; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { |
| final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| final Behavior behavior = lp.getBehavior(); |
| |
| if (behavior != null |
| && behavior.onRequestChildRectangleOnScreen(this, child, rectangle, immediate)) { |
| return true; |
| } |
| |
| return super.requestChildRectangleOnScreen(child, rectangle, immediate); |
| } |
| |
| private void setupForInsets() { |
| if (Build.VERSION.SDK_INT < 21) { |
| return; |
| } |
| |
| if (ViewCompat.getFitsSystemWindows(this)) { |
| if (mApplyWindowInsetsListener == null) { |
| mApplyWindowInsetsListener = |
| new androidx.core.view.OnApplyWindowInsetsListener() { |
| @Override |
| public WindowInsetsCompat onApplyWindowInsets(View v, |
| WindowInsetsCompat insets) { |
| return setWindowInsets(insets); |
| } |
| }; |
| } |
| // First apply the insets listener |
| ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener); |
| |
| // Now set the sys ui flags to enable us to lay out in the window insets |
| setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
| | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); |
| } else { |
| ViewCompat.setOnApplyWindowInsetsListener(this, null); |
| } |
| } |
| |
| protected static class SavedState extends AbsSavedState { |
| SparseArray<Parcelable> behaviorStates; |
| |
| public SavedState(Parcel source, ClassLoader loader) { |
| super(source, loader); |
| |
| final int size = source.readInt(); |
| |
| final int[] ids = new int[size]; |
| source.readIntArray(ids); |
| |
| final Parcelable[] states = source.readParcelableArray(loader); |
| |
| behaviorStates = new SparseArray<>(size); |
| for (int i = 0; i < size; i++) { |
| behaviorStates.append(ids[i], states[i]); |
| } |
| } |
| |
| public SavedState(Parcelable superState) { |
| super(superState); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| super.writeToParcel(dest, flags); |
| |
| final int size = behaviorStates != null ? behaviorStates.size() : 0; |
| dest.writeInt(size); |
| |
| final int[] ids = new int[size]; |
| final Parcelable[] states = new Parcelable[size]; |
| |
| for (int i = 0; i < size; i++) { |
| ids[i] = behaviorStates.keyAt(i); |
| states[i] = behaviorStates.valueAt(i); |
| } |
| dest.writeIntArray(ids); |
| dest.writeParcelableArray(states, flags); |
| |
| } |
| |
| public static final Creator<SavedState> CREATOR = |
| new ClassLoaderCreator<SavedState>() { |
| @Override |
| public SavedState createFromParcel(Parcel in, ClassLoader loader) { |
| return new SavedState(in, loader); |
| } |
| |
| @Override |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in, null); |
| } |
| |
| @Override |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| } |