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