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