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