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