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