CoordinatorLayout.java revision 245f6b0f5f502171d2e0d82df42f5b29895711df
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20import static android.support.design.widget.ViewUtils.objectEquals;
21
22import android.content.Context;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.Rect;
29import android.graphics.Region;
30import android.graphics.drawable.ColorDrawable;
31import android.graphics.drawable.Drawable;
32import android.os.Build;
33import android.os.Parcel;
34import android.os.Parcelable;
35import android.os.SystemClock;
36import android.support.annotation.ColorInt;
37import android.support.annotation.DrawableRes;
38import android.support.annotation.FloatRange;
39import android.support.annotation.IdRes;
40import android.support.annotation.IntDef;
41import android.support.annotation.NonNull;
42import android.support.annotation.Nullable;
43import android.support.annotation.RestrictTo;
44import android.support.annotation.VisibleForTesting;
45import android.support.design.R;
46import android.support.v4.content.ContextCompat;
47import android.support.v4.graphics.drawable.DrawableCompat;
48import android.support.v4.os.ParcelableCompat;
49import android.support.v4.os.ParcelableCompatCreatorCallbacks;
50import android.support.v4.view.AbsSavedState;
51import android.support.v4.view.GravityCompat;
52import android.support.v4.view.MotionEventCompat;
53import android.support.v4.view.NestedScrollingParent;
54import android.support.v4.view.NestedScrollingParentHelper;
55import android.support.v4.view.ViewCompat;
56import android.support.v4.view.WindowInsetsCompat;
57import android.text.TextUtils;
58import android.util.AttributeSet;
59import android.util.Log;
60import android.util.SparseArray;
61import android.view.Gravity;
62import android.view.MotionEvent;
63import android.view.View;
64import android.view.ViewGroup;
65import android.view.ViewParent;
66import android.view.ViewTreeObserver;
67
68import java.lang.annotation.Retention;
69import java.lang.annotation.RetentionPolicy;
70import java.lang.reflect.Constructor;
71import java.util.ArrayList;
72import java.util.Collections;
73import java.util.Comparator;
74import java.util.HashMap;
75import java.util.List;
76import java.util.Map;
77
78/**
79 * CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}.
80 *
81 * <p>CoordinatorLayout is intended for two primary use cases:</p>
82 * <ol>
83 *     <li>As a top-level application decor or chrome layout</li>
84 *     <li>As a container for a specific interaction with one or more child views</li>
85 * </ol>
86 *
87 * <p>By specifying {@link CoordinatorLayout.Behavior Behaviors} for child views of a
88 * CoordinatorLayout you can provide many different interactions within a single parent and those
89 * views can also interact with one another. View classes can specify a default behavior when
90 * used as a child of a CoordinatorLayout using the
91 * {@link CoordinatorLayout.DefaultBehavior DefaultBehavior} annotation.</p>
92 *
93 * <p>Behaviors may be used to implement a variety of interactions and additional layout
94 * modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons
95 * that stick to other elements as they move and animate.</p>
96 *
97 * <p>Children of a CoordinatorLayout may have an
98 * {@link CoordinatorLayout.LayoutParams#setAnchorId(int) anchor}. This view id must correspond
99 * to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself
100 * or a descendant of the anchored child. This can be used to place floating views relative to
101 * other arbitrary content panes.</p>
102 *
103 * <p>Children can specify {@link CoordinatorLayout.LayoutParams#insetEdge} to describe how the
104 * view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by
105 * {@link CoordinatorLayout.LayoutParams#dodgeInsetEdges} will be moved appropriately so that the
106 * views do not overlap.</p>
107 */
108public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
109    static final String TAG = "CoordinatorLayout";
110    static final String WIDGET_PACKAGE_NAME;
111
112    static {
113        final Package pkg = CoordinatorLayout.class.getPackage();
114        WIDGET_PACKAGE_NAME = pkg != null ? pkg.getName() : null;
115    }
116
117    private static final int TYPE_ON_INTERCEPT = 0;
118    private static final int TYPE_ON_TOUCH = 1;
119
120    static {
121        if (Build.VERSION.SDK_INT >= 21) {
122            TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
123        } else {
124            TOP_SORTED_CHILDREN_COMPARATOR = null;
125        }
126    }
127
128    static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
129            Context.class,
130            AttributeSet.class
131    };
132
133    static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
134            new ThreadLocal<>();
135
136
137    static final int EVENT_PRE_DRAW = 0;
138    static final int EVENT_NESTED_SCROLL = 1;
139    static final int EVENT_VIEW_REMOVED = 2;
140
141    /** @hide */
142    @RestrictTo(LIBRARY_GROUP)
143    @Retention(RetentionPolicy.SOURCE)
144    @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
145    public @interface DispatchChangeEvent {}
146
147    static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
148
149    private final List<View> mDependencySortedChildren = new ArrayList<>();
150    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
151
152    private final List<View> mTempList1 = new ArrayList<>();
153    private final List<View> mTempDependenciesList = new ArrayList<>();
154    private final Rect mTempRect1 = new Rect();
155    private final Rect mTempRect2 = new Rect();
156    private final Rect mTempRect3 = new Rect();
157    private final Rect mTempRect4 = new Rect();
158    private final 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            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
1233                // Do not try to update GONE child views in pre draw updates.
1234                continue;
1235            }
1236
1237            // Check child views before for anchor
1238            for (int j = 0; j < i; j++) {
1239                final View checkChild = mDependencySortedChildren.get(j);
1240
1241                if (lp.mAnchorDirectChild == checkChild) {
1242                    offsetChildToAnchor(child, layoutDirection);
1243                }
1244            }
1245
1246            // Get the current draw rect of the view
1247            final Rect drawRect = mTempRect1;
1248            getChildRect(child, true, drawRect);
1249
1250            // Accumulate inset sizes
1251            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
1252                final int absInsetEdge = GravityCompat.getAbsoluteGravity(
1253                        lp.insetEdge, layoutDirection);
1254                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
1255                    case Gravity.TOP:
1256                        inset.top = Math.max(inset.top, drawRect.bottom);
1257                        break;
1258                    case Gravity.BOTTOM:
1259                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
1260                        break;
1261                }
1262                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
1263                    case Gravity.LEFT:
1264                        inset.left = Math.max(inset.left, drawRect.right);
1265                        break;
1266                    case Gravity.RIGHT:
1267                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
1268                        break;
1269                }
1270            }
1271
1272            // Dodge inset edges if necessary
1273            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
1274                offsetChildByInset(child, inset, layoutDirection);
1275            }
1276
1277            if (type == EVENT_PRE_DRAW) {
1278                // Did it change? if not continue
1279                final Rect lastDrawRect = mTempRect2;
1280                getLastChildRect(child, lastDrawRect);
1281                if (lastDrawRect.equals(drawRect)) {
1282                    continue;
1283                }
1284                recordLastChildRect(child, drawRect);
1285            }
1286
1287            // Update any behavior-dependent views for the change
1288            for (int j = i + 1; j < childCount; j++) {
1289                final View checkChild = mDependencySortedChildren.get(j);
1290                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
1291                final Behavior b = checkLp.getBehavior();
1292
1293                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
1294                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
1295                        // If this is from a pre-draw and we have already been changed
1296                        // from a nested scroll, skip the dispatch and reset the flag
1297                        checkLp.resetChangedAfterNestedScroll();
1298                        continue;
1299                    }
1300
1301                    final boolean handled;
1302                    switch (type) {
1303                        case EVENT_VIEW_REMOVED:
1304                            // EVENT_VIEW_REMOVED means that we need to dispatch
1305                            // onDependentViewRemoved() instead
1306                            b.onDependentViewRemoved(this, checkChild, child);
1307                            handled = true;
1308                            break;
1309                        default:
1310                            // Otherwise we dispatch onDependentViewChanged()
1311                            handled = b.onDependentViewChanged(this, checkChild, child);
1312                            break;
1313                    }
1314
1315                    if (type == EVENT_NESTED_SCROLL) {
1316                        // If this is from a nested scroll, set the flag so that we may skip
1317                        // any resulting onPreDraw dispatch (if needed)
1318                        checkLp.setChangedAfterNestedScroll(handled);
1319                    }
1320                }
1321            }
1322        }
1323    }
1324
1325    private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
1326        if (!ViewCompat.isLaidOut(child)) {
1327            // The view has not been laid out yet,
1328            // so we can't obtain its bounds.
1329            return;
1330        }
1331
1332        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1333        final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges,
1334                layoutDirection);
1335
1336        final Behavior behavior = lp.getBehavior();
1337        final Rect rect = mTempRect3;
1338        rect.setEmpty();
1339        final Rect bounds = mTempRect4;
1340        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
1341
1342        if (behavior != null && behavior.getInsetDodgeRect(this, child, rect)) {
1343            // Make sure that the rect is within the view's bounds
1344            if (!bounds.contains(rect)) {
1345                throw new IllegalArgumentException("Rect should be within the child's bounds."
1346                        + " Rect:" + rect.toShortString() + " | Bounds:" + bounds.toShortString());
1347            }
1348        } else {
1349            rect.set(bounds);
1350        }
1351
1352        if (rect.isEmpty()) {
1353            // Rect is empty so there is nothing to dodge against, skip...
1354            return;
1355        }
1356
1357        boolean offsetY = false;
1358        if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) {
1359            int distance = rect.top - lp.topMargin - lp.mInsetOffsetY;
1360            if (distance < inset.top) {
1361                setInsetOffsetY(child, inset.top - distance);
1362                offsetY = true;
1363            }
1364        }
1365        if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) {
1366            int distance = getHeight() - rect.bottom - lp.bottomMargin + lp.mInsetOffsetY;
1367            if (distance < inset.bottom) {
1368                setInsetOffsetY(child, distance - inset.bottom);
1369                offsetY = true;
1370            }
1371        }
1372        if (!offsetY) {
1373            setInsetOffsetY(child, 0);
1374        }
1375
1376        boolean offsetX = false;
1377        if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) {
1378            int distance = rect.left - lp.leftMargin - lp.mInsetOffsetX;
1379            if (distance < inset.left) {
1380                setInsetOffsetX(child, inset.left - distance);
1381                offsetX = true;
1382            }
1383        }
1384        if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) {
1385            int distance = getWidth() - rect.right - lp.rightMargin + lp.mInsetOffsetX;
1386            if (distance < inset.right) {
1387                setInsetOffsetX(child, distance - inset.right);
1388                offsetX = true;
1389            }
1390        }
1391        if (!offsetX) {
1392            setInsetOffsetX(child, 0);
1393        }
1394    }
1395
1396    private void setInsetOffsetX(View child, int offsetX) {
1397        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1398        if (lp.mInsetOffsetX != offsetX) {
1399            final int dx = offsetX - lp.mInsetOffsetX;
1400            ViewCompat.offsetLeftAndRight(child, dx);
1401            lp.mInsetOffsetX = offsetX;
1402        }
1403    }
1404
1405    private void setInsetOffsetY(View child, int offsetY) {
1406        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1407        if (lp.mInsetOffsetY != offsetY) {
1408            final int dy = offsetY - lp.mInsetOffsetY;
1409            ViewCompat.offsetTopAndBottom(child, dy);
1410            lp.mInsetOffsetY = offsetY;
1411        }
1412    }
1413
1414    /**
1415     * Allows the caller to manually dispatch
1416     * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
1417     * {@link Behavior} instances of views which depend on the provided {@link View}.
1418     *
1419     * <p>You should not normally need to call this method as the it will be automatically done
1420     * when the view has changed.
1421     *
1422     * @param view the View to find dependents of to dispatch the call.
1423     */
1424    public void dispatchDependentViewsChanged(View view) {
1425        final List<View> dependents = mChildDag.getIncomingEdges(view);
1426        if (dependents != null && !dependents.isEmpty()) {
1427            for (int i = 0; i < dependents.size(); i++) {
1428                final View child = dependents.get(i);
1429                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
1430                        child.getLayoutParams();
1431                CoordinatorLayout.Behavior b = lp.getBehavior();
1432                if (b != null) {
1433                    b.onDependentViewChanged(this, child, view);
1434                }
1435            }
1436        }
1437    }
1438
1439    /**
1440     * Returns the list of views which the provided view depends on. Do not store this list as its
1441     * contents may not be valid beyond the caller.
1442     *
1443     * @param child the view to find dependencies for.
1444     *
1445     * @return the list of views which {@code child} depends on.
1446     */
1447    @NonNull
1448    public List<View> getDependencies(@NonNull View child) {
1449        final List<View> dependencies = mChildDag.getOutgoingEdges(child);
1450        mTempDependenciesList.clear();
1451        if (dependencies != null) {
1452            mTempDependenciesList.addAll(dependencies);
1453        }
1454        return mTempDependenciesList;
1455    }
1456
1457    /**
1458     * Returns the list of views which depend on the provided view. Do not store this list as its
1459     * contents may not be valid beyond the caller.
1460     *
1461     * @param child the view to find dependents of.
1462     *
1463     * @return the list of views which depend on {@code child}.
1464     */
1465    @NonNull
1466    public List<View> getDependents(@NonNull View child) {
1467        final List<View> edges = mChildDag.getIncomingEdges(child);
1468        mTempDependenciesList.clear();
1469        if (edges != null) {
1470            mTempDependenciesList.addAll(edges);
1471        }
1472        return mTempDependenciesList;
1473    }
1474
1475    @VisibleForTesting
1476    final List<View> getDependencySortedChildren() {
1477        prepareChildren();
1478        return Collections.unmodifiableList(mDependencySortedChildren);
1479    }
1480
1481    /**
1482     * Add or remove the pre-draw listener as necessary.
1483     */
1484    void ensurePreDrawListener() {
1485        boolean hasDependencies = false;
1486        final int childCount = getChildCount();
1487        for (int i = 0; i < childCount; i++) {
1488            final View child = getChildAt(i);
1489            if (hasDependencies(child)) {
1490                hasDependencies = true;
1491                break;
1492            }
1493        }
1494
1495        if (hasDependencies != mNeedsPreDrawListener) {
1496            if (hasDependencies) {
1497                addPreDrawListener();
1498            } else {
1499                removePreDrawListener();
1500            }
1501        }
1502    }
1503
1504    /**
1505     * Check if the given child has any layout dependencies on other child views.
1506     */
1507    private boolean hasDependencies(View child) {
1508        return mChildDag.hasOutgoingEdges(child);
1509    }
1510
1511    /**
1512     * Add the pre-draw listener if we're attached to a window and mark that we currently
1513     * need it when attached.
1514     */
1515    void addPreDrawListener() {
1516        if (mIsAttachedToWindow) {
1517            // Add the listener
1518            if (mOnPreDrawListener == null) {
1519                mOnPreDrawListener = new OnPreDrawListener();
1520            }
1521            final ViewTreeObserver vto = getViewTreeObserver();
1522            vto.addOnPreDrawListener(mOnPreDrawListener);
1523        }
1524
1525        // Record that we need the listener regardless of whether or not we're attached.
1526        // We'll add the real listener when we become attached.
1527        mNeedsPreDrawListener = true;
1528    }
1529
1530    /**
1531     * Remove the pre-draw listener if we're attached to a window and mark that we currently
1532     * do not need it when attached.
1533     */
1534    void removePreDrawListener() {
1535        if (mIsAttachedToWindow) {
1536            if (mOnPreDrawListener != null) {
1537                final ViewTreeObserver vto = getViewTreeObserver();
1538                vto.removeOnPreDrawListener(mOnPreDrawListener);
1539            }
1540        }
1541        mNeedsPreDrawListener = false;
1542    }
1543
1544    /**
1545     * Adjust the child left, top, right, bottom rect to the correct anchor view position,
1546     * respecting gravity and anchor gravity.
1547     *
1548     * Note that child translation properties are ignored in this process, allowing children
1549     * to be animated away from their anchor. However, if the anchor view is animated,
1550     * the child will be offset to match the anchor's translated position.
1551     */
1552    void offsetChildToAnchor(View child, int layoutDirection) {
1553        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1554        if (lp.mAnchorView != null) {
1555            final Rect anchorRect = mTempRect1;
1556            final Rect childRect = mTempRect2;
1557            final Rect desiredChildRect = mTempRect3;
1558
1559            getDescendantRect(lp.mAnchorView, anchorRect);
1560            getChildRect(child, false, childRect);
1561
1562            int childWidth = child.getMeasuredWidth();
1563            int childHeight = child.getMeasuredHeight();
1564            getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect,
1565                    desiredChildRect, lp, childWidth, childHeight);
1566            boolean changed = desiredChildRect.left != childRect.left ||
1567                    desiredChildRect.top != childRect.top;
1568            constrainChildRect(lp, desiredChildRect, childWidth, childHeight);
1569
1570            final int dx = desiredChildRect.left - childRect.left;
1571            final int dy = desiredChildRect.top - childRect.top;
1572
1573            if (dx != 0) {
1574                ViewCompat.offsetLeftAndRight(child, dx);
1575            }
1576            if (dy != 0) {
1577                ViewCompat.offsetTopAndBottom(child, dy);
1578            }
1579
1580            if (changed) {
1581                // If we have needed to move, make sure to notify the child's Behavior
1582                final Behavior b = lp.getBehavior();
1583                if (b != null) {
1584                    b.onDependentViewChanged(this, child, lp.mAnchorView);
1585                }
1586            }
1587        }
1588    }
1589
1590    /**
1591     * Check if a given point in the CoordinatorLayout's coordinates are within the view bounds
1592     * of the given direct child view.
1593     *
1594     * @param child child view to test
1595     * @param x X coordinate to test, in the CoordinatorLayout's coordinate system
1596     * @param y Y coordinate to test, in the CoordinatorLayout's coordinate system
1597     * @return true if the point is within the child view's bounds, false otherwise
1598     */
1599    public boolean isPointInChildBounds(View child, int x, int y) {
1600        final Rect r = mTempRect1;
1601        getDescendantRect(child, r);
1602        return r.contains(x, y);
1603    }
1604
1605    /**
1606     * Check whether two views overlap each other. The views need to be descendants of this
1607     * {@link CoordinatorLayout} in the view hierarchy.
1608     *
1609     * @param first first child view to test
1610     * @param second second child view to test
1611     * @return true if both views are visible and overlap each other
1612     */
1613    public boolean doViewsOverlap(View first, View second) {
1614        if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
1615            final Rect firstRect = mTempRect1;
1616            getChildRect(first, first.getParent() != this, firstRect);
1617            final Rect secondRect = mTempRect2;
1618            getChildRect(second, second.getParent() != this, secondRect);
1619
1620            return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
1621                    || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
1622        }
1623        return false;
1624    }
1625
1626    @Override
1627    public LayoutParams generateLayoutParams(AttributeSet attrs) {
1628        return new LayoutParams(getContext(), attrs);
1629    }
1630
1631    @Override
1632    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1633        if (p instanceof LayoutParams) {
1634            return new LayoutParams((LayoutParams) p);
1635        } else if (p instanceof MarginLayoutParams) {
1636            return new LayoutParams((MarginLayoutParams) p);
1637        }
1638        return new LayoutParams(p);
1639    }
1640
1641    @Override
1642    protected LayoutParams generateDefaultLayoutParams() {
1643        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1644    }
1645
1646    @Override
1647    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1648        return p instanceof LayoutParams && super.checkLayoutParams(p);
1649    }
1650
1651    @Override
1652    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
1653        boolean handled = false;
1654
1655        final int childCount = getChildCount();
1656        for (int i = 0; i < childCount; i++) {
1657            final View view = getChildAt(i);
1658            if (view.getVisibility() == View.GONE) {
1659                // If it's GONE, don't dispatch
1660                continue;
1661            }
1662            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1663            final Behavior viewBehavior = lp.getBehavior();
1664            if (viewBehavior != null) {
1665                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
1666                        nestedScrollAxes);
1667                handled |= accepted;
1668
1669                lp.acceptNestedScroll(accepted);
1670            } else {
1671                lp.acceptNestedScroll(false);
1672            }
1673        }
1674        return handled;
1675    }
1676
1677    @Override
1678    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
1679        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
1680        mNestedScrollingDirectChild = child;
1681        mNestedScrollingTarget = target;
1682
1683        final int childCount = getChildCount();
1684        for (int i = 0; i < childCount; i++) {
1685            final View view = getChildAt(i);
1686            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1687            if (!lp.isNestedScrollAccepted()) {
1688                continue;
1689            }
1690
1691            final Behavior viewBehavior = lp.getBehavior();
1692            if (viewBehavior != null) {
1693                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
1694            }
1695        }
1696    }
1697
1698    @Override
1699    public void onStopNestedScroll(View target) {
1700        mNestedScrollingParentHelper.onStopNestedScroll(target);
1701
1702        final int childCount = getChildCount();
1703        for (int i = 0; i < childCount; i++) {
1704            final View view = getChildAt(i);
1705            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1706            if (!lp.isNestedScrollAccepted()) {
1707                continue;
1708            }
1709
1710            final Behavior viewBehavior = lp.getBehavior();
1711            if (viewBehavior != null) {
1712                viewBehavior.onStopNestedScroll(this, view, target);
1713            }
1714            lp.resetNestedScroll();
1715            lp.resetChangedAfterNestedScroll();
1716        }
1717
1718        mNestedScrollingDirectChild = null;
1719        mNestedScrollingTarget = null;
1720    }
1721
1722    @Override
1723    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
1724            int dxUnconsumed, int dyUnconsumed) {
1725        final int childCount = getChildCount();
1726        boolean accepted = false;
1727
1728        for (int i = 0; i < childCount; i++) {
1729            final View view = getChildAt(i);
1730            if (view.getVisibility() == GONE) {
1731                // If the child is GONE, skip...
1732                continue;
1733            }
1734
1735            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1736            if (!lp.isNestedScrollAccepted()) {
1737                continue;
1738            }
1739
1740            final Behavior viewBehavior = lp.getBehavior();
1741            if (viewBehavior != null) {
1742                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
1743                        dxUnconsumed, dyUnconsumed);
1744                accepted = true;
1745            }
1746        }
1747
1748        if (accepted) {
1749            onChildViewsChanged(EVENT_NESTED_SCROLL);
1750        }
1751    }
1752
1753    @Override
1754    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
1755        int xConsumed = 0;
1756        int yConsumed = 0;
1757        boolean accepted = false;
1758
1759        final int childCount = getChildCount();
1760        for (int i = 0; i < childCount; i++) {
1761            final View view = getChildAt(i);
1762            if (view.getVisibility() == GONE) {
1763                // If the child is GONE, skip...
1764                continue;
1765            }
1766
1767            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1768            if (!lp.isNestedScrollAccepted()) {
1769                continue;
1770            }
1771
1772            final Behavior viewBehavior = lp.getBehavior();
1773            if (viewBehavior != null) {
1774                mTempIntPair[0] = mTempIntPair[1] = 0;
1775                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
1776
1777                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
1778                        : Math.min(xConsumed, mTempIntPair[0]);
1779                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
1780                        : Math.min(yConsumed, mTempIntPair[1]);
1781
1782                accepted = true;
1783            }
1784        }
1785
1786        consumed[0] = xConsumed;
1787        consumed[1] = yConsumed;
1788
1789        if (accepted) {
1790            onChildViewsChanged(EVENT_NESTED_SCROLL);
1791        }
1792    }
1793
1794    @Override
1795    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
1796        boolean handled = false;
1797
1798        final int childCount = getChildCount();
1799        for (int i = 0; i < childCount; i++) {
1800            final View view = getChildAt(i);
1801            if (view.getVisibility() == GONE) {
1802                // If the child is GONE, skip...
1803                continue;
1804            }
1805
1806            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1807            if (!lp.isNestedScrollAccepted()) {
1808                continue;
1809            }
1810
1811            final Behavior viewBehavior = lp.getBehavior();
1812            if (viewBehavior != null) {
1813                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
1814                        consumed);
1815            }
1816        }
1817        if (handled) {
1818            onChildViewsChanged(EVENT_NESTED_SCROLL);
1819        }
1820        return handled;
1821    }
1822
1823    @Override
1824    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
1825        boolean handled = false;
1826
1827        final int childCount = getChildCount();
1828        for (int i = 0; i < childCount; i++) {
1829            final View view = getChildAt(i);
1830            if (view.getVisibility() == GONE) {
1831                // If the child is GONE, skip...
1832                continue;
1833            }
1834
1835            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1836            if (!lp.isNestedScrollAccepted()) {
1837                continue;
1838            }
1839
1840            final Behavior viewBehavior = lp.getBehavior();
1841            if (viewBehavior != null) {
1842                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
1843            }
1844        }
1845        return handled;
1846    }
1847
1848    @Override
1849    public int getNestedScrollAxes() {
1850        return mNestedScrollingParentHelper.getNestedScrollAxes();
1851    }
1852
1853    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1854        @Override
1855        public boolean onPreDraw() {
1856            onChildViewsChanged(EVENT_PRE_DRAW);
1857            return true;
1858        }
1859    }
1860
1861    /**
1862     * Sorts child views with higher Z values to the beginning of a collection.
1863     */
1864    static class ViewElevationComparator implements Comparator<View> {
1865        @Override
1866        public int compare(View lhs, View rhs) {
1867            final float lz = ViewCompat.getZ(lhs);
1868            final float rz = ViewCompat.getZ(rhs);
1869            if (lz > rz) {
1870                return -1;
1871            } else if (lz < rz) {
1872                return 1;
1873            }
1874            return 0;
1875        }
1876    }
1877
1878    /**
1879     * Defines the default {@link Behavior} of a {@link View} class.
1880     *
1881     * <p>When writing a custom view, use this annotation to define the default behavior
1882     * when used as a direct child of an {@link CoordinatorLayout}. The default behavior
1883     * can be overridden using {@link LayoutParams#setBehavior}.</p>
1884     *
1885     * <p>Example: <code>@DefaultBehavior(MyBehavior.class)</code></p>
1886     */
1887    @Retention(RetentionPolicy.RUNTIME)
1888    public @interface DefaultBehavior {
1889        Class<? extends Behavior> value();
1890    }
1891
1892    /**
1893     * Interaction behavior plugin for child views of {@link CoordinatorLayout}.
1894     *
1895     * <p>A Behavior implements one or more interactions that a user can take on a child view.
1896     * These interactions may include drags, swipes, flings, or any other gestures.</p>
1897     *
1898     * @param <V> The View type that this Behavior operates on
1899     */
1900    public static abstract class Behavior<V extends View> {
1901
1902        /**
1903         * Default constructor for instantiating Behaviors.
1904         */
1905        public Behavior() {
1906        }
1907
1908        /**
1909         * Default constructor for inflating Behaviors from layout. The Behavior will have
1910         * the opportunity to parse specially defined layout parameters. These parameters will
1911         * appear on the child view tag.
1912         *
1913         * @param context
1914         * @param attrs
1915         */
1916        public Behavior(Context context, AttributeSet attrs) {
1917        }
1918
1919        /**
1920         * Called when the Behavior has been attached to a LayoutParams instance.
1921         *
1922         * <p>This will be called after the LayoutParams has been instantiated and can be
1923         * modified.</p>
1924         *
1925         * @param params the LayoutParams instance that this Behavior has been attached to
1926         */
1927        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
1928        }
1929
1930        /**
1931         * Called when the Behavior has been detached from its holding LayoutParams instance.
1932         *
1933         * <p>This will only be called if the Behavior has been explicitly removed from the
1934         * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be
1935         * called if the associated view is removed from the CoordinatorLayout or similar.</p>
1936         */
1937        public void onDetachedFromLayoutParams() {
1938        }
1939
1940        /**
1941         * Respond to CoordinatorLayout touch events before they are dispatched to child views.
1942         *
1943         * <p>Behaviors can use this to monitor inbound touch events until one decides to
1944         * intercept the rest of the event stream to take an action on its associated child view.
1945         * This method will return false until it detects the proper intercept conditions, then
1946         * return true once those conditions have occurred.</p>
1947         *
1948         * <p>Once a Behavior intercepts touch events, the rest of the event stream will
1949         * be sent to the {@link #onTouchEvent} method.</p>
1950         *
1951         * <p>The default implementation of this method always returns false.</p>
1952         *
1953         * @param parent the parent view currently receiving this touch event
1954         * @param child the child view associated with this Behavior
1955         * @param ev the MotionEvent describing the touch event being processed
1956         * @return true if this Behavior would like to intercept and take over the event stream.
1957         *         The default always returns false.
1958         */
1959        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
1960            return false;
1961        }
1962
1963        /**
1964         * Respond to CoordinatorLayout touch events after this Behavior has started
1965         * {@link #onInterceptTouchEvent intercepting} them.
1966         *
1967         * <p>Behaviors may intercept touch events in order to help the CoordinatorLayout
1968         * manipulate its child views. For example, a Behavior may allow a user to drag a
1969         * UI pane open or closed. This method should perform actual mutations of view
1970         * layout state.</p>
1971         *
1972         * @param parent the parent view currently receiving this touch event
1973         * @param child the child view associated with this Behavior
1974         * @param ev the MotionEvent describing the touch event being processed
1975         * @return true if this Behavior handled this touch event and would like to continue
1976         *         receiving events in this stream. The default always returns false.
1977         */
1978        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
1979            return false;
1980        }
1981
1982        /**
1983         * Supply a scrim color that will be painted behind the associated child view.
1984         *
1985         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
1986         * interactive or actionable, drawing user focus and attention to the views above the scrim.
1987         * </p>
1988         *
1989         * <p>The default implementation returns {@link Color#BLACK}.</p>
1990         *
1991         * @param parent the parent view of the given child
1992         * @param child the child view above the scrim
1993         * @return the desired scrim color in 0xAARRGGBB format. The default return value is
1994         *         {@link Color#BLACK}.
1995         * @see #getScrimOpacity(CoordinatorLayout, android.view.View)
1996         */
1997        @ColorInt
1998        public int getScrimColor(CoordinatorLayout parent, V child) {
1999            return Color.BLACK;
2000        }
2001
2002        /**
2003         * Determine the current opacity of the scrim behind a given child view
2004         *
2005         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
2006         * interactive or actionable, drawing user focus and attention to the views above the scrim.
2007         * </p>
2008         *
2009         * <p>The default implementation returns 0.0f.</p>
2010         *
2011         * @param parent the parent view of the given child
2012         * @param child the child view above the scrim
2013         * @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
2014         */
2015        @FloatRange(from = 0, to = 1)
2016        public float getScrimOpacity(CoordinatorLayout parent, V child) {
2017            return 0.f;
2018        }
2019
2020        /**
2021         * Determine whether interaction with views behind the given child in the child order
2022         * should be blocked.
2023         *
2024         * <p>The default implementation returns true if
2025         * {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would return > 0.0f.</p>
2026         *
2027         * @param parent the parent view of the given child
2028         * @param child the child view to test
2029         * @return true if {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would
2030         *         return > 0.0f.
2031         */
2032        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
2033            return getScrimOpacity(parent, child) > 0.f;
2034        }
2035
2036        /**
2037         * Determine whether the supplied child view has another specific sibling view as a
2038         * layout dependency.
2039         *
2040         * <p>This method will be called at least once in response to a layout request. If it
2041         * returns true for a given child and dependency view pair, the parent CoordinatorLayout
2042         * will:</p>
2043         * <ol>
2044         *     <li>Always lay out this child after the dependent child is laid out, regardless
2045         *     of child order.</li>
2046         *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
2047         *     position changes.</li>
2048         * </ol>
2049         *
2050         * @param parent the parent view of the given child
2051         * @param child the child view to test
2052         * @param dependency the proposed dependency of child
2053         * @return true if child's layout depends on the proposed dependency's layout,
2054         *         false otherwise
2055         *
2056         * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
2057         */
2058        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
2059            return false;
2060        }
2061
2062        /**
2063         * Respond to a change in a child's dependent view
2064         *
2065         * <p>This method is called whenever a dependent view changes in size or position outside
2066         * of the standard layout flow. A Behavior may use this method to appropriately update
2067         * the child view in response.</p>
2068         *
2069         * <p>A view's dependency is determined by
2070         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
2071         * if {@code child} has set another view as it's anchor.</p>
2072         *
2073         * <p>Note that if a Behavior changes the layout of a child via this method, it should
2074         * also be able to reconstruct the correct position in
2075         * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}.
2076         * <code>onDependentViewChanged</code> will not be called during normal layout since
2077         * the layout of each child view will always happen in dependency order.</p>
2078         *
2079         * <p>If the Behavior changes the child view's size or position, it should return true.
2080         * The default implementation returns false.</p>
2081         *
2082         * @param parent the parent view of the given child
2083         * @param child the child view to manipulate
2084         * @param dependency the dependent view that changed
2085         * @return true if the Behavior changed the child view's size or position, false otherwise
2086         */
2087        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
2088            return false;
2089        }
2090
2091        /**
2092         * Respond to a child's dependent view being removed.
2093         *
2094         * <p>This method is called after a dependent view has been removed from the parent.
2095         * A Behavior may use this method to appropriately update the child view in response.</p>
2096         *
2097         * <p>A view's dependency is determined by
2098         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
2099         * if {@code child} has set another view as it's anchor.</p>
2100         *
2101         * @param parent the parent view of the given child
2102         * @param child the child view to manipulate
2103         * @param dependency the dependent view that has been removed
2104         */
2105        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
2106        }
2107
2108        /**
2109         * @deprecated this method is not called anymore. You can safely remove all usages
2110         * and implementations. This method will be removed in a future release.
2111         */
2112        @Deprecated
2113        public boolean isDirty(CoordinatorLayout parent, V child) {
2114            return false;
2115        }
2116
2117        /**
2118         * Called when the parent CoordinatorLayout is about to measure the given child view.
2119         *
2120         * <p>This method can be used to perform custom or modified measurement of a child view
2121         * in place of the default child measurement behavior. The Behavior's implementation
2122         * can delegate to the standard CoordinatorLayout measurement behavior by calling
2123         * {@link CoordinatorLayout#onMeasureChild(android.view.View, int, int, int, int)
2124         * parent.onMeasureChild}.</p>
2125         *
2126         * @param parent the parent CoordinatorLayout
2127         * @param child the child to measure
2128         * @param parentWidthMeasureSpec the width requirements for this view
2129         * @param widthUsed extra space that has been used up by the parent
2130         *        horizontally (possibly by other children of the parent)
2131         * @param parentHeightMeasureSpec the height requirements for this view
2132         * @param heightUsed extra space that has been used up by the parent
2133         *        vertically (possibly by other children of the parent)
2134         * @return true if the Behavior measured the child view, false if the CoordinatorLayout
2135         *         should perform its default measurement
2136         */
2137        public boolean onMeasureChild(CoordinatorLayout parent, V child,
2138                int parentWidthMeasureSpec, int widthUsed,
2139                int parentHeightMeasureSpec, int heightUsed) {
2140            return false;
2141        }
2142
2143        /**
2144         * Called when the parent CoordinatorLayout is about the lay out the given child view.
2145         *
2146         * <p>This method can be used to perform custom or modified layout of a child view
2147         * in place of the default child layout behavior. The Behavior's implementation can
2148         * delegate to the standard CoordinatorLayout measurement behavior by calling
2149         * {@link CoordinatorLayout#onLayoutChild(android.view.View, int)
2150         * parent.onLayoutChild}.</p>
2151         *
2152         * <p>If a Behavior implements
2153         * {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)}
2154         * to change the position of a view in response to a dependent view changing, it
2155         * should also implement <code>onLayoutChild</code> in such a way that respects those
2156         * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
2157         * <em>after</em> its dependency has been laid out.</p>
2158         *
2159         * @param parent the parent CoordinatorLayout
2160         * @param child child view to lay out
2161         * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
2162         *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
2163         *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
2164         * @return true if the Behavior performed layout of the child view, false to request
2165         *         default layout behavior
2166         */
2167        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
2168            return false;
2169        }
2170
2171        // Utility methods for accessing child-specific, behavior-modifiable properties.
2172
2173        /**
2174         * Associate a Behavior-specific tag object with the given child view.
2175         * This object will be stored with the child view's LayoutParams.
2176         *
2177         * @param child child view to set tag with
2178         * @param tag tag object to set
2179         */
2180        public static void setTag(View child, Object tag) {
2181            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2182            lp.mBehaviorTag = tag;
2183        }
2184
2185        /**
2186         * Get the behavior-specific tag object with the given child view.
2187         * This object is stored with the child view's LayoutParams.
2188         *
2189         * @param child child view to get tag with
2190         * @return the previously stored tag object
2191         */
2192        public static Object getTag(View child) {
2193            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2194            return lp.mBehaviorTag;
2195        }
2196
2197
2198        /**
2199         * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
2200         *
2201         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
2202         * to this event and return true to indicate that the CoordinatorLayout should act as
2203         * a nested scrolling parent for this scroll. Only Behaviors that return true from
2204         * this method will receive subsequent nested scroll events.</p>
2205         *
2206         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2207         *                          associated with
2208         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2209         * @param directTargetChild the child view of the CoordinatorLayout that either is or
2210         *                          contains the target of the nested scroll operation
2211         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
2212         * @param nestedScrollAxes the axes that this nested scroll applies to. See
2213         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
2214         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
2215         * @return true if the Behavior wishes to accept this nested scroll
2216         *
2217         * @see NestedScrollingParent#onStartNestedScroll(View, View, int)
2218         */
2219        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
2220                V child, View directTargetChild, View target, int nestedScrollAxes) {
2221            return false;
2222        }
2223
2224        /**
2225         * Called when a nested scroll has been accepted by the CoordinatorLayout.
2226         *
2227         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
2228         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2229         * that returned true will receive subsequent nested scroll events for that nested scroll.
2230         * </p>
2231         *
2232         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2233         *                          associated with
2234         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2235         * @param directTargetChild the child view of the CoordinatorLayout that either is or
2236         *                          contains the target of the nested scroll operation
2237         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
2238         * @param nestedScrollAxes the axes that this nested scroll applies to. See
2239         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
2240         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
2241         *
2242         * @see NestedScrollingParent#onNestedScrollAccepted(View, View, int)
2243         */
2244        public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
2245                View directTargetChild, View target, int nestedScrollAxes) {
2246            // Do nothing
2247        }
2248
2249        /**
2250         * Called when a nested scroll has ended.
2251         *
2252         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
2253         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2254         * that returned true will receive subsequent nested scroll events for that nested scroll.
2255         * </p>
2256         *
2257         * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event
2258         * sequence. This is a good place to clean up any state related to the nested scroll.
2259         * </p>
2260         *
2261         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2262         *                          associated with
2263         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2264         * @param target the descendant view of the CoordinatorLayout that initiated
2265         *               the nested scroll
2266         *
2267         * @see NestedScrollingParent#onStopNestedScroll(View)
2268         */
2269        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
2270            // Do nothing
2271        }
2272
2273        /**
2274         * Called when a nested scroll in progress has updated and the target has scrolled or
2275         * attempted to scroll.
2276         *
2277         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2278         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2279         * that returned true will receive subsequent nested scroll events for that nested scroll.
2280         * </p>
2281         *
2282         * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
2283         * nested scrolling child, with both consumed and unconsumed components of the scroll
2284         * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
2285         * same values.</em>
2286         * </p>
2287         *
2288         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2289         *                          associated with
2290         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2291         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2292         * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
2293         * @param dyConsumed vertical pixels consumed by the target's own scrolling operation
2294         * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
2295         *                     operation, but requested by the user
2296         * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
2297         *                     but requested by the user
2298         *
2299         * @see NestedScrollingParent#onNestedScroll(View, int, int, int, int)
2300         */
2301        public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
2302                int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
2303            // Do nothing
2304        }
2305
2306        /**
2307         * Called when a nested scroll in progress is about to update, before the target has
2308         * consumed any of the scrolled distance.
2309         *
2310         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2311         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2312         * that returned true will receive subsequent nested scroll events for that nested scroll.
2313         * </p>
2314         *
2315         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
2316         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
2317         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
2318         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
2319         * of pixels in either direction that any Behavior responding to the nested scroll reported
2320         * as consumed.</p>
2321         *
2322         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2323         *                          associated with
2324         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2325         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2326         * @param dx the raw horizontal number of pixels that the user attempted to scroll
2327         * @param dy the raw vertical number of pixels that the user attempted to scroll
2328         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
2329         *                 was consumed, consumed[1] should be set to the distance of dy that
2330         *                 was consumed
2331         *
2332         * @see NestedScrollingParent#onNestedPreScroll(View, int, int, int[])
2333         */
2334        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
2335                int dx, int dy, int[] consumed) {
2336            // Do nothing
2337        }
2338
2339        /**
2340         * Called when a nested scrolling child is starting a fling or an action that would
2341         * be a fling.
2342         *
2343         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2344         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2345         * that returned true will receive subsequent nested scroll events for that nested scroll.
2346         * </p>
2347         *
2348         * <p><code>onNestedFling</code> is called when the current nested scrolling child view
2349         * detects the proper conditions for a fling. It reports if the child itself consumed
2350         * the fling. If it did not, the child is expected to show some sort of overscroll
2351         * indication. This method should return true if it consumes the fling, so that a child
2352         * that did not itself take an action in response can choose not to show an overfling
2353         * indication.</p>
2354         *
2355         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2356         *                          associated with
2357         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2358         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2359         * @param velocityX horizontal velocity of the attempted fling
2360         * @param velocityY vertical velocity of the attempted fling
2361         * @param consumed true if the nested child view consumed the fling
2362         * @return true if the Behavior consumed the fling
2363         *
2364         * @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
2365         */
2366        public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
2367                float velocityX, float velocityY, boolean consumed) {
2368            return false;
2369        }
2370
2371        /**
2372         * Called when a nested scrolling child is about to start a fling.
2373         *
2374         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2375         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
2376         * that returned true will receive subsequent nested scroll events for that nested scroll.
2377         * </p>
2378         *
2379         * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
2380         * detects the proper conditions for a fling, but it has not acted on it yet. A
2381         * Behavior can return true to indicate that it consumed the fling. If at least one
2382         * Behavior returns true, the fling should not be acted upon by the child.</p>
2383         *
2384         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2385         *                          associated with
2386         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2387         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
2388         * @param velocityX horizontal velocity of the attempted fling
2389         * @param velocityY vertical velocity of the attempted fling
2390         * @return true if the Behavior consumed the fling
2391         *
2392         * @see NestedScrollingParent#onNestedPreFling(View, float, float)
2393         */
2394        public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
2395                float velocityX, float velocityY) {
2396            return false;
2397        }
2398
2399        /**
2400         * Called when the window insets have changed.
2401         *
2402         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
2403         * to handle the window inset change on behalf of it's associated view.
2404         * </p>
2405         *
2406         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2407         *                          associated with
2408         * @param child the child view of the CoordinatorLayout this Behavior is associated with
2409         * @param insets the new window insets.
2410         *
2411         * @return The insets supplied, minus any insets that were consumed
2412         */
2413        @NonNull
2414        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
2415                V child, WindowInsetsCompat insets) {
2416            return insets;
2417        }
2418
2419        /**
2420         * Called when a child of the view associated with this behavior wants a particular
2421         * rectangle to be positioned onto the screen.
2422         *
2423         * <p>The contract for this method is the same as
2424         * {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}.</p>
2425         *
2426         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
2427         *                          associated with
2428         * @param child             the child view of the CoordinatorLayout this Behavior is
2429         *                          associated with
2430         * @param rectangle         The rectangle which the child wishes to be on the screen
2431         *                          in the child's coordinates
2432         * @param immediate         true to forbid animated or delayed scrolling, false otherwise
2433         * @return true if the Behavior handled the request
2434         * @see ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)
2435         */
2436        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
2437                V child, Rect rectangle, boolean immediate) {
2438            return false;
2439        }
2440
2441        /**
2442         * Hook allowing a behavior to re-apply a representation of its internal state that had
2443         * previously been generated by {@link #onSaveInstanceState}. This function will never
2444         * be called with a null state.
2445         *
2446         * @param parent the parent CoordinatorLayout
2447         * @param child child view to restore from
2448         * @param state The frozen state that had previously been returned by
2449         *        {@link #onSaveInstanceState}.
2450         *
2451         * @see #onSaveInstanceState()
2452         */
2453        public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
2454            // no-op
2455        }
2456
2457        /**
2458         * Hook allowing a behavior to generate a representation of its internal state
2459         * that can later be used to create a new instance with that same state.
2460         * This state should only contain information that is not persistent or can
2461         * not be reconstructed later.
2462         *
2463         * <p>Behavior state is only saved when both the parent {@link CoordinatorLayout} and
2464         * a view using this behavior have valid IDs set.</p>
2465         *
2466         * @param parent the parent CoordinatorLayout
2467         * @param child child view to restore from
2468         *
2469         * @return Returns a Parcelable object containing the behavior's current dynamic
2470         *         state.
2471         *
2472         * @see #onRestoreInstanceState(android.os.Parcelable)
2473         * @see View#onSaveInstanceState()
2474         */
2475        public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
2476            return BaseSavedState.EMPTY_STATE;
2477        }
2478
2479        /**
2480         * Called when a view is set to dodge view insets.
2481         *
2482         * <p>This method allows a behavior to update the rectangle that should be dodged.
2483         * The rectangle should be in the parent's coordinate system and within the child's
2484         * bounds. If not, a {@link IllegalArgumentException} is thrown.</p>
2485         *
2486         * @param parent the CoordinatorLayout parent of the view this Behavior is
2487         *               associated with
2488         * @param child  the child view of the CoordinatorLayout this Behavior is associated with
2489         * @param rect   the rect to update with the dodge rectangle
2490         * @return true the rect was updated, false if we should use the child's bounds
2491         */
2492        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,
2493                @NonNull Rect rect) {
2494            return false;
2495        }
2496    }
2497
2498    /**
2499     * Parameters describing the desired layout for a child of a {@link CoordinatorLayout}.
2500     */
2501    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2502        /**
2503         * A {@link Behavior} that the child view should obey.
2504         */
2505        Behavior mBehavior;
2506
2507        boolean mBehaviorResolved = false;
2508
2509        /**
2510         * A {@link Gravity} value describing how this child view should lay out.
2511         * If an {@link #setAnchorId(int) anchor} is also specified, the gravity describes
2512         * how this child view should be positioned relative to its anchored position.
2513         */
2514        public int gravity = Gravity.NO_GRAVITY;
2515
2516        /**
2517         * A {@link Gravity} value describing which edge of a child view's
2518         * {@link #getAnchorId() anchor} view the child should position itself relative to.
2519         */
2520        public int anchorGravity = Gravity.NO_GRAVITY;
2521
2522        /**
2523         * The index of the horizontal keyline specified to the parent CoordinatorLayout that this
2524         * child should align relative to. If an {@link #setAnchorId(int) anchor} is present the
2525         * keyline will be ignored.
2526         */
2527        public int keyline = -1;
2528
2529        /**
2530         * A {@link View#getId() view id} of a descendant view of the CoordinatorLayout that
2531         * this child should position relative to.
2532         */
2533        int mAnchorId = View.NO_ID;
2534
2535        /**
2536         * A {@link Gravity} value describing how this child view insets the CoordinatorLayout.
2537         * Other child views which are set to dodge the same inset edges will be moved appropriately
2538         * so that the views do not overlap.
2539         */
2540        public int insetEdge = Gravity.NO_GRAVITY;
2541
2542        /**
2543         * A {@link Gravity} value describing how this child view dodges any inset child views in
2544         * the CoordinatorLayout. Any views which are inset on the same edge as this view is set to
2545         * dodge will result in this view being moved so that the views do not overlap.
2546         */
2547        public int dodgeInsetEdges = Gravity.NO_GRAVITY;
2548
2549        int mInsetOffsetX;
2550        int mInsetOffsetY;
2551
2552        View mAnchorView;
2553        View mAnchorDirectChild;
2554
2555        private boolean mDidBlockInteraction;
2556        private boolean mDidAcceptNestedScroll;
2557        private boolean mDidChangeAfterNestedScroll;
2558
2559        final Rect mLastChildRect = new Rect();
2560
2561        Object mBehaviorTag;
2562
2563        public LayoutParams(int width, int height) {
2564            super(width, height);
2565        }
2566
2567        LayoutParams(Context context, AttributeSet attrs) {
2568            super(context, attrs);
2569
2570            final TypedArray a = context.obtainStyledAttributes(attrs,
2571                    R.styleable.CoordinatorLayout_Layout);
2572
2573            this.gravity = a.getInteger(
2574                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
2575                    Gravity.NO_GRAVITY);
2576            mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
2577                    View.NO_ID);
2578            this.anchorGravity = a.getInteger(
2579                    R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
2580                    Gravity.NO_GRAVITY);
2581
2582            this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
2583                    -1);
2584
2585            insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
2586            dodgeInsetEdges = a.getInt(
2587                    R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
2588            mBehaviorResolved = a.hasValue(
2589                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
2590            if (mBehaviorResolved) {
2591                mBehavior = parseBehavior(context, attrs, a.getString(
2592                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
2593            }
2594            a.recycle();
2595
2596            if (mBehavior != null) {
2597                // If we have a Behavior, dispatch that it has been attached
2598                mBehavior.onAttachedToLayoutParams(this);
2599            }
2600        }
2601
2602        public LayoutParams(LayoutParams p) {
2603            super(p);
2604        }
2605
2606        public LayoutParams(MarginLayoutParams p) {
2607            super(p);
2608        }
2609
2610        public LayoutParams(ViewGroup.LayoutParams p) {
2611            super(p);
2612        }
2613
2614        /**
2615         * Get the id of this view's anchor.
2616         *
2617         * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
2618         */
2619        @IdRes
2620        public int getAnchorId() {
2621            return mAnchorId;
2622        }
2623
2624        /**
2625         * Set the id of this view's anchor.
2626         *
2627         * <p>The view with this id must be a descendant of the CoordinatorLayout containing
2628         * the child view this LayoutParams belongs to. It may not be the child view with
2629         * this LayoutParams or a descendant of it.</p>
2630         *
2631         * @param id The {@link View#getId() view id} of the anchor or
2632         *           {@link View#NO_ID} if there is no anchor
2633         */
2634        public void setAnchorId(@IdRes int id) {
2635            invalidateAnchor();
2636            mAnchorId = id;
2637        }
2638
2639        /**
2640         * Get the behavior governing the layout and interaction of the child view within
2641         * a parent CoordinatorLayout.
2642         *
2643         * @return The current behavior or null if no behavior is specified
2644         */
2645        @Nullable
2646        public Behavior getBehavior() {
2647            return mBehavior;
2648        }
2649
2650        /**
2651         * Set the behavior governing the layout and interaction of the child view within
2652         * a parent CoordinatorLayout.
2653         *
2654         * <p>Setting a new behavior will remove any currently associated
2655         * {@link Behavior#setTag(android.view.View, Object) Behavior tag}.</p>
2656         *
2657         * @param behavior The behavior to set or null for no special behavior
2658         */
2659        public void setBehavior(@Nullable Behavior behavior) {
2660            if (mBehavior != behavior) {
2661                if (mBehavior != null) {
2662                    // First detach any old behavior
2663                    mBehavior.onDetachedFromLayoutParams();
2664                }
2665
2666                mBehavior = behavior;
2667                mBehaviorTag = null;
2668                mBehaviorResolved = true;
2669
2670                if (behavior != null) {
2671                    // Now dispatch that the Behavior has been attached
2672                    behavior.onAttachedToLayoutParams(this);
2673                }
2674            }
2675        }
2676
2677        /**
2678         * Set the last known position rect for this child view
2679         * @param r the rect to set
2680         */
2681        void setLastChildRect(Rect r) {
2682            mLastChildRect.set(r);
2683        }
2684
2685        /**
2686         * Get the last known position rect for this child view.
2687         * Note: do not mutate the result of this call.
2688         */
2689        Rect getLastChildRect() {
2690            return mLastChildRect;
2691        }
2692
2693        /**
2694         * Returns true if the anchor id changed to another valid view id since the anchor view
2695         * was resolved.
2696         */
2697        boolean checkAnchorChanged() {
2698            return mAnchorView == null && mAnchorId != View.NO_ID;
2699        }
2700
2701        /**
2702         * Returns true if the associated Behavior previously blocked interaction with other views
2703         * below the associated child since the touch behavior tracking was last
2704         * {@link #resetTouchBehaviorTracking() reset}.
2705         *
2706         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
2707         */
2708        boolean didBlockInteraction() {
2709            if (mBehavior == null) {
2710                mDidBlockInteraction = false;
2711            }
2712            return mDidBlockInteraction;
2713        }
2714
2715        /**
2716         * Check if the associated Behavior wants to block interaction below the given child
2717         * view. The given child view should be the child this LayoutParams is associated with.
2718         *
2719         * <p>Once interaction is blocked, it will remain blocked until touch interaction tracking
2720         * is {@link #resetTouchBehaviorTracking() reset}.</p>
2721         *
2722         * @param parent the parent CoordinatorLayout
2723         * @param child the child view this LayoutParams is associated with
2724         * @return true to block interaction below the given child
2725         */
2726        boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) {
2727            if (mDidBlockInteraction) {
2728                return true;
2729            }
2730
2731            return mDidBlockInteraction |= mBehavior != null
2732                    ? mBehavior.blocksInteractionBelow(parent, child)
2733                    : false;
2734        }
2735
2736        /**
2737         * Reset tracking of Behavior-specific touch interactions. This includes
2738         * interaction blocking.
2739         *
2740         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
2741         * @see #didBlockInteraction()
2742         */
2743        void resetTouchBehaviorTracking() {
2744            mDidBlockInteraction = false;
2745        }
2746
2747        void resetNestedScroll() {
2748            mDidAcceptNestedScroll = false;
2749        }
2750
2751        void acceptNestedScroll(boolean accept) {
2752            mDidAcceptNestedScroll = accept;
2753        }
2754
2755        boolean isNestedScrollAccepted() {
2756            return mDidAcceptNestedScroll;
2757        }
2758
2759        boolean getChangedAfterNestedScroll() {
2760            return mDidChangeAfterNestedScroll;
2761        }
2762
2763        void setChangedAfterNestedScroll(boolean changed) {
2764            mDidChangeAfterNestedScroll = changed;
2765        }
2766
2767        void resetChangedAfterNestedScroll() {
2768            mDidChangeAfterNestedScroll = false;
2769        }
2770
2771        /**
2772         * Check if an associated child view depends on another child view of the CoordinatorLayout.
2773         *
2774         * @param parent the parent CoordinatorLayout
2775         * @param child the child to check
2776         * @param dependency the proposed dependency to check
2777         * @return true if child depends on dependency
2778         */
2779        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
2780            return dependency == mAnchorDirectChild
2781                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
2782                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
2783        }
2784
2785        /**
2786         * Invalidate the cached anchor view and direct child ancestor of that anchor.
2787         * The anchor will need to be
2788         * {@link #findAnchorView(CoordinatorLayout, android.view.View) found} before
2789         * being used again.
2790         */
2791        void invalidateAnchor() {
2792            mAnchorView = mAnchorDirectChild = null;
2793        }
2794
2795        /**
2796         * Locate the appropriate anchor view by the current {@link #setAnchorId(int) anchor id}
2797         * or return the cached anchor view if already known.
2798         *
2799         * @param parent the parent CoordinatorLayout
2800         * @param forChild the child this LayoutParams is associated with
2801         * @return the located descendant anchor view, or null if the anchor id is
2802         *         {@link View#NO_ID}.
2803         */
2804        View findAnchorView(CoordinatorLayout parent, View forChild) {
2805            if (mAnchorId == View.NO_ID) {
2806                mAnchorView = mAnchorDirectChild = null;
2807                return null;
2808            }
2809
2810            if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
2811                resolveAnchorView(forChild, parent);
2812            }
2813            return mAnchorView;
2814        }
2815
2816        /**
2817         * Determine the anchor view for the child view this LayoutParams is assigned to.
2818         * Assumes mAnchorId is valid.
2819         */
2820        private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
2821            mAnchorView = parent.findViewById(mAnchorId);
2822            if (mAnchorView != null) {
2823                if (mAnchorView == parent) {
2824                    if (parent.isInEditMode()) {
2825                        mAnchorView = mAnchorDirectChild = null;
2826                        return;
2827                    }
2828                    throw new IllegalStateException(
2829                            "View can not be anchored to the the parent CoordinatorLayout");
2830                }
2831
2832                View directChild = mAnchorView;
2833                for (ViewParent p = mAnchorView.getParent();
2834                        p != parent && p != null;
2835                        p = p.getParent()) {
2836                    if (p == forChild) {
2837                        if (parent.isInEditMode()) {
2838                            mAnchorView = mAnchorDirectChild = null;
2839                            return;
2840                        }
2841                        throw new IllegalStateException(
2842                                "Anchor must not be a descendant of the anchored view");
2843                    }
2844                    if (p instanceof View) {
2845                        directChild = (View) p;
2846                    }
2847                }
2848                mAnchorDirectChild = directChild;
2849            } else {
2850                if (parent.isInEditMode()) {
2851                    mAnchorView = mAnchorDirectChild = null;
2852                    return;
2853                }
2854                throw new IllegalStateException("Could not find CoordinatorLayout descendant view"
2855                        + " with id " + parent.getResources().getResourceName(mAnchorId)
2856                        + " to anchor view " + forChild);
2857            }
2858        }
2859
2860        /**
2861         * Verify that the previously resolved anchor view is still valid - that it is still
2862         * a descendant of the expected parent view, it is not the child this LayoutParams
2863         * is assigned to or a descendant of it, and it has the expected id.
2864         */
2865        private boolean verifyAnchorView(View forChild, CoordinatorLayout parent) {
2866            if (mAnchorView.getId() != mAnchorId) {
2867                return false;
2868            }
2869
2870            View directChild = mAnchorView;
2871            for (ViewParent p = mAnchorView.getParent();
2872                    p != parent;
2873                    p = p.getParent()) {
2874                if (p == null || p == forChild) {
2875                    mAnchorView = mAnchorDirectChild = null;
2876                    return false;
2877                }
2878                if (p instanceof View) {
2879                    directChild = (View) p;
2880                }
2881            }
2882            mAnchorDirectChild = directChild;
2883            return true;
2884        }
2885
2886        /**
2887         * Checks whether the view with this LayoutParams should dodge the specified view.
2888         */
2889        private boolean shouldDodge(View other, int layoutDirection) {
2890            LayoutParams lp = (LayoutParams) other.getLayoutParams();
2891            final int absInset = GravityCompat.getAbsoluteGravity(lp.insetEdge, layoutDirection);
2892            return absInset != Gravity.NO_GRAVITY && (absInset &
2893                    GravityCompat.getAbsoluteGravity(dodgeInsetEdges, layoutDirection)) == absInset;
2894        }
2895    }
2896
2897    private class HierarchyChangeListener implements OnHierarchyChangeListener {
2898        HierarchyChangeListener() {
2899        }
2900
2901        @Override
2902        public void onChildViewAdded(View parent, View child) {
2903            if (mOnHierarchyChangeListener != null) {
2904                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
2905            }
2906        }
2907
2908        @Override
2909        public void onChildViewRemoved(View parent, View child) {
2910            onChildViewsChanged(EVENT_VIEW_REMOVED);
2911
2912            if (mOnHierarchyChangeListener != null) {
2913                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
2914            }
2915        }
2916    }
2917
2918    @Override
2919    protected void onRestoreInstanceState(Parcelable state) {
2920        if (!(state instanceof SavedState)) {
2921            super.onRestoreInstanceState(state);
2922            return;
2923        }
2924
2925        final SavedState ss = (SavedState) state;
2926        super.onRestoreInstanceState(ss.getSuperState());
2927
2928        final SparseArray<Parcelable> behaviorStates = ss.behaviorStates;
2929
2930        for (int i = 0, count = getChildCount(); i < count; i++) {
2931            final View child = getChildAt(i);
2932            final int childId = child.getId();
2933            final LayoutParams lp = getResolvedLayoutParams(child);
2934            final Behavior b = lp.getBehavior();
2935
2936            if (childId != NO_ID && b != null) {
2937                Parcelable savedState = behaviorStates.get(childId);
2938                if (savedState != null) {
2939                    b.onRestoreInstanceState(this, child, savedState);
2940                }
2941            }
2942        }
2943    }
2944
2945    @Override
2946    protected Parcelable onSaveInstanceState() {
2947        final SavedState ss = new SavedState(super.onSaveInstanceState());
2948
2949        final SparseArray<Parcelable> behaviorStates = new SparseArray<>();
2950        for (int i = 0, count = getChildCount(); i < count; i++) {
2951            final View child = getChildAt(i);
2952            final int childId = child.getId();
2953            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2954            final Behavior b = lp.getBehavior();
2955
2956            if (childId != NO_ID && b != null) {
2957                // If the child has an ID and a Behavior, let it save some state...
2958                Parcelable state = b.onSaveInstanceState(this, child);
2959                if (state != null) {
2960                    behaviorStates.append(childId, state);
2961                }
2962            }
2963        }
2964        ss.behaviorStates = behaviorStates;
2965        return ss;
2966    }
2967
2968    @Override
2969    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
2970        final CoordinatorLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
2971        final Behavior behavior = lp.getBehavior();
2972
2973        if (behavior != null
2974                && behavior.onRequestChildRectangleOnScreen(this, child, rectangle, immediate)) {
2975            return true;
2976        }
2977
2978        return super.requestChildRectangleOnScreen(child, rectangle, immediate);
2979    }
2980
2981    private void setupForInsets() {
2982        if (Build.VERSION.SDK_INT < 21) {
2983            return;
2984        }
2985
2986        if (ViewCompat.getFitsSystemWindows(this)) {
2987            if (mApplyWindowInsetsListener == null) {
2988                mApplyWindowInsetsListener =
2989                        new android.support.v4.view.OnApplyWindowInsetsListener() {
2990                            @Override
2991                            public WindowInsetsCompat onApplyWindowInsets(View v,
2992                                    WindowInsetsCompat insets) {
2993                                return setWindowInsets(insets);
2994                            }
2995                        };
2996            }
2997            // First apply the insets listener
2998            ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
2999
3000            // Now set the sys ui flags to enable us to lay out in the window insets
3001            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
3002                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
3003        } else {
3004            ViewCompat.setOnApplyWindowInsetsListener(this, null);
3005        }
3006    }
3007
3008    protected static class SavedState extends AbsSavedState {
3009        SparseArray<Parcelable> behaviorStates;
3010
3011        public SavedState(Parcel source, ClassLoader loader) {
3012            super(source, loader);
3013
3014            final int size = source.readInt();
3015
3016            final int[] ids = new int[size];
3017            source.readIntArray(ids);
3018
3019            final Parcelable[] states = source.readParcelableArray(loader);
3020
3021            behaviorStates = new SparseArray<>(size);
3022            for (int i = 0; i < size; i++) {
3023                behaviorStates.append(ids[i], states[i]);
3024            }
3025        }
3026
3027        public SavedState(Parcelable superState) {
3028            super(superState);
3029        }
3030
3031        @Override
3032        public void writeToParcel(Parcel dest, int flags) {
3033            super.writeToParcel(dest, flags);
3034
3035            final int size = behaviorStates != null ? behaviorStates.size() : 0;
3036            dest.writeInt(size);
3037
3038            final int[] ids = new int[size];
3039            final Parcelable[] states = new Parcelable[size];
3040
3041            for (int i = 0; i < size; i++) {
3042                ids[i] = behaviorStates.keyAt(i);
3043                states[i] = behaviorStates.valueAt(i);
3044            }
3045            dest.writeIntArray(ids);
3046            dest.writeParcelableArray(states, flags);
3047
3048        }
3049
3050        public static final Parcelable.Creator<SavedState> CREATOR
3051                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
3052            @Override
3053            public SavedState createFromParcel(Parcel in, ClassLoader loader) {
3054                return new SavedState(in, loader);
3055            }
3056
3057            @Override
3058            public SavedState[] newArray(int size) {
3059                return new SavedState[size];
3060            }
3061        });
3062    }
3063}
3064