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