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