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