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