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