CoordinatorLayout.java revision b0ec0584ef4618908ddcc77b1701e5f22c458347
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20import static android.support.design.widget.ViewUtils.objectEquals;
21
22import android.content.Context;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.Rect;
29import android.graphics.Region;
30import android.graphics.drawable.ColorDrawable;
31import android.graphics.drawable.Drawable;
32import android.os.Build;
33import android.os.Parcel;
34import android.os.Parcelable;
35import android.os.SystemClock;
36import android.support.annotation.ColorInt;
37import android.support.annotation.DrawableRes;
38import android.support.annotation.FloatRange;
39import android.support.annotation.IdRes;
40import android.support.annotation.IntDef;
41import android.support.annotation.NonNull;
42import android.support.annotation.Nullable;
43import android.support.annotation.RestrictTo;
44import android.support.annotation.VisibleForTesting;
45import android.support.design.R;
46import android.support.v4.content.ContextCompat;
47import android.support.v4.graphics.drawable.DrawableCompat;
48import android.support.v4.math.MathUtils;
49import android.support.v4.util.Pools;
50import android.support.v4.view.AbsSavedState;
51import android.support.v4.view.GravityCompat;
52import android.support.v4.view.NestedScrollingParent;
53import android.support.v4.view.NestedScrollingParentHelper;
54import android.support.v4.view.ViewCompat;
55import android.support.v4.view.WindowInsetsCompat;
56import android.text.TextUtils;
57import android.util.AttributeSet;
58import android.util.Log;
59import android.util.SparseArray;
60import android.view.Gravity;
61import android.view.MotionEvent;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.ViewParent;
65import android.view.ViewTreeObserver;
66
67import java.lang.annotation.Retention;
68import java.lang.annotation.RetentionPolicy;
69import java.lang.reflect.Constructor;
70import java.util.ArrayList;
71import java.util.Collections;
72import java.util.Comparator;
73import java.util.HashMap;
74import java.util.List;
75import java.util.Map;
76
77/**
78 * CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}.
79 *
80 * <p>CoordinatorLayout is intended for two primary use cases:</p>
81 * <ol>
82 *     <li>As a top-level application decor or chrome layout</li>
83 *     <li>As a container for a specific interaction with one or more child views</li>
84 * </ol>
85 *
86 * <p>By specifying {@link CoordinatorLayout.Behavior Behaviors} for child views of a
87 * CoordinatorLayout you can provide many different interactions within a single parent and those
88 * views can also interact with one another. View classes can specify a default behavior when
89 * used as a child of a CoordinatorLayout using the
90 * {@link CoordinatorLayout.DefaultBehavior DefaultBehavior} annotation.</p>
91 *
92 * <p>Behaviors may be used to implement a variety of interactions and additional layout
93 * modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons
94 * that stick to other elements as they move and animate.</p>
95 *
96 * <p>Children of a CoordinatorLayout may have an
97 * {@link CoordinatorLayout.LayoutParams#setAnchorId(int) anchor}. This view id must correspond
98 * to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself
99 * or a descendant of the anchored child. This can be used to place floating views relative to
100 * other arbitrary content panes.</p>
101 *
102 * <p>Children can specify {@link CoordinatorLayout.LayoutParams#insetEdge} to describe how the
103 * view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by
104 * {@link CoordinatorLayout.LayoutParams#dodgeInsetEdges} will be moved appropriately so that the
105 * views do not overlap.</p>
106 */
107public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
108    static final String TAG = "CoordinatorLayout";
109    static final String WIDGET_PACKAGE_NAME;
110
111    static {
112        final Package pkg = CoordinatorLayout.class.getPackage();
113        WIDGET_PACKAGE_NAME = pkg != null ? pkg.getName() : null;
114    }
115
116    private static final int TYPE_ON_INTERCEPT = 0;
117    private static final int TYPE_ON_TOUCH = 1;
118
119    static {
120        if (Build.VERSION.SDK_INT >= 21) {
121            TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
122        } else {
123            TOP_SORTED_CHILDREN_COMPARATOR = null;
124        }
125    }
126
127    static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
128            Context.class,
129            AttributeSet.class
130    };
131
132    static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
133            new ThreadLocal<>();
134
135    static final int EVENT_PRE_DRAW = 0;
136    static final int EVENT_NESTED_SCROLL = 1;
137    static final int EVENT_VIEW_REMOVED = 2;
138
139    /** @hide */
140    @RestrictTo(LIBRARY_GROUP)
141    @Retention(RetentionPolicy.SOURCE)
142    @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
143    public @interface DispatchChangeEvent {}
144
145    static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
146    private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12);
147
148    @NonNull
149    private static Rect acquireTempRect() {
150        Rect rect = sRectPool.acquire();
151        if (rect == null) {
152            rect = new Rect();
153        }
154        return rect;
155    }
156
157    private static void releaseTempRect(@NonNull Rect rect) {
158        rect.setEmpty();
159        sRectPool.release(rect);
160    }
161
162    private final List<View> mDependencySortedChildren = new ArrayList<>();
163    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
164
165    private final List<View> mTempList1 = new ArrayList<>();
166    private final List<View> mTempDependenciesList = new ArrayList<>();
167    private final int[] mTempIntPair = new int[2];
168    private Paint mScrimPaint;
169
170    private boolean mDisallowInterceptReset;
171
172    private boolean mIsAttachedToWindow;
173
174    private int[] mKeylines;
175
176    private View mBehaviorTouchView;
177    private View mNestedScrollingDirectChild;
178    private View mNestedScrollingTarget;
179
180    private OnPreDrawListener mOnPreDrawListener;
181    private boolean mNeedsPreDrawListener;
182
183    private WindowInsetsCompat mLastInsets;
184    private boolean mDrawStatusBarBackground;
185    private Drawable mStatusBarBackground;
186
187    OnHierarchyChangeListener mOnHierarchyChangeListener;
188    private android.support.v4.view.OnApplyWindowInsetsListener mApplyWindowInsetsListener;
189
190    private final NestedScrollingParentHelper mNestedScrollingParentHelper =
191            new NestedScrollingParentHelper(this);
192
193    public CoordinatorLayout(Context context) {
194        this(context, null);
195    }
196
197    public CoordinatorLayout(Context context, AttributeSet attrs) {
198        this(context, attrs, 0);
199    }
200
201    public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
202        super(context, attrs, defStyleAttr);
203
204        ThemeUtils.checkAppCompatTheme(context);
205
206        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
207                defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
208        final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
209        if (keylineArrayRes != 0) {
210            final Resources res = context.getResources();
211            mKeylines = res.getIntArray(keylineArrayRes);
212            final float density = res.getDisplayMetrics().density;
213            final int count = mKeylines.length;
214            for (int i = 0; i < count; i++) {
215                mKeylines[i] = (int) (mKeylines[i] * density);
216            }
217        }
218        mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
219        a.recycle();
220
221        setupForInsets();
222        super.setOnHierarchyChangeListener(new HierarchyChangeListener());
223    }
224
225    @Override
226    public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
227        mOnHierarchyChangeListener = onHierarchyChangeListener;
228    }
229
230    @Override
231    public void onAttachedToWindow() {
232        super.onAttachedToWindow();
233        resetTouchBehaviors();
234        if (mNeedsPreDrawListener) {
235            if (mOnPreDrawListener == null) {
236                mOnPreDrawListener = new OnPreDrawListener();
237            }
238            final ViewTreeObserver vto = getViewTreeObserver();
239            vto.addOnPreDrawListener(mOnPreDrawListener);
240        }
241        if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
242            // We're set to fitSystemWindows but we haven't had any insets yet...
243            // We should request a new dispatch of window insets
244            ViewCompat.requestApplyInsets(this);
245        }
246        mIsAttachedToWindow = true;
247    }
248
249    @Override
250    public void onDetachedFromWindow() {
251        super.onDetachedFromWindow();
252        resetTouchBehaviors();
253        if (mNeedsPreDrawListener && mOnPreDrawListener != null) {
254            final ViewTreeObserver vto = getViewTreeObserver();
255            vto.removeOnPreDrawListener(mOnPreDrawListener);
256        }
257        if (mNestedScrollingTarget != null) {
258            onStopNestedScroll(mNestedScrollingTarget);
259        }
260        mIsAttachedToWindow = false;
261    }
262
263    /**
264     * Set a drawable to draw in the insets area for the status bar.
265     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
266     *
267     * @param bg Background drawable to draw behind the status bar
268     */
269    public void setStatusBarBackground(@Nullable final Drawable bg) {
270        if (mStatusBarBackground != bg) {
271            if (mStatusBarBackground != null) {
272                mStatusBarBackground.setCallback(null);
273            }
274            mStatusBarBackground = bg != null ? bg.mutate() : null;
275            if (mStatusBarBackground != null) {
276                if (mStatusBarBackground.isStateful()) {
277                    mStatusBarBackground.setState(getDrawableState());
278                }
279                DrawableCompat.setLayoutDirection(mStatusBarBackground,
280                        ViewCompat.getLayoutDirection(this));
281                mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false);
282                mStatusBarBackground.setCallback(this);
283            }
284            ViewCompat.postInvalidateOnAnimation(this);
285        }
286    }
287
288    /**
289     * Gets the drawable used to draw in the insets area for the status bar.
290     *
291     * @return The status bar background drawable, or null if none set
292     */
293    @Nullable
294    public Drawable getStatusBarBackground() {
295        return mStatusBarBackground;
296    }
297
298    @Override
299    protected void drawableStateChanged() {
300        super.drawableStateChanged();
301
302        final int[] state = getDrawableState();
303        boolean changed = false;
304
305        Drawable d = mStatusBarBackground;
306        if (d != null && d.isStateful()) {
307            changed |= d.setState(state);
308        }
309
310        if (changed) {
311            invalidate();
312        }
313    }
314
315    @Override
316    protected boolean verifyDrawable(Drawable who) {
317        return super.verifyDrawable(who) || who == mStatusBarBackground;
318    }
319
320    @Override
321    public void setVisibility(int visibility) {
322        super.setVisibility(visibility);
323
324        final boolean visible = visibility == VISIBLE;
325        if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) {
326            mStatusBarBackground.setVisible(visible, false);
327        }
328    }
329
330    /**
331     * Set a drawable to draw in the insets area for the status bar.
332     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
333     *
334     * @param resId Resource id of a background drawable to draw behind the status bar
335     */
336    public void setStatusBarBackgroundResource(@DrawableRes int resId) {
337        setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
338    }
339
340    /**
341     * Set a drawable to draw in the insets area for the status bar.
342     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
343     *
344     * @param color Color to use as a background drawable to draw behind the status bar
345     *              in 0xAARRGGBB format.
346     */
347    public void setStatusBarBackgroundColor(@ColorInt int color) {
348        setStatusBarBackground(new ColorDrawable(color));
349    }
350
351    final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
352        if (!objectEquals(mLastInsets, insets)) {
353            mLastInsets = insets;
354            mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
355            setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
356
357            // Now dispatch to the Behaviors
358            insets = dispatchApplyWindowInsetsToBehaviors(insets);
359            requestLayout();
360        }
361        return insets;
362    }
363
364    final WindowInsetsCompat getLastWindowInsets() {
365        return mLastInsets;
366    }
367
368    /**
369     * Reset all Behavior-related tracking records either to clean up or in preparation
370     * for a new event stream. This should be called when attached or detached from a window,
371     * in response to an UP or CANCEL event, when intercept is request-disallowed
372     * and similar cases where an event stream in progress will be aborted.
373     */
374    private void resetTouchBehaviors() {
375        if (mBehaviorTouchView != null) {
376            final Behavior b = ((LayoutParams) mBehaviorTouchView.getLayoutParams()).getBehavior();
377            if (b != null) {
378                final long now = SystemClock.uptimeMillis();
379                final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
380                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
381                b.onTouchEvent(this, mBehaviorTouchView, cancelEvent);
382                cancelEvent.recycle();
383            }
384            mBehaviorTouchView = null;
385        }
386
387        final int childCount = getChildCount();
388        for (int i = 0; i < childCount; i++) {
389            final View child = getChildAt(i);
390            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
391            lp.resetTouchBehaviorTracking();
392        }
393        mDisallowInterceptReset = false;
394    }
395
396    /**
397     * Populate a list with the current child views, sorted such that the topmost views
398     * in z-order are at the front of the list. Useful for hit testing and event dispatch.
399     */
400    private void getTopSortedChildren(List<View> out) {
401        out.clear();
402
403        final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
404        final int childCount = getChildCount();
405        for (int i = childCount - 1; i >= 0; i--) {
406            final int childIndex = useCustomOrder ? getChildDrawingOrder(childCount, i) : i;
407            final View child = getChildAt(childIndex);
408            out.add(child);
409        }
410
411        if (TOP_SORTED_CHILDREN_COMPARATOR != null) {
412            Collections.sort(out, TOP_SORTED_CHILDREN_COMPARATOR);
413        }
414    }
415
416    private boolean performIntercept(MotionEvent ev, final int type) {
417        boolean intercepted = false;
418        boolean newBlock = false;
419
420        MotionEvent cancelEvent = null;
421
422        final int action = ev.getActionMasked();
423
424        final List<View> topmostChildList = mTempList1;
425        getTopSortedChildren(topmostChildList);
426
427        // Let topmost child views inspect first
428        final int childCount = topmostChildList.size();
429        for (int i = 0; i < childCount; i++) {
430            final View child = topmostChildList.get(i);
431            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
432            final Behavior b = lp.getBehavior();
433
434            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
435                // Cancel all behaviors beneath the one that intercepted.
436                // If the event is "down" then we don't have anything to cancel yet.
437                if (b != null) {
438                    if (cancelEvent == null) {
439                        final long now = SystemClock.uptimeMillis();
440                        cancelEvent = MotionEvent.obtain(now, now,
441                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
442                    }
443                    switch (type) {
444                        case TYPE_ON_INTERCEPT:
445                            b.onInterceptTouchEvent(this, child, cancelEvent);
446                            break;
447                        case TYPE_ON_TOUCH:
448                            b.onTouchEvent(this, child, cancelEvent);
449                            break;
450                    }
451                }
452                continue;
453            }
454
455            if (!intercepted && b != null) {
456                switch (type) {
457                    case TYPE_ON_INTERCEPT:
458                        intercepted = b.onInterceptTouchEvent(this, child, ev);
459                        break;
460                    case TYPE_ON_TOUCH:
461                        intercepted = b.onTouchEvent(this, child, ev);
462                        break;
463                }
464                if (intercepted) {
465                    mBehaviorTouchView = child;
466                }
467            }
468
469            // Don't keep going if we're not allowing interaction below this.
470            // Setting newBlock will make sure we cancel the rest of the behaviors.
471            final boolean wasBlocking = lp.didBlockInteraction();
472            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
473            newBlock = isBlocking && !wasBlocking;
474            if (isBlocking && !newBlock) {
475                // Stop here since we don't have anything more to cancel - we already did
476                // when the behavior first started blocking things below this point.
477                break;
478            }
479        }
480
481        topmostChildList.clear();
482
483        return intercepted;
484    }
485
486    @Override
487    public boolean onInterceptTouchEvent(MotionEvent ev) {
488        MotionEvent cancelEvent = null;
489
490        final int action = ev.getActionMasked();
491
492        // Make sure we reset in case we had missed a previous important event.
493        if (action == MotionEvent.ACTION_DOWN) {
494            resetTouchBehaviors();
495        }
496
497        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
498
499        if (cancelEvent != null) {
500            cancelEvent.recycle();
501        }
502
503        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
504            resetTouchBehaviors();
505        }
506
507        return intercepted;
508    }
509
510    @Override
511    public boolean onTouchEvent(MotionEvent ev) {
512        boolean handled = false;
513        boolean cancelSuper = false;
514        MotionEvent cancelEvent = null;
515
516        final int action = ev.getActionMasked();
517
518        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
519            // Safe since performIntercept guarantees that
520            // mBehaviorTouchView != null if it returns true
521            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
522            final Behavior b = lp.getBehavior();
523            if (b != null) {
524                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
525            }
526        }
527
528        // Keep the super implementation correct
529        if (mBehaviorTouchView == null) {
530            handled |= super.onTouchEvent(ev);
531        } else if (cancelSuper) {
532            if (cancelEvent == null) {
533                final long now = SystemClock.uptimeMillis();
534                cancelEvent = MotionEvent.obtain(now, now,
535                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
536            }
537            super.onTouchEvent(cancelEvent);
538        }
539
540        if (!handled && action == MotionEvent.ACTION_DOWN) {
541
542        }
543
544        if (cancelEvent != null) {
545            cancelEvent.recycle();
546        }
547
548        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
549            resetTouchBehaviors();
550        }
551
552        return handled;
553    }
554
555    @Override
556    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
557        super.requestDisallowInterceptTouchEvent(disallowIntercept);
558        if (disallowIntercept && !mDisallowInterceptReset) {
559            resetTouchBehaviors();
560            mDisallowInterceptReset = true;
561        }
562    }
563
564    private int getKeyline(int index) {
565        if (mKeylines == null) {
566            Log.e(TAG, "No keylines defined for " + this + " - attempted index lookup " + index);
567            return 0;
568        }
569
570        if (index < 0 || index >= mKeylines.length) {
571            Log.e(TAG, "Keyline index " + index + " out of range for " + this);
572            return 0;
573        }
574
575        return mKeylines[index];
576    }
577
578    static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
579        if (TextUtils.isEmpty(name)) {
580            return null;
581        }
582
583        final String fullName;
584        if (name.startsWith(".")) {
585            // Relative to the app package. Prepend the app package name.
586            fullName = context.getPackageName() + name;
587        } else if (name.indexOf('.') >= 0) {
588            // Fully qualified package name.
589            fullName = name;
590        } else {
591            // Assume stock behavior in this package (if we have one)
592            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
593                    ? (WIDGET_PACKAGE_NAME + '.' + name)
594                    : name;
595        }
596
597        try {
598            Map<String, Constructor<Behavior>> constructors = sConstructors.get();
599            if (constructors == null) {
600                constructors = new HashMap<>();
601                sConstructors.set(constructors);
602            }
603            Constructor<Behavior> c = constructors.get(fullName);
604            if (c == null) {
605                final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
606                        context.getClassLoader());
607                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
608                c.setAccessible(true);
609                constructors.put(fullName, c);
610            }
611            return c.newInstance(context, attrs);
612        } catch (Exception e) {
613            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
614        }
615    }
616
617    LayoutParams getResolvedLayoutParams(View child) {
618        final LayoutParams result = (LayoutParams) child.getLayoutParams();
619        if (!result.mBehaviorResolved) {
620            Class<?> childClass = child.getClass();
621            DefaultBehavior defaultBehavior = null;
622            while (childClass != null &&
623                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
624                childClass = childClass.getSuperclass();
625            }
626            if (defaultBehavior != null) {
627                try {
628                    result.setBehavior(defaultBehavior.value().newInstance());
629                } catch (Exception e) {
630                    Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
631                            " could not be instantiated. Did you forget a default constructor?", e);
632                }
633            }
634            result.mBehaviorResolved = true;
635        }
636        return result;
637    }
638
639    private void prepareChildren() {
640        mDependencySortedChildren.clear();
641        mChildDag.clear();
642
643        for (int i = 0, count = getChildCount(); i < count; i++) {
644            final View view = getChildAt(i);
645
646            final LayoutParams lp = getResolvedLayoutParams(view);
647            lp.findAnchorView(this, view);
648
649            mChildDag.addNode(view);
650
651            // Now iterate again over the other children, adding any dependencies to the graph
652            for (int j = 0; j < count; j++) {
653                if (j == i) {
654                    continue;
655                }
656                final View other = getChildAt(j);
657                final LayoutParams otherLp = getResolvedLayoutParams(other);
658                if (otherLp.dependsOn(this, other, view)) {
659                    if (!mChildDag.contains(other)) {
660                        // Make sure that the other node is added
661                        mChildDag.addNode(other);
662                    }
663                    // Now add the dependency to the graph
664                    mChildDag.addEdge(view, other);
665                }
666            }
667        }
668
669        // Finally add the sorted graph list to our list
670        mDependencySortedChildren.addAll(mChildDag.getSortedList());
671        // We also need to reverse the result since we want the start of the list to contain
672        // Views which have no dependencies, then dependent views after that
673        Collections.reverse(mDependencySortedChildren);
674    }
675
676    /**
677     * Retrieve the transformed bounding rect of an arbitrary descendant view.
678     * This does not need to be a direct child.
679     *
680     * @param descendant descendant view to reference
681     * @param out rect to set to the bounds of the descendant view
682     */
683    void getDescendantRect(View descendant, Rect out) {
684        ViewGroupUtils.getDescendantRect(this, descendant, out);
685    }
686
687    @Override
688    protected int getSuggestedMinimumWidth() {
689        return Math.max(super.getSuggestedMinimumWidth(), getPaddingLeft() + getPaddingRight());
690    }
691
692    @Override
693    protected int getSuggestedMinimumHeight() {
694        return Math.max(super.getSuggestedMinimumHeight(), getPaddingTop() + getPaddingBottom());
695    }
696
697    /**
698     * Called to measure each individual child view unless a
699     * {@link CoordinatorLayout.Behavior Behavior} is present. The Behavior may choose to delegate
700     * child measurement to this method.
701     *
702     * @param child the child to measure
703     * @param parentWidthMeasureSpec the width requirements for this view
704     * @param widthUsed extra space that has been used up by the parent
705     *        horizontally (possibly by other children of the parent)
706     * @param parentHeightMeasureSpec the height requirements for this view
707     * @param heightUsed extra space that has been used up by the parent
708     *        vertically (possibly by other children of the parent)
709     */
710    public void onMeasureChild(View child, int parentWidthMeasureSpec, int widthUsed,
711            int parentHeightMeasureSpec, int heightUsed) {
712        measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
713                parentHeightMeasureSpec, heightUsed);
714    }
715
716    @Override
717    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
718        prepareChildren();
719        ensurePreDrawListener();
720
721        final int paddingLeft = getPaddingLeft();
722        final int paddingTop = getPaddingTop();
723        final int paddingRight = getPaddingRight();
724        final int paddingBottom = getPaddingBottom();
725        final int layoutDirection = ViewCompat.getLayoutDirection(this);
726        final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
727        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
728        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
729        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
730        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
731
732        final int widthPadding = paddingLeft + paddingRight;
733        final int heightPadding = paddingTop + paddingBottom;
734        int widthUsed = getSuggestedMinimumWidth();
735        int heightUsed = getSuggestedMinimumHeight();
736        int childState = 0;
737
738        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
739
740        final int childCount = mDependencySortedChildren.size();
741        for (int i = 0; i < childCount; i++) {
742            final View child = mDependencySortedChildren.get(i);
743            if (child.getVisibility() == GONE) {
744                // If the child is GONE, skip...
745                continue;
746            }
747
748            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
749
750            int keylineWidthUsed = 0;
751            if (lp.keyline >= 0 && widthMode != MeasureSpec.UNSPECIFIED) {
752                final int keylinePos = getKeyline(lp.keyline);
753                final int keylineGravity = GravityCompat.getAbsoluteGravity(
754                        resolveKeylineGravity(lp.gravity), layoutDirection)
755                        & Gravity.HORIZONTAL_GRAVITY_MASK;
756                if ((keylineGravity == Gravity.LEFT && !isRtl)
757                        || (keylineGravity == Gravity.RIGHT && isRtl)) {
758                    keylineWidthUsed = Math.max(0, widthSize - paddingRight - keylinePos);
759                } else if ((keylineGravity == Gravity.RIGHT && !isRtl)
760                        || (keylineGravity == Gravity.LEFT && isRtl)) {
761                    keylineWidthUsed = Math.max(0, keylinePos - paddingLeft);
762                }
763            }
764
765            int childWidthMeasureSpec = widthMeasureSpec;
766            int childHeightMeasureSpec = heightMeasureSpec;
767            if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
768                // We're set to handle insets but this child isn't, so we will measure the
769                // child as if there are no insets
770                final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
771                        + mLastInsets.getSystemWindowInsetRight();
772                final int vertInsets = mLastInsets.getSystemWindowInsetTop()
773                        + mLastInsets.getSystemWindowInsetBottom();
774
775                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
776                        widthSize - horizInsets, widthMode);
777                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
778                        heightSize - vertInsets, heightMode);
779            }
780
781            final Behavior b = lp.getBehavior();
782            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
783                    childHeightMeasureSpec, 0)) {
784                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
785                        childHeightMeasureSpec, 0);
786            }
787
788            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
789                    lp.leftMargin + lp.rightMargin);
790
791            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
792                    lp.topMargin + lp.bottomMargin);
793            childState = View.combineMeasuredStates(childState, child.getMeasuredState());
794        }
795
796        final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
797                childState & View.MEASURED_STATE_MASK);
798        final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
799                childState << View.MEASURED_HEIGHT_STATE_SHIFT);
800        setMeasuredDimension(width, height);
801    }
802
803    private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
804        if (insets.isConsumed()) {
805            return insets;
806        }
807
808        for (int i = 0, z = getChildCount(); i < z; i++) {
809            final View child = getChildAt(i);
810            if (ViewCompat.getFitsSystemWindows(child)) {
811                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
812                final Behavior b = lp.getBehavior();
813
814                if (b != null) {
815                    // If the view has a behavior, let it try first
816                    insets = b.onApplyWindowInsets(this, child, insets);
817                    if (insets.isConsumed()) {
818                        // If it consumed the insets, break
819                        break;
820                    }
821                }
822            }
823        }
824
825        return insets;
826    }
827
828    /**
829     * Called to lay out each individual child view unless a
830     * {@link CoordinatorLayout.Behavior Behavior} is present. The Behavior may choose to
831     * delegate child measurement to this method.
832     *
833     * @param child child view to lay out
834     * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
835     *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
836     *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
837     */
838    public void onLayoutChild(View child, int layoutDirection) {
839        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
840        if (lp.checkAnchorChanged()) {
841            throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
842                    + " measurement begins before layout is complete.");
843        }
844        if (lp.mAnchorView != null) {
845            layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
846        } else if (lp.keyline >= 0) {
847            layoutChildWithKeyline(child, lp.keyline, layoutDirection);
848        } else {
849            layoutChild(child, layoutDirection);
850        }
851    }
852
853    @Override
854    protected void onLayout(boolean changed, int l, int t, int r, int b) {
855        final int layoutDirection = ViewCompat.getLayoutDirection(this);
856        final int childCount = mDependencySortedChildren.size();
857        for (int i = 0; i < childCount; i++) {
858            final View child = mDependencySortedChildren.get(i);
859            if (child.getVisibility() == GONE) {
860                // If the child is GONE, skip...
861                continue;
862            }
863
864            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
865            final Behavior behavior = lp.getBehavior();
866
867            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
868                onLayoutChild(child, layoutDirection);
869            }
870        }
871    }
872
873    @Override
874    public void onDraw(Canvas c) {
875        super.onDraw(c);
876        if (mDrawStatusBarBackground && mStatusBarBackground != null) {
877            final int inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
878            if (inset > 0) {
879                mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
880                mStatusBarBackground.draw(c);
881            }
882        }
883    }
884
885    @Override
886    public void setFitsSystemWindows(boolean fitSystemWindows) {
887        super.setFitsSystemWindows(fitSystemWindows);
888        setupForInsets();
889    }
890
891    /**
892     * Mark the last known child position rect for the given child view.
893     * This will be used when checking if a child view's position has changed between frames.
894     * The rect used here should be one returned by
895     * {@link #getChildRect(android.view.View, boolean, android.graphics.Rect)}, with translation
896     * disabled.
897     *
898     * @param child child view to set for
899     * @param r rect to set
900     */
901    void recordLastChildRect(View child, Rect r) {
902        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
903        lp.setLastChildRect(r);
904    }
905
906    /**
907     * Get the last known child rect recorded by
908     * {@link #recordLastChildRect(android.view.View, android.graphics.Rect)}.
909     *
910     * @param child child view to retrieve from
911     * @param out rect to set to the outpur values
912     */
913    void getLastChildRect(View child, Rect out) {
914        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
915        out.set(lp.getLastChildRect());
916    }
917
918    /**
919     * Get the position rect for the given child. If the child has currently requested layout
920     * or has a visibility of GONE.
921     *
922     * @param child child view to check
923     * @param transform true to include transformation in the output rect, false to
924     *                        only account for the base position
925     * @param out rect to set to the output values
926     */
927    void getChildRect(View child, boolean transform, Rect out) {
928        if (child.isLayoutRequested() || child.getVisibility() == View.GONE) {
929            out.setEmpty();
930            return;
931        }
932        if (transform) {
933            getDescendantRect(child, out);
934        } else {
935            out.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
936        }
937    }
938
939    private void getDesiredAnchoredChildRectWithoutConstraints(View child, int layoutDirection,
940            Rect anchorRect, Rect out, LayoutParams lp, int childWidth, int childHeight) {
941        final int absGravity = GravityCompat.getAbsoluteGravity(
942                resolveAnchoredChildGravity(lp.gravity), layoutDirection);
943        final int absAnchorGravity = GravityCompat.getAbsoluteGravity(
944                resolveGravity(lp.anchorGravity),
945                layoutDirection);
946
947        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
948        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
949        final int anchorHgrav = absAnchorGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
950        final int anchorVgrav = absAnchorGravity & Gravity.VERTICAL_GRAVITY_MASK;
951
952        int left;
953        int top;
954
955        // Align to the anchor. This puts us in an assumed right/bottom child view gravity.
956        // If this is not the case we will subtract out the appropriate portion of
957        // the child size below.
958        switch (anchorHgrav) {
959            default:
960            case Gravity.LEFT:
961                left = anchorRect.left;
962                break;
963            case Gravity.RIGHT:
964                left = anchorRect.right;
965                break;
966            case Gravity.CENTER_HORIZONTAL:
967                left = anchorRect.left + anchorRect.width() / 2;
968                break;
969        }
970
971        switch (anchorVgrav) {
972            default:
973            case Gravity.TOP:
974                top = anchorRect.top;
975                break;
976            case Gravity.BOTTOM:
977                top = anchorRect.bottom;
978                break;
979            case Gravity.CENTER_VERTICAL:
980                top = anchorRect.top + anchorRect.height() / 2;
981                break;
982        }
983
984        // Offset by the child view's gravity itself. The above assumed right/bottom gravity.
985        switch (hgrav) {
986            default:
987            case Gravity.LEFT:
988                left -= childWidth;
989                break;
990            case Gravity.RIGHT:
991                // Do nothing, we're already in position.
992                break;
993            case Gravity.CENTER_HORIZONTAL:
994                left -= childWidth / 2;
995                break;
996        }
997
998        switch (vgrav) {
999            default:
1000            case Gravity.TOP:
1001                top -= childHeight;
1002                break;
1003            case Gravity.BOTTOM:
1004                // Do nothing, we're already in position.
1005                break;
1006            case Gravity.CENTER_VERTICAL:
1007                top -= childHeight / 2;
1008                break;
1009        }
1010
1011        out.set(left, top, left + childWidth, top + childHeight);
1012    }
1013
1014    private void constrainChildRect(LayoutParams lp, Rect out, int childWidth, int childHeight) {
1015        final int width = getWidth();
1016        final int height = getHeight();
1017
1018        // Obey margins and padding
1019        int left = Math.max(getPaddingLeft() + lp.leftMargin,
1020                Math.min(out.left,
1021                        width - getPaddingRight() - childWidth - lp.rightMargin));
1022        int top = Math.max(getPaddingTop() + lp.topMargin,
1023                Math.min(out.top,
1024                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
1025
1026        out.set(left, top, left + childWidth, top + childHeight);
1027    }
1028
1029    /**
1030     * Calculate the desired child rect relative to an anchor rect, respecting both
1031     * gravity and anchorGravity.
1032     *
1033     * @param child child view to calculate a rect for
1034     * @param layoutDirection the desired layout direction for the CoordinatorLayout
1035     * @param anchorRect rect in CoordinatorLayout coordinates of the anchor view area
1036     * @param out rect to set to the output values
1037     */
1038    void getDesiredAnchoredChildRect(View child, int layoutDirection, Rect anchorRect, Rect out) {
1039        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1040        final int childWidth = child.getMeasuredWidth();
1041        final int childHeight = child.getMeasuredHeight();
1042        getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect, out, lp,
1043                childWidth, childHeight);
1044        constrainChildRect(lp, out, childWidth, childHeight);
1045    }
1046
1047    /**
1048     * CORE ASSUMPTION: anchor has been laid out by the time this is called for a given child view.
1049     *
1050     * @param child child to lay out
1051     * @param anchor view to anchor child relative to; already laid out.
1052     * @param layoutDirection ViewCompat constant for layout direction
1053     */
1054    private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
1055        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1056
1057        final Rect anchorRect = acquireTempRect();
1058        final Rect childRect = acquireTempRect();
1059        try {
1060            getDescendantRect(anchor, anchorRect);
1061            getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
1062            child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
1063        } finally {
1064            releaseTempRect(anchorRect);
1065            releaseTempRect(childRect);
1066        }
1067    }
1068
1069    /**
1070     * Lay out a child view with respect to a keyline.
1071     *
1072     * <p>The keyline represents a horizontal offset from the unpadded starting edge of
1073     * the CoordinatorLayout. The child's gravity will affect how it is positioned with
1074     * respect to the keyline.</p>
1075     *
1076     * @param child child to lay out
1077     * @param keyline offset from the starting edge in pixels of the keyline to align with
1078     * @param layoutDirection ViewCompat constant for layout direction
1079     */
1080    private void layoutChildWithKeyline(View child, int keyline, int layoutDirection) {
1081        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1082        final int absGravity = GravityCompat.getAbsoluteGravity(
1083                resolveKeylineGravity(lp.gravity), layoutDirection);
1084
1085        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1086        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
1087        final int width = getWidth();
1088        final int height = getHeight();
1089        final int childWidth = child.getMeasuredWidth();
1090        final int childHeight = child.getMeasuredHeight();
1091
1092        if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
1093            keyline = width - keyline;
1094        }
1095
1096        int left = getKeyline(keyline) - childWidth;
1097        int top = 0;
1098
1099        switch (hgrav) {
1100            default:
1101            case Gravity.LEFT:
1102                // Nothing to do.
1103                break;
1104            case Gravity.RIGHT:
1105                left += childWidth;
1106                break;
1107            case Gravity.CENTER_HORIZONTAL:
1108                left += childWidth / 2;
1109                break;
1110        }
1111
1112        switch (vgrav) {
1113            default:
1114            case Gravity.TOP:
1115                // Do nothing, we're already in position.
1116                break;
1117            case Gravity.BOTTOM:
1118                top += childHeight;
1119                break;
1120            case Gravity.CENTER_VERTICAL:
1121                top += childHeight / 2;
1122                break;
1123        }
1124
1125        // Obey margins and padding
1126        left = Math.max(getPaddingLeft() + lp.leftMargin,
1127                Math.min(left,
1128                        width - getPaddingRight() - childWidth - lp.rightMargin));
1129        top = Math.max(getPaddingTop() + lp.topMargin,
1130                Math.min(top,
1131                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
1132
1133        child.layout(left, top, left + childWidth, top + childHeight);
1134    }
1135
1136    /**
1137     * Lay out a child view with no special handling. This will position the child as
1138     * if it were within a FrameLayout or similar simple frame.
1139     *
1140     * @param child child view to lay out
1141     * @param layoutDirection ViewCompat constant for the desired layout direction
1142     */
1143    private void layoutChild(View child, int layoutDirection) {
1144        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1145        final Rect parent = acquireTempRect();
1146        parent.set(getPaddingLeft() + lp.leftMargin,
1147                getPaddingTop() + lp.topMargin,
1148                getWidth() - getPaddingRight() - lp.rightMargin,
1149                getHeight() - getPaddingBottom() - lp.bottomMargin);
1150
1151        if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
1152                && !ViewCompat.getFitsSystemWindows(child)) {
1153            // If we're set to handle insets but this child isn't, then it has been measured as
1154            // if there are no insets. We need to lay it out to match.
1155            parent.left += mLastInsets.getSystemWindowInsetLeft();
1156            parent.top += mLastInsets.getSystemWindowInsetTop();
1157            parent.right -= mLastInsets.getSystemWindowInsetRight();
1158            parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
1159        }
1160
1161        final Rect out = acquireTempRect();
1162        GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
1163                child.getMeasuredHeight(), parent, out, layoutDirection);
1164        child.layout(out.left, out.top, out.right, out.bottom);
1165
1166        releaseTempRect(parent);
1167        releaseTempRect(out);
1168    }
1169
1170    /**
1171     * Return the given gravity value, but if either or both of the axes doesn't have any gravity
1172     * specified, the default value (start or top) is specified. This should be used for children
1173     * that are not anchored to another view or a keyline.
1174     */
1175    private static int resolveGravity(int gravity) {
1176        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
1177            gravity |= GravityCompat.START;
1178        }
1179        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
1180            gravity |= Gravity.TOP;
1181        }
1182        return gravity;
1183    }
1184
1185    /**
1186     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
1187     * This should be used for children that are positioned relative to a keyline.
1188     */
1189    private static int resolveKeylineGravity(int gravity) {
1190        return gravity == Gravity.NO_GRAVITY ? GravityCompat.END | Gravity.TOP : gravity;
1191    }
1192
1193    /**
1194     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
1195     * This should be used for children that are anchored to another view.
1196     */
1197    private static int resolveAnchoredChildGravity(int gravity) {
1198        return gravity == Gravity.NO_GRAVITY ? Gravity.CENTER : gravity;
1199    }
1200
1201    @Override
1202    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1203        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1204        if (lp.mBehavior != null) {
1205            final float scrimAlpha = lp.mBehavior.getScrimOpacity(this, child);
1206            if (scrimAlpha > 0f) {
1207                if (mScrimPaint == null) {
1208                    mScrimPaint = new Paint();
1209                }
1210                mScrimPaint.setColor(lp.mBehavior.getScrimColor(this, child));
1211                mScrimPaint.setAlpha(MathUtils.clamp(Math.round(255 * scrimAlpha), 0, 255));
1212
1213                final int saved = canvas.save();
1214                if (child.isOpaque()) {
1215                    // If the child is opaque, there is no need to draw behind it so we'll inverse
1216                    // clip the canvas
1217                    canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(),
1218                            child.getBottom(), Region.Op.DIFFERENCE);
1219                }
1220                // Now draw the rectangle for the scrim
1221                canvas.drawRect(getPaddingLeft(), getPaddingTop(),
1222                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(),
1223                        mScrimPaint);
1224                canvas.restoreToCount(saved);
1225            }
1226        }
1227        return super.drawChild(canvas, child, drawingTime);
1228    }
1229
1230    /**
1231     * Dispatch any dependent view changes to the relevant {@link Behavior} instances.
1232     *
1233     * Usually run as part of the pre-draw step when at least one child view has a reported
1234     * dependency on another view. This allows CoordinatorLayout to account for layout
1235     * changes and animations that occur outside of the normal layout pass.
1236     *
1237     * It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting
1238     * is completed within the correct coordinate window.
1239     *
1240     * The offsetting behavior implemented here does not store the computed offset in
1241     * the LayoutParams; instead it expects that the layout process will always reconstruct
1242     * the proper positioning.
1243     *
1244     * @param type the type of event which has caused this call
1245     */
1246    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
1247        final int layoutDirection = ViewCompat.getLayoutDirection(this);
1248        final int childCount = mDependencySortedChildren.size();
1249        final Rect inset = acquireTempRect();
1250        final Rect drawRect = acquireTempRect();
1251        final Rect lastDrawRect = acquireTempRect();
1252
1253        for (int i = 0; i < childCount; i++) {
1254            final View child = mDependencySortedChildren.get(i);
1255            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1256            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
1257                // Do not try to update GONE child views in pre draw updates.
1258                continue;
1259            }
1260
1261            // Check child views before for anchor
1262            for (int j = 0; j < i; j++) {
1263                final View checkChild = mDependencySortedChildren.get(j);
1264
1265                if (lp.mAnchorDirectChild == checkChild) {
1266                    offsetChildToAnchor(child, layoutDirection);
1267                }
1268            }
1269
1270            // Get the current draw rect of the view
1271            getChildRect(child, true, drawRect);
1272
1273            // Accumulate inset sizes
1274            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
1275                final int absInsetEdge = GravityCompat.getAbsoluteGravity(
1276                        lp.insetEdge, layoutDirection);
1277                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
1278                    case Gravity.TOP:
1279                        inset.top = Math.max(inset.top, drawRect.bottom);
1280                        break;
1281                    case Gravity.BOTTOM:
1282                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
1283                        break;
1284                }
1285                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
1286                    case Gravity.LEFT:
1287                        inset.left = Math.max(inset.left, drawRect.right);
1288                        break;
1289                    case Gravity.RIGHT:
1290                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
1291                        break;
1292                }
1293            }
1294
1295            // Dodge inset edges if necessary
1296            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
1297                offsetChildByInset(child, inset, layoutDirection);
1298            }
1299
1300            if (type != EVENT_VIEW_REMOVED) {
1301                // Did it change? if not continue
1302                getLastChildRect(child, lastDrawRect);
1303                if (lastDrawRect.equals(drawRect)) {
1304                    continue;
1305                }
1306                recordLastChildRect(child, drawRect);
1307            }
1308
1309            // Update any behavior-dependent views for the change
1310            for (int j = i + 1; j < childCount; j++) {
1311                final View checkChild = mDependencySortedChildren.get(j);
1312                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
1313                final Behavior b = checkLp.getBehavior();
1314
1315                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
1316                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
1317                        // If this is from a pre-draw and we have already been changed
1318                        // from a nested scroll, skip the dispatch and reset the flag
1319                        checkLp.resetChangedAfterNestedScroll();
1320                        continue;
1321                    }
1322
1323                    final boolean handled;
1324                    switch (type) {
1325                        case EVENT_VIEW_REMOVED:
1326                            // EVENT_VIEW_REMOVED means that we need to dispatch
1327                            // onDependentViewRemoved() instead
1328                            b.onDependentViewRemoved(this, checkChild, child);
1329                            handled = true;
1330                            break;
1331                        default:
1332                            // Otherwise we dispatch onDependentViewChanged()
1333                            handled = b.onDependentViewChanged(this, checkChild, child);
1334                            break;
1335                    }
1336
1337                    if (type == EVENT_NESTED_SCROLL) {
1338                        // If this is from a nested scroll, set the flag so that we may skip
1339                        // any resulting onPreDraw dispatch (if needed)
1340                        checkLp.setChangedAfterNestedScroll(handled);
1341                    }
1342                }
1343            }
1344        }
1345
1346        releaseTempRect(inset);
1347        releaseTempRect(drawRect);
1348        releaseTempRect(lastDrawRect);
1349    }
1350
1351    private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
1352        if (!ViewCompat.isLaidOut(child)) {
1353            // The view has not been laid out yet, so we can't obtain its bounds.
1354            return;
1355        }
1356
1357        if (child.getWidth() <= 0 || child.getHeight() <= 0) {
1358            // Bounds are empty so there is nothing to dodge against, skip...
1359            return;
1360        }
1361
1362        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1363        final Behavior behavior = lp.getBehavior();
1364        final Rect dodgeRect = acquireTempRect();
1365        final Rect bounds = acquireTempRect();
1366        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
1367
1368        if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) {
1369            // Make sure that the rect is within the view's bounds
1370            if (!bounds.contains(dodgeRect)) {
1371                throw new IllegalArgumentException("Rect should be within the child's bounds."
1372                        + " Rect:" + dodgeRect.toShortString()
1373                        + " | Bounds:" + bounds.toShortString());
1374            }
1375        } else {
1376            dodgeRect.set(bounds);
1377        }
1378
1379        // We can release the bounds rect now
1380        releaseTempRect(bounds);
1381
1382        if (dodgeRect.isEmpty()) {
1383            // Rect is empty so there is nothing to dodge against, skip...
1384            releaseTempRect(dodgeRect);
1385            return;
1386        }
1387
1388        final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges,
1389                layoutDirection);
1390
1391        boolean offsetY = false;
1392        if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) {
1393            int distance = dodgeRect.top - lp.topMargin - lp.mInsetOffsetY;
1394            if (distance < inset.top) {
1395                setInsetOffsetY(child, inset.top - distance);
1396                offsetY = true;
1397            }
1398        }
1399        if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) {
1400            int distance = getHeight() - dodgeRect.bottom - lp.bottomMargin + lp.mInsetOffsetY;
1401            if (distance < inset.bottom) {
1402                setInsetOffsetY(child, distance - inset.bottom);
1403                offsetY = true;
1404            }
1405        }
1406        if (!offsetY) {
1407            setInsetOffsetY(child, 0);
1408        }
1409
1410        boolean offsetX = false;
1411        if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) {
1412            int distance = dodgeRect.left - lp.leftMargin - lp.mInsetOffsetX;
1413            if (distance < inset.left) {
1414                setInsetOffsetX(child, inset.left - distance);
1415                offsetX = true;
1416            }
1417        }
1418        if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) {
1419            int distance = getWidth() - dodgeRect.right - lp.rightMargin + lp.mInsetOffsetX;
1420            if (distance < inset.right) {
1421                setInsetOffsetX(child, distance - inset.right);
1422                offsetX = true;
1423            }
1424        }
1425        if (!offsetX) {
1426            setInsetOffsetX(child, 0);
1427        }
1428
1429        releaseTempRect(dodgeRect);
1430    }
1431
1432    private void setInsetOffsetX(View child, int offsetX) {
1433        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1434        if (lp.mInsetOffsetX != offsetX) {
1435            final int dx = offsetX - lp.mInsetOffsetX;
1436            ViewCompat.offsetLeftAndRight(child, dx);
1437            lp.mInsetOffsetX = offsetX;
1438        }
1439    }
1440
1441    private void setInsetOffsetY(View child, int offsetY) {
1442        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1443        if (lp.mInsetOffsetY != offsetY) {
1444            final int dy = offsetY - lp.mInsetOffsetY;
1445            ViewCompat.offsetTopAndBottom(child, dy);
1446            lp.mInsetOffsetY = offsetY;
1447        }
1448    }
1449
1450    /**
1451     * Allows the caller to manually dispatch
1452     * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
1453     * {@link Behavior} instances of views which depend on the provided {@link View}.
1454     *
1455     * <p>You should not normally need to call this method as the it will be automatically done
1456     * when the view has changed.
1457     *
1458     * @param view the View to find dependents of to dispatch the call.
1459     */
1460    public void dispatchDependentViewsChanged(View view) {
1461        final List<View> dependents = mChildDag.getIncomingEdges(view);
1462        if (dependents != null && !dependents.isEmpty()) {
1463            for (int i = 0; i < dependents.size(); i++) {
1464                final View child = dependents.get(i);
1465                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
1466                        child.getLayoutParams();
1467                CoordinatorLayout.Behavior b = lp.getBehavior();
1468                if (b != null) {
1469                    b.onDependentViewChanged(this, child, view);
1470                }
1471            }
1472        }
1473    }
1474
1475    /**
1476     * Returns the list of views which the provided view depends on. Do not store this list as its
1477     * contents may not be valid beyond the caller.
1478     *
1479     * @param child the view to find dependencies for.
1480     *
1481     * @return the list of views which {@code child} depends on.
1482     */
1483    @NonNull
1484    public List<View> getDependencies(@NonNull View child) {
1485        final List<View> dependencies = mChildDag.getOutgoingEdges(child);
1486        mTempDependenciesList.clear();
1487        if (dependencies != null) {
1488            mTempDependenciesList.addAll(dependencies);
1489        }
1490        return mTempDependenciesList;
1491    }
1492
1493    /**
1494     * Returns the list of views which depend on the provided view. Do not store this list as its
1495     * contents may not be valid beyond the caller.
1496     *
1497     * @param child the view to find dependents of.
1498     *
1499     * @return the list of views which depend on {@code child}.
1500     */
1501    @NonNull
1502    public List<View> getDependents(@NonNull View child) {
1503        final List<View> edges = mChildDag.getIncomingEdges(child);
1504        mTempDependenciesList.clear();
1505        if (edges != null) {
1506            mTempDependenciesList.addAll(edges);
1507        }
1508        return mTempDependenciesList;
1509    }
1510
1511    @VisibleForTesting
1512    final List<View> getDependencySortedChildren() {
1513        prepareChildren();
1514        return Collections.unmodifiableList(mDependencySortedChildren);
1515    }
1516
1517    /**
1518     * Add or remove the pre-draw listener as necessary.
1519     */
1520    void ensurePreDrawListener() {
1521        boolean hasDependencies = false;
1522        final int childCount = getChildCount();
1523        for (int i = 0; i < childCount; i++) {
1524            final View child = getChildAt(i);
1525            if (hasDependencies(child)) {
1526                hasDependencies = true;
1527                break;
1528            }
1529        }
1530
1531        if (hasDependencies != mNeedsPreDrawListener) {
1532            if (hasDependencies) {
1533                addPreDrawListener();
1534            } else {
1535                removePreDrawListener();
1536            }
1537        }
1538    }
1539
1540    /**
1541     * Check if the given child has any layout dependencies on other child views.
1542     */
1543    private boolean hasDependencies(View child) {
1544        return mChildDag.hasOutgoingEdges(child);
1545    }
1546
1547    /**
1548     * Add the pre-draw listener if we're attached to a window and mark that we currently
1549     * need it when attached.
1550     */
1551    void addPreDrawListener() {
1552        if (mIsAttachedToWindow) {
1553            // Add the listener
1554            if (mOnPreDrawListener == null) {
1555                mOnPreDrawListener = new OnPreDrawListener();
1556            }
1557            final ViewTreeObserver vto = getViewTreeObserver();
1558            vto.addOnPreDrawListener(mOnPreDrawListener);
1559        }
1560
1561        // Record that we need the listener regardless of whether or not we're attached.
1562        // We'll add the real listener when we become attached.
1563        mNeedsPreDrawListener = true;
1564    }
1565
1566    /**
1567     * Remove the pre-draw listener if we're attached to a window and mark that we currently
1568     * do not need it when attached.
1569     */
1570    void removePreDrawListener() {
1571        if (mIsAttachedToWindow) {
1572            if (mOnPreDrawListener != null) {
1573                final ViewTreeObserver vto = getViewTreeObserver();
1574                vto.removeOnPreDrawListener(mOnPreDrawListener);
1575            }
1576        }
1577        mNeedsPreDrawListener = false;
1578    }
1579
1580    /**
1581     * Adjust the child left, top, right, bottom rect to the correct anchor view position,
1582     * respecting gravity and anchor gravity.
1583     *
1584     * Note that child translation properties are ignored in this process, allowing children
1585     * to be animated away from their anchor. However, if the anchor view is animated,
1586     * the child will be offset to match the anchor's translated position.
1587     */
1588    void offsetChildToAnchor(View child, int layoutDirection) {
1589        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1590        if (lp.mAnchorView != null) {
1591            final Rect anchorRect = acquireTempRect();
1592            final Rect childRect = acquireTempRect();
1593            final Rect desiredChildRect = acquireTempRect();
1594
1595            getDescendantRect(lp.mAnchorView, anchorRect);
1596            getChildRect(child, false, childRect);
1597
1598            int childWidth = child.getMeasuredWidth();
1599            int childHeight = child.getMeasuredHeight();
1600            getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect,
1601                    desiredChildRect, lp, childWidth, childHeight);
1602            boolean changed = desiredChildRect.left != childRect.left ||
1603                    desiredChildRect.top != childRect.top;
1604            constrainChildRect(lp, desiredChildRect, childWidth, childHeight);
1605
1606            final int dx = desiredChildRect.left - childRect.left;
1607            final int dy = desiredChildRect.top - childRect.top;
1608
1609            if (dx != 0) {
1610                ViewCompat.offsetLeftAndRight(child, dx);
1611            }
1612            if (dy != 0) {
1613                ViewCompat.offsetTopAndBottom(child, dy);
1614            }
1615
1616            if (changed) {
1617                // If we have needed to move, make sure to notify the child's Behavior
1618                final Behavior b = lp.getBehavior();
1619                if (b != null) {
1620                    b.onDependentViewChanged(this, child, lp.mAnchorView);
1621                }
1622            }
1623
1624            releaseTempRect(anchorRect);
1625            releaseTempRect(childRect);
1626            releaseTempRect(desiredChildRect);
1627        }
1628    }
1629
1630    /**
1631     * Check if a given point in the CoordinatorLayout's coordinates are within the view bounds
1632     * of the given direct child view.
1633     *
1634     * @param child child view to test
1635     * @param x X coordinate to test, in the CoordinatorLayout's coordinate system
1636     * @param y Y coordinate to test, in the CoordinatorLayout's coordinate system
1637     * @return true if the point is within the child view's bounds, false otherwise
1638     */
1639    public boolean isPointInChildBounds(View child, int x, int y) {
1640        final Rect r = acquireTempRect();
1641        getDescendantRect(child, r);
1642        try {
1643            return r.contains(x, y);
1644        } finally {
1645            releaseTempRect(r);
1646        }
1647    }
1648
1649    /**
1650     * Check whether two views overlap each other. The views need to be descendants of this
1651     * {@link CoordinatorLayout} in the view hierarchy.
1652     *
1653     * @param first first child view to test
1654     * @param second second child view to test
1655     * @return true if both views are visible and overlap each other
1656     */
1657    public boolean doViewsOverlap(View first, View second) {
1658        if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
1659            final Rect firstRect = acquireTempRect();
1660            getChildRect(first, first.getParent() != this, firstRect);
1661            final Rect secondRect = acquireTempRect();
1662            getChildRect(second, second.getParent() != this, secondRect);
1663            try {
1664                return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
1665                        || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
1666            } finally {
1667                releaseTempRect(firstRect);
1668                releaseTempRect(secondRect);
1669            }
1670        }
1671        return false;
1672    }
1673
1674    @Override
1675    public LayoutParams generateLayoutParams(AttributeSet attrs) {
1676        return new LayoutParams(getContext(), attrs);
1677    }
1678
1679    @Override
1680    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1681        if (p instanceof LayoutParams) {
1682            return new LayoutParams((LayoutParams) p);
1683        } else if (p instanceof MarginLayoutParams) {
1684            return new LayoutParams((MarginLayoutParams) p);
1685        }
1686        return new LayoutParams(p);
1687    }
1688
1689    @Override
1690    protected LayoutParams generateDefaultLayoutParams() {
1691        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1692    }
1693
1694    @Override
1695    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1696        return p instanceof LayoutParams && super.checkLayoutParams(p);
1697    }
1698
1699    @Override
1700    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
1701        boolean handled = false;
1702
1703        final int childCount = getChildCount();
1704        for (int i = 0; i < childCount; i++) {
1705            final View view = getChildAt(i);
1706            if (view.getVisibility() == View.GONE) {
1707                // If it's GONE, don't dispatch
1708                continue;
1709            }
1710            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1711            final Behavior viewBehavior = lp.getBehavior();
1712            if (viewBehavior != null) {
1713                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
1714                        nestedScrollAxes);
1715                handled |= accepted;
1716
1717                lp.acceptNestedScroll(accepted);
1718            } else {
1719                lp.acceptNestedScroll(false);
1720            }
1721        }
1722        return handled;
1723    }
1724
1725    @Override
1726    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
1727        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
1728        mNestedScrollingDirectChild = child;
1729        mNestedScrollingTarget = target;
1730
1731        final int childCount = getChildCount();
1732        for (int i = 0; i < childCount; i++) {
1733            final View view = getChildAt(i);
1734            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1735            if (!lp.isNestedScrollAccepted()) {
1736                continue;
1737            }
1738
1739            final Behavior viewBehavior = lp.getBehavior();
1740            if (viewBehavior != null) {
1741                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
1742            }
1743        }
1744    }
1745
1746    @Override
1747    public void onStopNestedScroll(View target) {
1748        mNestedScrollingParentHelper.onStopNestedScroll(target);
1749
1750        final int childCount = getChildCount();
1751        for (int i = 0; i < childCount; i++) {
1752            final View view = getChildAt(i);
1753            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1754            if (!lp.isNestedScrollAccepted()) {
1755                continue;
1756            }
1757
1758            final Behavior viewBehavior = lp.getBehavior();
1759            if (viewBehavior != null) {
1760                viewBehavior.onStopNestedScroll(this, view, target);
1761            }
1762            lp.resetNestedScroll();
1763            lp.resetChangedAfterNestedScroll();
1764        }
1765
1766        mNestedScrollingDirectChild = null;
1767        mNestedScrollingTarget = null;
1768    }
1769
1770    @Override
1771    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
1772            int dxUnconsumed, int dyUnconsumed) {
1773        final int childCount = getChildCount();
1774        boolean accepted = false;
1775
1776        for (int i = 0; i < childCount; i++) {
1777            final View view = getChildAt(i);
1778            if (view.getVisibility() == GONE) {
1779                // If the child is GONE, skip...
1780                continue;
1781            }
1782
1783            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1784            if (!lp.isNestedScrollAccepted()) {
1785                continue;
1786            }
1787
1788            final Behavior viewBehavior = lp.getBehavior();
1789            if (viewBehavior != null) {
1790                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
1791                        dxUnconsumed, dyUnconsumed);
1792                accepted = true;
1793            }
1794        }
1795
1796        if (accepted) {
1797            onChildViewsChanged(EVENT_NESTED_SCROLL);
1798        }
1799    }
1800
1801    @Override
1802    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
1803        int xConsumed = 0;
1804        int yConsumed = 0;
1805        boolean accepted = false;
1806
1807        final int childCount = getChildCount();
1808        for (int i = 0; i < childCount; i++) {
1809            final View view = getChildAt(i);
1810            if (view.getVisibility() == GONE) {
1811                // If the child is GONE, skip...
1812                continue;
1813            }
1814
1815            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1816            if (!lp.isNestedScrollAccepted()) {
1817                continue;
1818            }
1819
1820            final Behavior viewBehavior = lp.getBehavior();
1821            if (viewBehavior != null) {
1822                mTempIntPair[0] = mTempIntPair[1] = 0;
1823                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
1824
1825                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
1826                        : Math.min(xConsumed, mTempIntPair[0]);
1827                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
1828                        : Math.min(yConsumed, mTempIntPair[1]);
1829
1830                accepted = true;
1831            }
1832        }
1833
1834        consumed[0] = xConsumed;
1835        consumed[1] = yConsumed;
1836
1837        if (accepted) {
1838            onChildViewsChanged(EVENT_NESTED_SCROLL);
1839        }
1840    }
1841
1842    @Override
1843    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
1844        boolean handled = false;
1845
1846        final int childCount = getChildCount();
1847        for (int i = 0; i < childCount; i++) {
1848            final View view = getChildAt(i);
1849            if (view.getVisibility() == GONE) {
1850                // If the child is GONE, skip...
1851                continue;
1852            }
1853
1854            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1855            if (!lp.isNestedScrollAccepted()) {
1856                continue;
1857            }
1858
1859            final Behavior viewBehavior = lp.getBehavior();
1860            if (viewBehavior != null) {
1861                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
1862                        consumed);
1863            }
1864        }
1865        if (handled) {
1866            onChildViewsChanged(EVENT_NESTED_SCROLL);
1867        }
1868        return handled;
1869    }
1870
1871    @Override
1872    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
1873        boolean handled = false;
1874
1875        final int childCount = getChildCount();
1876        for (int i = 0; i < childCount; i++) {
1877            final View view = getChildAt(i);
1878            if (view.getVisibility() == GONE) {
1879                // If the child is GONE, skip...
1880                continue;
1881            }
1882
1883            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1884            if (!lp.isNestedScrollAccepted()) {
1885                continue;
1886            }
1887
1888            final Behavior viewBehavior = lp.getBehavior();
1889            if (viewBehavior != null) {
1890                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
1891            }
1892        }
1893        return handled;
1894    }
1895
1896    @Override
1897    public int getNestedScrollAxes() {
1898        return mNestedScrollingParentHelper.getNestedScrollAxes();
1899    }
1900
1901    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1902        @Override
1903        public boolean onPreDraw() {
1904            onChildViewsChanged(EVENT_PRE_DRAW);
1905            return true;
1906        }
1907    }
1908
1909    /**
1910     * Sorts child views with higher Z values to the beginning of a collection.
1911     */
1912    static class ViewElevationComparator implements Comparator<View> {
1913        @Override
1914        public int compare(View lhs, View rhs) {
1915            final float lz = ViewCompat.getZ(lhs);
1916            final float rz = ViewCompat.getZ(rhs);
1917            if (lz > rz) {
1918                return -1;
1919            } else if (lz < rz) {
1920                return 1;
1921            }
1922            return 0;
1923        }
1924    }
1925
1926    /**
1927     * Defines the default {@link Behavior} of a {@link View} class.
1928     *
1929     * <p>When writing a custom view, use this annotation to define the default behavior
1930     * when used as a direct child of an {@link CoordinatorLayout}. The default behavior
1931     * can be overridden using {@link LayoutParams#setBehavior}.</p>
1932     *
1933     * <p>Example: <code>@DefaultBehavior(MyBehavior.class)</code></p>
1934     */
1935    @Retention(RetentionPolicy.RUNTIME)
1936    public @interface DefaultBehavior {
1937        Class<? extends Behavior> value();
1938    }
1939
1940    /**
1941     * Interaction behavior plugin for child views of {@link CoordinatorLayout}.
1942     *
1943     * <p>A Behavior implements one or more interactions that a user can take on a child view.
1944     * These interactions may include drags, swipes, flings, or any other gestures.</p>
1945     *
1946     * @param <V> The View type that this Behavior operates on
1947     */
1948    public static abstract class Behavior<V extends View> {
1949
1950        /**
1951         * Default constructor for instantiating Behaviors.
1952         */
1953        public Behavior() {
1954        }
1955
1956        /**
1957         * Default constructor for inflating Behaviors from layout. The Behavior will have
1958         * the opportunity to parse specially defined layout parameters. These parameters will
1959         * appear on the child view tag.
1960         *
1961         * @param context
1962         * @param attrs
1963         */
1964        public Behavior(Context context, AttributeSet attrs) {
1965        }
1966
1967        /**
1968         * Called when the Behavior has been attached to a LayoutParams instance.
1969         *
1970         * <p>This will be called after the LayoutParams has been instantiated and can be
1971         * modified.</p>
1972         *
1973         * @param params the LayoutParams instance that this Behavior has been attached to
1974         */
1975        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
1976        }
1977
1978        /**
1979         * Called when the Behavior has been detached from its holding LayoutParams instance.
1980         *
1981         * <p>This will only be called if the Behavior has been explicitly removed from the
1982         * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be
1983         * called if the associated view is removed from the CoordinatorLayout or similar.</p>
1984         */
1985        public void onDetachedFromLayoutParams() {
1986        }
1987
1988        /**
1989         * Respond to CoordinatorLayout touch events before they are dispatched to child views.
1990         *
1991         * <p>Behaviors can use this to monitor inbound touch events until one decides to
1992         * intercept the rest of the event stream to take an action on its associated child view.
1993         * This method will return false until it detects the proper intercept conditions, then
1994         * return true once those conditions have occurred.</p>
1995         *
1996         * <p>Once a Behavior intercepts touch events, the rest of the event stream will
1997         * be sent to the {@link #onTouchEvent} method.</p>
1998         *
1999         * <p>This method will be called regardless of the visibility of the associated child
2000         * of the behavior. If you only wish to handle touch events when the child is visible, you
2001         * should add a check to {@link View#isShown()} on the given child.</p>
2002         *
2003         * <p>The default implementation of this method always returns false.</p>
2004         *
2005         * @param parent the parent view currently receiving this touch event
2006         * @param child the child view associated with this Behavior
2007         * @param ev the MotionEvent describing the touch event being processed
2008         * @return true if this Behavior would like to intercept and take over the event stream.
2009         *         The default always returns false.
2010         */
2011        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
2012            return false;
2013        }
2014
2015        /**
2016         * Respond to CoordinatorLayout touch events after this Behavior has started
2017         * {@link #onInterceptTouchEvent intercepting} them.
2018         *
2019         * <p>Behaviors may intercept touch events in order to help the CoordinatorLayout
2020         * manipulate its child views. For example, a Behavior may allow a user to drag a
2021         * UI pane open or closed. This method should perform actual mutations of view
2022         * layout state.</p>
2023         *
2024         * <p>This method will be called regardless of the visibility of the associated child
2025         * of the behavior. If you only wish to handle touch events when the child is visible, you
2026         * should add a check to {@link View#isShown()} on the given child.</p>
2027         *
2028         * @param parent the parent view currently receiving this touch event
2029         * @param child the child view associated with this Behavior
2030         * @param ev the MotionEvent describing the touch event being processed
2031         * @return true if this Behavior handled this touch event and would like to continue
2032         *         receiving events in this stream. The default always returns false.
2033         */
2034        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
2035            return false;
2036        }
2037
2038        /**
2039         * Supply a scrim color that will be painted behind the associated child view.
2040         *
2041         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
2042         * interactive or actionable, drawing user focus and attention to the views above the scrim.
2043         * </p>
2044         *
2045         * <p>The default implementation returns {@link Color#BLACK}.</p>
2046         *
2047         * @param parent the parent view of the given child
2048         * @param child the child view above the scrim
2049         * @return the desired scrim color in 0xAARRGGBB format. The default return value is
2050         *         {@link Color#BLACK}.
2051         * @see #getScrimOpacity(CoordinatorLayout, android.view.View)
2052         */
2053        @ColorInt
2054        public int getScrimColor(CoordinatorLayout parent, V child) {
2055            return Color.BLACK;
2056        }
2057
2058        /**
2059         * Determine the current opacity of the scrim behind a given child view
2060         *
2061         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
2062         * interactive or actionable, drawing user focus and attention to the views above the scrim.
2063         * </p>
2064         *
2065         * <p>The default implementation returns 0.0f.</p>
2066         *
2067         * @param parent the parent view of the given child
2068         * @param child the child view above the scrim
2069         * @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
2070         */
2071        @FloatRange(from = 0, to = 1)
2072        public float getScrimOpacity(CoordinatorLayout parent, V child) {
2073            return 0.f;
2074        }
2075
2076        /**
2077         * Determine whether interaction with views behind the given child in the child order
2078         * should be blocked.
2079         *
2080         * <p>The default implementation returns true if
2081         * {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would return > 0.0f.</p>
2082         *
2083         * @param parent the parent view of the given child
2084         * @param child the child view to test
2085         * @return true if {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would
2086         *         return > 0.0f.
2087         */
2088        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
2089            return getScrimOpacity(parent, child) > 0.f;
2090        }
2091
2092        /**
2093         * Determine whether the supplied child view has another specific sibling view as a
2094         * layout dependency.
2095         *
2096         * <p>This method will be called at least once in response to a layout request. If it
2097         * returns true for a given child and dependency view pair, the parent CoordinatorLayout
2098         * will:</p>
2099         * <ol>
2100         *     <li>Always lay out this child after the dependent child is laid out, regardless
2101         *     of child order.</li>
2102         *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
2103         *     position changes.</li>
2104         * </ol>
2105         *
2106         * @param parent the parent view of the given child
2107         * @param child the child view to test
2108         * @param dependency the proposed dependency of child
2109         * @return true if child's layout depends on the proposed dependency's layout,
2110         *         false otherwise
2111         *
2112         * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
2113         */
2114        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
2115            return false;
2116        }
2117
2118        /**
2119         * Respond to a change in a child's dependent view
2120         *
2121         * <p>This method is called whenever a dependent view changes in size or position outside
2122         * of the standard layout flow. A Behavior may use this method to appropriately update
2123         * the child view in response.</p>
2124         *
2125         * <p>A view's dependency is determined by
2126         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
2127         * if {@code child} has set another view as it's anchor.</p>
2128         *
2129         * <p>Note that if a Behavior changes the layout of a child via this method, it should
2130         * also be able to reconstruct the correct position in
2131         * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}.
2132         * <code>onDependentViewChanged</code> will not be called during normal layout since
2133         * the layout of each child view will always happen in dependency order.</p>
2134         *
2135         * <p>If the Behavior changes the child view's size or position, it should return true.
2136         * The default implementation returns false.</p>
2137         *
2138         * @param parent the parent view of the given child
2139         * @param child the child view to manipulate
2140         * @param dependency the dependent view that changed
2141         * @return true if the Behavior changed the child view's size or position, false otherwise
2142         */
2143        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
2144            return false;
2145        }
2146
2147        /**
2148         * Respond to a child's dependent view being removed.
2149         *
2150         * <p>This method is called after a dependent view has been removed from the parent.
2151         * A Behavior may use this method to appropriately update the child view in response.</p>
2152         *
2153         * <p>A view's dependency is determined by
2154         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
2155         * if {@code child} has set another view as it's anchor.</p>
2156         *
2157         * @param parent the parent view of the given child
2158         * @param child the child view to manipulate
2159         * @param dependency the dependent view that has been removed
2160         */
2161        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
2162        }
2163
2164        /**
2165         * Called when the parent CoordinatorLayout is about to measure the given child view.
2166         *
2167         * <p>This method can be used to perform custom or modified measurement of a child view
2168         * in place of the default child measurement behavior. The Behavior's implementation
2169         * can delegate to the standard CoordinatorLayout measurement behavior by calling
2170         * {@link CoordinatorLayout#onMeasureChild(android.view.View, int, int, int, int)
2171         * parent.onMeasureChild}.</p>
2172         *
2173         * @param parent the parent CoordinatorLayout
2174         * @param child the child to measure
2175         * @param parentWidthMeasureSpec the width requirements for this view
2176         * @param widthUsed extra space that has been used up by the parent
2177         *        horizontally (possibly by other children of the parent)
2178         * @param parentHeightMeasureSpec the height requirements for this view
2179         * @param heightUsed extra space that has been used up by the parent
2180         *        vertically (possibly by other children of the parent)
2181         * @return true if the Behavior measured the child view, false if the CoordinatorLayout
2182         *         should perform its default measurement
2183         */
2184        public boolean onMeasureChild(CoordinatorLayout parent, V child,
2185                int parentWidthMeasureSpec, int widthUsed,
2186                int parentHeightMeasureSpec, int heightUsed) {
2187            return false;
2188        }
2189
2190        /**
2191         * Called when the parent CoordinatorLayout is about the lay out the given child view.
2192         *
2193         * <p>This method can be used to perform custom or modified layout of a child view
2194         * in place of the default child layout behavior. The Behavior's implementation can
2195         * delegate to the standard CoordinatorLayout measurement behavior by calling
2196         * {@link CoordinatorLayout#onLayoutChild(android.view.View, int)
2197         * parent.onLayoutChild}.</p>
2198         *
2199         * <p>If a Behavior implements
2200         * {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)}
2201         * to change the position of a view in response to a dependent view changing, it
2202         * should also implement <code>onLayoutChild</code> in such a way that respects those
2203         * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
2204         * <em>after</em> its dependency has been laid out.</p>
2205         *
2206         * @param parent the parent CoordinatorLayout
2207         * @param child child view to lay out
2208         * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
2209         *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
2210         *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
2211         * @return true if the Behavior performed layout of the child view, false to request
2212         *         default layout behavior
2213         */
2214        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
2215            return false;
2216        }
2217
2218        // Utility methods for accessing child-specific, behavior-modifiable properties.
2219
2220        /**
2221         * Associate a Behavior-specific tag object with the given child view.
2222         * This object will be stored with the child view's LayoutParams.
2223         *
2224         * @param child child view to set tag with
2225         * @param tag tag object to set
2226         */
2227        public static void setTag(View child, Object tag) {
2228            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2229            lp.mBehaviorTag = tag;
2230        }
2231
2232        /**
2233         * Get the behavior-specific tag object with the given child view.
2234         * This object is stored with the child view's LayoutParams.
2235         *
2236         * @param child child view to get tag with
2237         * @return the previously stored tag object
2238         */
2239        public static Object getTag(View child) {
2240            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2241            return lp.mBehaviorTag;
2242        }
2243
2244
2245        /**
2246         * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
2247         *
2248         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
2249         * to this event and return true to indicate that the CoordinatorLayout should act as
2250         * a nested scrolling parent for this scroll. Only Behaviors that return true from
2251         * this method will receive subsequent nested scroll events.</p>
2252         *
2253         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2254         *                          associated with
2255         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2256         * @param directTargetChild the child view of the CoordinatorLayout that either is or
2257         *                          contains the target of the nested scroll operation
2258         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
2259         * @param nestedScrollAxes the axes that this nested scroll applies to. See
2260         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
2261         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
2262         * @return true if the Behavior wishes to accept this nested scroll
2263         *
2264         * @see NestedScrollingParent#onStartNestedScroll(View, View, int)
2265         */
2266        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
2267                V child, View directTargetChild, View target, int nestedScrollAxes) {
2268            return false;
2269        }
2270
2271        /**
2272         * Called when a nested scroll has been accepted by the CoordinatorLayout.
2273         *
2274         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
2275         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2276         * that returned true will receive subsequent nested scroll events for that nested scroll.
2277         * </p>
2278         *
2279         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2280         *                          associated with
2281         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2282         * @param directTargetChild the child view of the CoordinatorLayout that either is or
2283         *                          contains the target of the nested scroll operation
2284         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
2285         * @param nestedScrollAxes the axes that this nested scroll applies to. See
2286         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
2287         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
2288         *
2289         * @see NestedScrollingParent#onNestedScrollAccepted(View, View, int)
2290         */
2291        public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
2292                View directTargetChild, View target, int nestedScrollAxes) {
2293            // Do nothing
2294        }
2295
2296        /**
2297         * Called when a nested scroll has ended.
2298         *
2299         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
2300         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2301         * that returned true will receive subsequent nested scroll events for that nested scroll.
2302         * </p>
2303         *
2304         * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event
2305         * sequence. This is a good place to clean up any state related to the nested scroll.
2306         * </p>
2307         *
2308         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2309         *                          associated with
2310         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2311         * @param target the descendant view of the CoordinatorLayout that initiated
2312         *               the nested scroll
2313         *
2314         * @see NestedScrollingParent#onStopNestedScroll(View)
2315         */
2316        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
2317            // Do nothing
2318        }
2319
2320        /**
2321         * Called when a nested scroll in progress has updated and the target has scrolled or
2322         * attempted to scroll.
2323         *
2324         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2325         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2326         * that returned true will receive subsequent nested scroll events for that nested scroll.
2327         * </p>
2328         *
2329         * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
2330         * nested scrolling child, with both consumed and unconsumed components of the scroll
2331         * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
2332         * same values.</em>
2333         * </p>
2334         *
2335         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2336         *                          associated with
2337         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2338         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2339         * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
2340         * @param dyConsumed vertical pixels consumed by the target's own scrolling operation
2341         * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
2342         *                     operation, but requested by the user
2343         * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
2344         *                     but requested by the user
2345         *
2346         * @see NestedScrollingParent#onNestedScroll(View, int, int, int, int)
2347         */
2348        public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
2349                int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
2350            // Do nothing
2351        }
2352
2353        /**
2354         * Called when a nested scroll in progress is about to update, before the target has
2355         * consumed any of the scrolled distance.
2356         *
2357         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2358         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2359         * that returned true will receive subsequent nested scroll events for that nested scroll.
2360         * </p>
2361         *
2362         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
2363         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
2364         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
2365         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
2366         * of pixels in either direction that any Behavior responding to the nested scroll reported
2367         * as consumed.</p>
2368         *
2369         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2370         *                          associated with
2371         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2372         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2373         * @param dx the raw horizontal number of pixels that the user attempted to scroll
2374         * @param dy the raw vertical number of pixels that the user attempted to scroll
2375         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
2376         *                 was consumed, consumed[1] should be set to the distance of dy that
2377         *                 was consumed
2378         *
2379         * @see NestedScrollingParent#onNestedPreScroll(View, int, int, int[])
2380         */
2381        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
2382                int dx, int dy, int[] consumed) {
2383            // Do nothing
2384        }
2385
2386        /**
2387         * Called when a nested scrolling child is starting a fling or an action that would
2388         * be a fling.
2389         *
2390         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2391         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2392         * that returned true will receive subsequent nested scroll events for that nested scroll.
2393         * </p>
2394         *
2395         * <p><code>onNestedFling</code> is called when the current nested scrolling child view
2396         * detects the proper conditions for a fling. It reports if the child itself consumed
2397         * the fling. If it did not, the child is expected to show some sort of overscroll
2398         * indication. This method should return true if it consumes the fling, so that a child
2399         * that did not itself take an action in response can choose not to show an overfling
2400         * indication.</p>
2401         *
2402         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2403         *                          associated with
2404         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2405         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2406         * @param velocityX horizontal velocity of the attempted fling
2407         * @param velocityY vertical velocity of the attempted fling
2408         * @param consumed true if the nested child view consumed the fling
2409         * @return true if the Behavior consumed the fling
2410         *
2411         * @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
2412         */
2413        public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
2414                float velocityX, float velocityY, boolean consumed) {
2415            return false;
2416        }
2417
2418        /**
2419         * Called when a nested scrolling child is about to start a fling.
2420         *
2421         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2422         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2423         * that returned true will receive subsequent nested scroll events for that nested scroll.
2424         * </p>
2425         *
2426         * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
2427         * detects the proper conditions for a fling, but it has not acted on it yet. A
2428         * Behavior can return true to indicate that it consumed the fling. If at least one
2429         * Behavior returns true, the fling should not be acted upon by the child.</p>
2430         *
2431         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2432         *                          associated with
2433         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2434         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2435         * @param velocityX horizontal velocity of the attempted fling
2436         * @param velocityY vertical velocity of the attempted fling
2437         * @return true if the Behavior consumed the fling
2438         *
2439         * @see NestedScrollingParent#onNestedPreFling(View, float, float)
2440         */
2441        public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
2442                float velocityX, float velocityY) {
2443            return false;
2444        }
2445
2446        /**
2447         * Called when the window insets have changed.
2448         *
2449         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2450         * to handle the window inset change on behalf of it's associated view.
2451         * </p>
2452         *
2453         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2454         *                          associated with
2455         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2456         * @param insets the new window insets.
2457         *
2458         * @return The insets supplied, minus any insets that were consumed
2459         */
2460        @NonNull
2461        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
2462                V child, WindowInsetsCompat insets) {
2463            return insets;
2464        }
2465
2466        /**
2467         * Called when a child of the view associated with this behavior wants a particular
2468         * rectangle to be positioned onto the screen.
2469         *
2470         * <p>The contract for this method is the same as
2471         * {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}.</p>
2472         *
2473         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2474         *                          associated with
2475         * @param child             the child view of the CoordinatorLayout this Behavior is
2476         *                          associated with
2477         * @param rectangle         The rectangle which the child wishes to be on the screen
2478         *                          in the child's coordinates
2479         * @param immediate         true to forbid animated or delayed scrolling, false otherwise
2480         * @return true if the Behavior handled the request
2481         * @see ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)
2482         */
2483        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
2484                V child, Rect rectangle, boolean immediate) {
2485            return false;
2486        }
2487
2488        /**
2489         * Hook allowing a behavior to re-apply a representation of its internal state that had
2490         * previously been generated by {@link #onSaveInstanceState}. This function will never
2491         * be called with a null state.
2492         *
2493         * @param parent the parent CoordinatorLayout
2494         * @param child child view to restore from
2495         * @param state The frozen state that had previously been returned by
2496         *        {@link #onSaveInstanceState}.
2497         *
2498         * @see #onSaveInstanceState()
2499         */
2500        public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
2501            // no-op
2502        }
2503
2504        /**
2505         * Hook allowing a behavior to generate a representation of its internal state
2506         * that can later be used to create a new instance with that same state.
2507         * This state should only contain information that is not persistent or can
2508         * not be reconstructed later.
2509         *
2510         * <p>Behavior state is only saved when both the parent {@link CoordinatorLayout} and
2511         * a view using this behavior have valid IDs set.</p>
2512         *
2513         * @param parent the parent CoordinatorLayout
2514         * @param child child view to restore from
2515         *
2516         * @return Returns a Parcelable object containing the behavior's current dynamic
2517         *         state.
2518         *
2519         * @see #onRestoreInstanceState(android.os.Parcelable)
2520         * @see View#onSaveInstanceState()
2521         */
2522        public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
2523            return BaseSavedState.EMPTY_STATE;
2524        }
2525
2526        /**
2527         * Called when a view is set to dodge view insets.
2528         *
2529         * <p>This method allows a behavior to update the rectangle that should be dodged.
2530         * The rectangle should be in the parent's coordinate system and within the child's
2531         * bounds. If not, a {@link IllegalArgumentException} is thrown.</p>
2532         *
2533         * @param parent the CoordinatorLayout parent of the view this Behavior is
2534         *               associated with
2535         * @param child  the child view of the CoordinatorLayout this Behavior is associated with
2536         * @param rect   the rect to update with the dodge rectangle
2537         * @return true the rect was updated, false if we should use the child's bounds
2538         */
2539        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,
2540                @NonNull Rect rect) {
2541            return false;
2542        }
2543    }
2544
2545    /**
2546     * Parameters describing the desired layout for a child of a {@link CoordinatorLayout}.
2547     */
2548    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2549        /**
2550         * A {@link Behavior} that the child view should obey.
2551         */
2552        Behavior mBehavior;
2553
2554        boolean mBehaviorResolved = false;
2555
2556        /**
2557         * A {@link Gravity} value describing how this child view should lay out.
2558         * If either or both of the axes are not specified, they are treated by CoordinatorLayout
2559         * as {@link Gravity#TOP} or {@link GravityCompat#START}. If an
2560         * {@link #setAnchorId(int) anchor} is also specified, the gravity describes how this child
2561         * view should be positioned relative to its anchored position.
2562         */
2563        public int gravity = Gravity.NO_GRAVITY;
2564
2565        /**
2566         * A {@link Gravity} value describing which edge of a child view's
2567         * {@link #getAnchorId() anchor} view the child should position itself relative to.
2568         */
2569        public int anchorGravity = Gravity.NO_GRAVITY;
2570
2571        /**
2572         * The index of the horizontal keyline specified to the parent CoordinatorLayout that this
2573         * child should align relative to. If an {@link #setAnchorId(int) anchor} is present the
2574         * keyline will be ignored.
2575         */
2576        public int keyline = -1;
2577
2578        /**
2579         * A {@link View#getId() view id} of a descendant view of the CoordinatorLayout that
2580         * this child should position relative to.
2581         */
2582        int mAnchorId = View.NO_ID;
2583
2584        /**
2585         * A {@link Gravity} value describing how this child view insets the CoordinatorLayout.
2586         * Other child views which are set to dodge the same inset edges will be moved appropriately
2587         * so that the views do not overlap.
2588         */
2589        public int insetEdge = Gravity.NO_GRAVITY;
2590
2591        /**
2592         * A {@link Gravity} value describing how this child view dodges any inset child views in
2593         * the CoordinatorLayout. Any views which are inset on the same edge as this view is set to
2594         * dodge will result in this view being moved so that the views do not overlap.
2595         */
2596        public int dodgeInsetEdges = Gravity.NO_GRAVITY;
2597
2598        int mInsetOffsetX;
2599        int mInsetOffsetY;
2600
2601        View mAnchorView;
2602        View mAnchorDirectChild;
2603
2604        private boolean mDidBlockInteraction;
2605        private boolean mDidAcceptNestedScroll;
2606        private boolean mDidChangeAfterNestedScroll;
2607
2608        final Rect mLastChildRect = new Rect();
2609
2610        Object mBehaviorTag;
2611
2612        public LayoutParams(int width, int height) {
2613            super(width, height);
2614        }
2615
2616        LayoutParams(Context context, AttributeSet attrs) {
2617            super(context, attrs);
2618
2619            final TypedArray a = context.obtainStyledAttributes(attrs,
2620                    R.styleable.CoordinatorLayout_Layout);
2621
2622            this.gravity = a.getInteger(
2623                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
2624                    Gravity.NO_GRAVITY);
2625            mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
2626                    View.NO_ID);
2627            this.anchorGravity = a.getInteger(
2628                    R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
2629                    Gravity.NO_GRAVITY);
2630
2631            this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
2632                    -1);
2633
2634            insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
2635            dodgeInsetEdges = a.getInt(
2636                    R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
2637            mBehaviorResolved = a.hasValue(
2638                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
2639            if (mBehaviorResolved) {
2640                mBehavior = parseBehavior(context, attrs, a.getString(
2641                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
2642            }
2643            a.recycle();
2644
2645            if (mBehavior != null) {
2646                // If we have a Behavior, dispatch that it has been attached
2647                mBehavior.onAttachedToLayoutParams(this);
2648            }
2649        }
2650
2651        public LayoutParams(LayoutParams p) {
2652            super(p);
2653        }
2654
2655        public LayoutParams(MarginLayoutParams p) {
2656            super(p);
2657        }
2658
2659        public LayoutParams(ViewGroup.LayoutParams p) {
2660            super(p);
2661        }
2662
2663        /**
2664         * Get the id of this view's anchor.
2665         *
2666         * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
2667         */
2668        @IdRes
2669        public int getAnchorId() {
2670            return mAnchorId;
2671        }
2672
2673        /**
2674         * Set the id of this view's anchor.
2675         *
2676         * <p>The view with this id must be a descendant of the CoordinatorLayout containing
2677         * the child view this LayoutParams belongs to. It may not be the child view with
2678         * this LayoutParams or a descendant of it.</p>
2679         *
2680         * @param id The {@link View#getId() view id} of the anchor or
2681         *           {@link View#NO_ID} if there is no anchor
2682         */
2683        public void setAnchorId(@IdRes int id) {
2684            invalidateAnchor();
2685            mAnchorId = id;
2686        }
2687
2688        /**
2689         * Get the behavior governing the layout and interaction of the child view within
2690         * a parent CoordinatorLayout.
2691         *
2692         * @return The current behavior or null if no behavior is specified
2693         */
2694        @Nullable
2695        public Behavior getBehavior() {
2696            return mBehavior;
2697        }
2698
2699        /**
2700         * Set the behavior governing the layout and interaction of the child view within
2701         * a parent CoordinatorLayout.
2702         *
2703         * <p>Setting a new behavior will remove any currently associated
2704         * {@link Behavior#setTag(android.view.View, Object) Behavior tag}.</p>
2705         *
2706         * @param behavior The behavior to set or null for no special behavior
2707         */
2708        public void setBehavior(@Nullable Behavior behavior) {
2709            if (mBehavior != behavior) {
2710                if (mBehavior != null) {
2711                    // First detach any old behavior
2712                    mBehavior.onDetachedFromLayoutParams();
2713                }
2714
2715                mBehavior = behavior;
2716                mBehaviorTag = null;
2717                mBehaviorResolved = true;
2718
2719                if (behavior != null) {
2720                    // Now dispatch that the Behavior has been attached
2721                    behavior.onAttachedToLayoutParams(this);
2722                }
2723            }
2724        }
2725
2726        /**
2727         * Set the last known position rect for this child view
2728         * @param r the rect to set
2729         */
2730        void setLastChildRect(Rect r) {
2731            mLastChildRect.set(r);
2732        }
2733
2734        /**
2735         * Get the last known position rect for this child view.
2736         * Note: do not mutate the result of this call.
2737         */
2738        Rect getLastChildRect() {
2739            return mLastChildRect;
2740        }
2741
2742        /**
2743         * Returns true if the anchor id changed to another valid view id since the anchor view
2744         * was resolved.
2745         */
2746        boolean checkAnchorChanged() {
2747            return mAnchorView == null && mAnchorId != View.NO_ID;
2748        }
2749
2750        /**
2751         * Returns true if the associated Behavior previously blocked interaction with other views
2752         * below the associated child since the touch behavior tracking was last
2753         * {@link #resetTouchBehaviorTracking() reset}.
2754         *
2755         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
2756         */
2757        boolean didBlockInteraction() {
2758            if (mBehavior == null) {
2759                mDidBlockInteraction = false;
2760            }
2761            return mDidBlockInteraction;
2762        }
2763
2764        /**
2765         * Check if the associated Behavior wants to block interaction below the given child
2766         * view. The given child view should be the child this LayoutParams is associated with.
2767         *
2768         * <p>Once interaction is blocked, it will remain blocked until touch interaction tracking
2769         * is {@link #resetTouchBehaviorTracking() reset}.</p>
2770         *
2771         * @param parent the parent CoordinatorLayout
2772         * @param child the child view this LayoutParams is associated with
2773         * @return true to block interaction below the given child
2774         */
2775        boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) {
2776            if (mDidBlockInteraction) {
2777                return true;
2778            }
2779
2780            return mDidBlockInteraction |= mBehavior != null
2781                    ? mBehavior.blocksInteractionBelow(parent, child)
2782                    : false;
2783        }
2784
2785        /**
2786         * Reset tracking of Behavior-specific touch interactions. This includes
2787         * interaction blocking.
2788         *
2789         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
2790         * @see #didBlockInteraction()
2791         */
2792        void resetTouchBehaviorTracking() {
2793            mDidBlockInteraction = false;
2794        }
2795
2796        void resetNestedScroll() {
2797            mDidAcceptNestedScroll = false;
2798        }
2799
2800        void acceptNestedScroll(boolean accept) {
2801            mDidAcceptNestedScroll = accept;
2802        }
2803
2804        boolean isNestedScrollAccepted() {
2805            return mDidAcceptNestedScroll;
2806        }
2807
2808        boolean getChangedAfterNestedScroll() {
2809            return mDidChangeAfterNestedScroll;
2810        }
2811
2812        void setChangedAfterNestedScroll(boolean changed) {
2813            mDidChangeAfterNestedScroll = changed;
2814        }
2815
2816        void resetChangedAfterNestedScroll() {
2817            mDidChangeAfterNestedScroll = false;
2818        }
2819
2820        /**
2821         * Check if an associated child view depends on another child view of the CoordinatorLayout.
2822         *
2823         * @param parent the parent CoordinatorLayout
2824         * @param child the child to check
2825         * @param dependency the proposed dependency to check
2826         * @return true if child depends on dependency
2827         */
2828        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
2829            return dependency == mAnchorDirectChild
2830                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
2831                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
2832        }
2833
2834        /**
2835         * Invalidate the cached anchor view and direct child ancestor of that anchor.
2836         * The anchor will need to be
2837         * {@link #findAnchorView(CoordinatorLayout, android.view.View) found} before
2838         * being used again.
2839         */
2840        void invalidateAnchor() {
2841            mAnchorView = mAnchorDirectChild = null;
2842        }
2843
2844        /**
2845         * Locate the appropriate anchor view by the current {@link #setAnchorId(int) anchor id}
2846         * or return the cached anchor view if already known.
2847         *
2848         * @param parent the parent CoordinatorLayout
2849         * @param forChild the child this LayoutParams is associated with
2850         * @return the located descendant anchor view, or null if the anchor id is
2851         *         {@link View#NO_ID}.
2852         */
2853        View findAnchorView(CoordinatorLayout parent, View forChild) {
2854            if (mAnchorId == View.NO_ID) {
2855                mAnchorView = mAnchorDirectChild = null;
2856                return null;
2857            }
2858
2859            if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
2860                resolveAnchorView(forChild, parent);
2861            }
2862            return mAnchorView;
2863        }
2864
2865        /**
2866         * Determine the anchor view for the child view this LayoutParams is assigned to.
2867         * Assumes mAnchorId is valid.
2868         */
2869        private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
2870            mAnchorView = parent.findViewById(mAnchorId);
2871            if (mAnchorView != null) {
2872                if (mAnchorView == parent) {
2873                    if (parent.isInEditMode()) {
2874                        mAnchorView = mAnchorDirectChild = null;
2875                        return;
2876                    }
2877                    throw new IllegalStateException(
2878                            "View can not be anchored to the the parent CoordinatorLayout");
2879                }
2880
2881                View directChild = mAnchorView;
2882                for (ViewParent p = mAnchorView.getParent();
2883                        p != parent && p != null;
2884                        p = p.getParent()) {
2885                    if (p == forChild) {
2886                        if (parent.isInEditMode()) {
2887                            mAnchorView = mAnchorDirectChild = null;
2888                            return;
2889                        }
2890                        throw new IllegalStateException(
2891                                "Anchor must not be a descendant of the anchored view");
2892                    }
2893                    if (p instanceof View) {
2894                        directChild = (View) p;
2895                    }
2896                }
2897                mAnchorDirectChild = directChild;
2898            } else {
2899                if (parent.isInEditMode()) {
2900                    mAnchorView = mAnchorDirectChild = null;
2901                    return;
2902                }
2903                throw new IllegalStateException("Could not find CoordinatorLayout descendant view"
2904                        + " with id " + parent.getResources().getResourceName(mAnchorId)
2905                        + " to anchor view " + forChild);
2906            }
2907        }
2908
2909        /**
2910         * Verify that the previously resolved anchor view is still valid - that it is still
2911         * a descendant of the expected parent view, it is not the child this LayoutParams
2912         * is assigned to or a descendant of it, and it has the expected id.
2913         */
2914        private boolean verifyAnchorView(View forChild, CoordinatorLayout parent) {
2915            if (mAnchorView.getId() != mAnchorId) {
2916                return false;
2917            }
2918
2919            View directChild = mAnchorView;
2920            for (ViewParent p = mAnchorView.getParent();
2921                    p != parent;
2922                    p = p.getParent()) {
2923                if (p == null || p == forChild) {
2924                    mAnchorView = mAnchorDirectChild = null;
2925                    return false;
2926                }
2927                if (p instanceof View) {
2928                    directChild = (View) p;
2929                }
2930            }
2931            mAnchorDirectChild = directChild;
2932            return true;
2933        }
2934
2935        /**
2936         * Checks whether the view with this LayoutParams should dodge the specified view.
2937         */
2938        private boolean shouldDodge(View other, int layoutDirection) {
2939            LayoutParams lp = (LayoutParams) other.getLayoutParams();
2940            final int absInset = GravityCompat.getAbsoluteGravity(lp.insetEdge, layoutDirection);
2941            return absInset != Gravity.NO_GRAVITY && (absInset &
2942                    GravityCompat.getAbsoluteGravity(dodgeInsetEdges, layoutDirection)) == absInset;
2943        }
2944    }
2945
2946    private class HierarchyChangeListener implements OnHierarchyChangeListener {
2947        HierarchyChangeListener() {
2948        }
2949
2950        @Override
2951        public void onChildViewAdded(View parent, View child) {
2952            if (mOnHierarchyChangeListener != null) {
2953                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
2954            }
2955        }
2956
2957        @Override
2958        public void onChildViewRemoved(View parent, View child) {
2959            onChildViewsChanged(EVENT_VIEW_REMOVED);
2960
2961            if (mOnHierarchyChangeListener != null) {
2962                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
2963            }
2964        }
2965    }
2966
2967    @Override
2968    protected void onRestoreInstanceState(Parcelable state) {
2969        if (!(state instanceof SavedState)) {
2970            super.onRestoreInstanceState(state);
2971            return;
2972        }
2973
2974        final SavedState ss = (SavedState) state;
2975        super.onRestoreInstanceState(ss.getSuperState());
2976
2977        final SparseArray<Parcelable> behaviorStates = ss.behaviorStates;
2978
2979        for (int i = 0, count = getChildCount(); i < count; i++) {
2980            final View child = getChildAt(i);
2981            final int childId = child.getId();
2982            final LayoutParams lp = getResolvedLayoutParams(child);
2983            final Behavior b = lp.getBehavior();
2984
2985            if (childId != NO_ID && b != null) {
2986                Parcelable savedState = behaviorStates.get(childId);
2987                if (savedState != null) {
2988                    b.onRestoreInstanceState(this, child, savedState);
2989                }
2990            }
2991        }
2992    }
2993
2994    @Override
2995    protected Parcelable onSaveInstanceState() {
2996        final SavedState ss = new SavedState(super.onSaveInstanceState());
2997
2998        final SparseArray<Parcelable> behaviorStates = new SparseArray<>();
2999        for (int i = 0, count = getChildCount(); i < count; i++) {
3000            final View child = getChildAt(i);
3001            final int childId = child.getId();
3002            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
3003            final Behavior b = lp.getBehavior();
3004
3005            if (childId != NO_ID && b != null) {
3006                // If the child has an ID and a Behavior, let it save some state...
3007                Parcelable state = b.onSaveInstanceState(this, child);
3008                if (state != null) {
3009                    behaviorStates.append(childId, state);
3010                }
3011            }
3012        }
3013        ss.behaviorStates = behaviorStates;
3014        return ss;
3015    }
3016
3017    @Override
3018    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
3019        final CoordinatorLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
3020        final Behavior behavior = lp.getBehavior();
3021
3022        if (behavior != null
3023                && behavior.onRequestChildRectangleOnScreen(this, child, rectangle, immediate)) {
3024            return true;
3025        }
3026
3027        return super.requestChildRectangleOnScreen(child, rectangle, immediate);
3028    }
3029
3030    private void setupForInsets() {
3031        if (Build.VERSION.SDK_INT < 21) {
3032            return;
3033        }
3034
3035        if (ViewCompat.getFitsSystemWindows(this)) {
3036            if (mApplyWindowInsetsListener == null) {
3037                mApplyWindowInsetsListener =
3038                        new android.support.v4.view.OnApplyWindowInsetsListener() {
3039                            @Override
3040                            public WindowInsetsCompat onApplyWindowInsets(View v,
3041                                    WindowInsetsCompat insets) {
3042                                return setWindowInsets(insets);
3043                            }
3044                        };
3045            }
3046            // First apply the insets listener
3047            ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
3048
3049            // Now set the sys ui flags to enable us to lay out in the window insets
3050            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
3051                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
3052        } else {
3053            ViewCompat.setOnApplyWindowInsetsListener(this, null);
3054        }
3055    }
3056
3057    protected static class SavedState extends AbsSavedState {
3058        SparseArray<Parcelable> behaviorStates;
3059
3060        public SavedState(Parcel source, ClassLoader loader) {
3061            super(source, loader);
3062
3063            final int size = source.readInt();
3064
3065            final int[] ids = new int[size];
3066            source.readIntArray(ids);
3067
3068            final Parcelable[] states = source.readParcelableArray(loader);
3069
3070            behaviorStates = new SparseArray<>(size);
3071            for (int i = 0; i < size; i++) {
3072                behaviorStates.append(ids[i], states[i]);
3073            }
3074        }
3075
3076        public SavedState(Parcelable superState) {
3077            super(superState);
3078        }
3079
3080        @Override
3081        public void writeToParcel(Parcel dest, int flags) {
3082            super.writeToParcel(dest, flags);
3083
3084            final int size = behaviorStates != null ? behaviorStates.size() : 0;
3085            dest.writeInt(size);
3086
3087            final int[] ids = new int[size];
3088            final Parcelable[] states = new Parcelable[size];
3089
3090            for (int i = 0; i < size; i++) {
3091                ids[i] = behaviorStates.keyAt(i);
3092                states[i] = behaviorStates.valueAt(i);
3093            }
3094            dest.writeIntArray(ids);
3095            dest.writeParcelableArray(states, flags);
3096
3097        }
3098
3099        public static final Parcelable.Creator<SavedState> CREATOR =
3100                new ClassLoaderCreator<SavedState>() {
3101                    @Override
3102                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
3103                        return new SavedState(in, loader);
3104                    }
3105
3106                    @Override
3107                    public SavedState createFromParcel(Parcel in) {
3108                        return new SavedState(in, null);
3109                    }
3110
3111                    @Override
3112                    public SavedState[] newArray(int size) {
3113                        return new SavedState[size];
3114                    }
3115                };
3116    }
3117}
3118