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