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