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