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