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