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