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