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