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