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