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