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