BottomSheetBehavior.java revision 6deb3a9dc52be7ab61702c430bb327343ef099ac
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.design.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.support.annotation.IntDef; 24import android.support.annotation.NonNull; 25import android.support.design.R; 26import android.support.v4.view.MotionEventCompat; 27import android.support.v4.view.NestedScrollingChild; 28import android.support.v4.view.VelocityTrackerCompat; 29import android.support.v4.view.ViewCompat; 30import android.support.v4.widget.ViewDragHelper; 31import android.util.AttributeSet; 32import android.view.MotionEvent; 33import android.view.VelocityTracker; 34import android.view.View; 35import android.view.ViewConfiguration; 36import android.view.ViewGroup; 37 38import java.lang.annotation.Retention; 39import java.lang.annotation.RetentionPolicy; 40import java.lang.ref.WeakReference; 41 42 43/** 44 * An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as 45 * a bottom sheet. 46 */ 47public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { 48 49 /** 50 * Callback for monitoring events about bottom sheets. 51 */ 52 public abstract static class BottomSheetCallback { 53 54 /** 55 * Called when the bottom sheet changes its state. 56 * 57 * @param bottomSheet The bottom sheet view. 58 * @param newState The new state. This will be one of {@link #STATE_DRAGGING}, 59 * {@link #STATE_SETTLING}, {@link #STATE_EXPANDED}, 60 * {@link #STATE_COLLAPSED}, or {@link #STATE_HIDDEN}. 61 */ 62 public abstract void onStateChanged(@NonNull View bottomSheet, @State int newState); 63 64 /** 65 * Called when the bottom sheet is being dragged. 66 * 67 * @param bottomSheet The bottom sheet view. 68 * @param slideOffset The new offset of this bottom sheet within its range, from 0 to 1 69 * when it is moving upward, and from 0 to -1 when it moving downward. 70 */ 71 public abstract void onSlide(@NonNull View bottomSheet, float slideOffset); 72 } 73 74 /** 75 * The bottom sheet is dragging. 76 */ 77 public static final int STATE_DRAGGING = 1; 78 79 /** 80 * The bottom sheet is settling. 81 */ 82 public static final int STATE_SETTLING = 2; 83 84 /** 85 * The bottom sheet is expanded. 86 */ 87 public static final int STATE_EXPANDED = 3; 88 89 /** 90 * The bottom sheet is collapsed. 91 */ 92 public static final int STATE_COLLAPSED = 4; 93 94 /** 95 * The bottom sheet is hidden. 96 */ 97 public static final int STATE_HIDDEN = 5; 98 99 /** @hide */ 100 @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING, STATE_HIDDEN}) 101 @Retention(RetentionPolicy.SOURCE) 102 public @interface State {} 103 104 private static final float HIDE_THRESHOLD = 0.5f; 105 106 private static final float HIDE_FRICTION = 0.1f; 107 108 private float mMaximumVelocity; 109 110 private int mPeekHeight; 111 112 private int mMinOffset; 113 114 private int mMaxOffset; 115 116 private boolean mHideable; 117 118 @State 119 private int mState = STATE_COLLAPSED; 120 121 private ViewDragHelper mViewDragHelper; 122 123 private boolean mIgnoreEvents; 124 125 private int mLastNestedScrollDy; 126 127 private boolean mNestedScrolled; 128 129 private int mParentHeight; 130 131 private WeakReference<V> mViewRef; 132 133 private WeakReference<View> mNestedScrollingChildRef; 134 135 private BottomSheetCallback mCallback; 136 137 private VelocityTracker mVelocityTracker; 138 139 private int mActivePointerId; 140 141 private int mInitialY; 142 143 private boolean mTouchingScrollingChild; 144 145 /** 146 * Default constructor for instantiating BottomSheetBehaviors. 147 */ 148 public BottomSheetBehavior() { 149 } 150 151 /** 152 * Default constructor for inflating BottomSheetBehaviors from layout. 153 * 154 * @param context The {@link Context}. 155 * @param attrs The {@link AttributeSet}. 156 */ 157 public BottomSheetBehavior(Context context, AttributeSet attrs) { 158 super(context, attrs); 159 TypedArray a = context.obtainStyledAttributes(attrs, 160 R.styleable.BottomSheetBehavior_Params); 161 setPeekHeight(a.getDimensionPixelSize( 162 R.styleable.BottomSheetBehavior_Params_behavior_peekHeight, 0)); 163 setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Params_behavior_hideable, false)); 164 a.recycle(); 165 ViewConfiguration configuration = ViewConfiguration.get(context); 166 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 167 } 168 169 @Override 170 public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) { 171 return new SavedState(super.onSaveInstanceState(parent, child), mState); 172 } 173 174 @Override 175 public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) { 176 SavedState ss = (SavedState) state; 177 super.onRestoreInstanceState(parent, child, ss.getSuperState()); 178 // Intermediate states are restored as collapsed state 179 if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) { 180 mState = STATE_COLLAPSED; 181 } else { 182 mState = ss.state; 183 } 184 } 185 186 @Override 187 public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { 188 // First let the parent lay it out 189 if (mState != STATE_DRAGGING && mState != STATE_SETTLING) { 190 if (ViewCompat.getFitsSystemWindows(parent) && 191 !ViewCompat.getFitsSystemWindows(child)) { 192 ViewCompat.setFitsSystemWindows(child, true); 193 } 194 parent.onLayoutChild(child, layoutDirection); 195 } 196 // Offset the bottom sheet 197 mParentHeight = parent.getHeight(); 198 mMinOffset = Math.max(0, mParentHeight - child.getHeight()); 199 mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset); 200 if (mState == STATE_EXPANDED) { 201 ViewCompat.offsetTopAndBottom(child, mMinOffset); 202 } else if (mHideable && mState == STATE_HIDDEN) { 203 ViewCompat.offsetTopAndBottom(child, mParentHeight); 204 } else if (mState == STATE_COLLAPSED) { 205 ViewCompat.offsetTopAndBottom(child, mMaxOffset); 206 } 207 if (mViewDragHelper == null) { 208 mViewDragHelper = ViewDragHelper.create(parent, mDragCallback); 209 } 210 mViewRef = new WeakReference<>(child); 211 mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); 212 return true; 213 } 214 215 @Override 216 public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { 217 if (!child.isShown()) { 218 return false; 219 } 220 int action = MotionEventCompat.getActionMasked(event); 221 // Record the velocity 222 if (action == MotionEvent.ACTION_DOWN) { 223 reset(); 224 } 225 if (mVelocityTracker == null) { 226 mVelocityTracker = VelocityTracker.obtain(); 227 } 228 mVelocityTracker.addMovement(event); 229 switch (action) { 230 case MotionEvent.ACTION_UP: 231 case MotionEvent.ACTION_CANCEL: 232 mTouchingScrollingChild = false; 233 mActivePointerId = MotionEvent.INVALID_POINTER_ID; 234 // Reset the ignore flag 235 if (mIgnoreEvents) { 236 mIgnoreEvents = false; 237 return false; 238 } 239 break; 240 case MotionEvent.ACTION_DOWN: 241 int initialX = (int) event.getX(); 242 mInitialY = (int) event.getY(); 243 View scroll = mNestedScrollingChildRef.get(); 244 if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) { 245 mActivePointerId = event.getPointerId(event.getActionIndex()); 246 mTouchingScrollingChild = true; 247 } 248 mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID && 249 !parent.isPointInChildBounds(child, initialX, mInitialY); 250 break; 251 } 252 if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) { 253 return true; 254 } 255 // We have to handle cases that the ViewDragHelper does not capture the bottom sheet because 256 // it is not the top most view of its parent. This is not necessary when the touch event is 257 // happening over the scrolling content as nested scrolling logic handles that case. 258 View scroll = mNestedScrollingChildRef.get(); 259 return action == MotionEvent.ACTION_MOVE && scroll != null && 260 !mIgnoreEvents && mState != STATE_DRAGGING && 261 !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && 262 Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop(); 263 } 264 265 @Override 266 public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { 267 if (!child.isShown()) { 268 return false; 269 } 270 int action = MotionEventCompat.getActionMasked(event); 271 if (mState == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) { 272 return true; 273 } 274 mViewDragHelper.processTouchEvent(event); 275 // Record the velocity 276 if (action == MotionEvent.ACTION_DOWN) { 277 reset(); 278 } 279 if (mVelocityTracker == null) { 280 mVelocityTracker = VelocityTracker.obtain(); 281 } 282 mVelocityTracker.addMovement(event); 283 // The ViewDragHelper tries to capture only the top-most View. We have to explicitly tell it 284 // to capture the bottom sheet in case it is not captured and the touch slop is passed. 285 if (action == MotionEvent.ACTION_MOVE && !mIgnoreEvents) { 286 if (Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop()) { 287 mViewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex())); 288 } 289 } 290 return true; 291 } 292 293 @Override 294 public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, 295 View directTargetChild, View target, int nestedScrollAxes) { 296 mLastNestedScrollDy = 0; 297 mNestedScrolled = false; 298 return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 299 } 300 301 @Override 302 public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, 303 int dy, int[] consumed) { 304 View scrollingChild = mNestedScrollingChildRef.get(); 305 if (target != scrollingChild) { 306 return; 307 } 308 int currentTop = child.getTop(); 309 int newTop = currentTop - dy; 310 if (dy > 0) { // Upward 311 if (newTop < mMinOffset) { 312 consumed[1] = currentTop - mMinOffset; 313 ViewCompat.offsetTopAndBottom(child, -consumed[1]); 314 setStateInternal(STATE_EXPANDED); 315 } else { 316 consumed[1] = dy; 317 ViewCompat.offsetTopAndBottom(child, -dy); 318 setStateInternal(STATE_DRAGGING); 319 } 320 } else if (dy < 0) { // Downward 321 if (!ViewCompat.canScrollVertically(target, -1)) { 322 if (newTop <= mMaxOffset || mHideable) { 323 consumed[1] = dy; 324 ViewCompat.offsetTopAndBottom(child, -dy); 325 setStateInternal(STATE_DRAGGING); 326 } else { 327 consumed[1] = currentTop - mMaxOffset; 328 ViewCompat.offsetTopAndBottom(child, -consumed[1]); 329 setStateInternal(STATE_COLLAPSED); 330 } 331 } 332 } 333 dispatchOnSlide(child.getTop()); 334 mLastNestedScrollDy = dy; 335 mNestedScrolled = true; 336 } 337 338 @Override 339 public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { 340 if (child.getTop() == mMinOffset) { 341 setStateInternal(STATE_EXPANDED); 342 return; 343 } 344 if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) { 345 return; 346 } 347 int top; 348 int targetState; 349 if (mLastNestedScrollDy > 0) { 350 top = mMinOffset; 351 targetState = STATE_EXPANDED; 352 } else if (mHideable && shouldHide(child, getYVelocity())) { 353 top = mParentHeight; 354 targetState = STATE_HIDDEN; 355 } else if (mLastNestedScrollDy == 0) { 356 int currentTop = child.getTop(); 357 if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) { 358 top = mMinOffset; 359 targetState = STATE_EXPANDED; 360 } else { 361 top = mMaxOffset; 362 targetState = STATE_COLLAPSED; 363 } 364 } else { 365 top = mMaxOffset; 366 targetState = STATE_COLLAPSED; 367 } 368 if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { 369 setStateInternal(STATE_SETTLING); 370 ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState)); 371 } else { 372 setStateInternal(targetState); 373 } 374 mNestedScrolled = false; 375 } 376 377 @Override 378 public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, 379 float velocityX, float velocityY) { 380 return target == mNestedScrollingChildRef.get() && 381 (mState != STATE_EXPANDED || 382 super.onNestedPreFling(coordinatorLayout, child, target, 383 velocityX, velocityY)); 384 } 385 386 /** 387 * Sets the height of the bottom sheet when it is collapsed. 388 * 389 * @param peekHeight The height of the collapsed bottom sheet in pixels. 390 * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight 391 */ 392 public final void setPeekHeight(int peekHeight) { 393 mPeekHeight = Math.max(0, peekHeight); 394 mMaxOffset = mParentHeight - peekHeight; 395 } 396 397 /** 398 * Gets the height of the bottom sheet when it is collapsed. 399 * 400 * @return The height of the collapsed bottom sheet. 401 * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight 402 */ 403 public final int getPeekHeight() { 404 return mPeekHeight; 405 } 406 407 /** 408 * Sets whether this bottom sheet can hide when it is swiped down. 409 * 410 * @param hideable {@code true} to make this bottom sheet hideable. 411 * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_hideable 412 */ 413 public void setHideable(boolean hideable) { 414 mHideable = hideable; 415 } 416 417 /** 418 * Gets whether this bottom sheet can hide when it is swiped down. 419 * 420 * @return {@code true} if this bottom sheet can hide. 421 * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_hideable 422 */ 423 public boolean isHideable() { 424 return mHideable; 425 } 426 427 /** 428 * Sets a callback to be notified of bottom sheet events. 429 * 430 * @param callback The callback to notify when bottom sheet events occur. 431 */ 432 public void setBottomSheetCallback(BottomSheetCallback callback) { 433 mCallback = callback; 434 } 435 436 /** 437 * Sets the state of the bottom sheet. The bottom sheet will transition to that state with 438 * animation. 439 * 440 * @param state One of {@link #STATE_COLLAPSED}, {@link #STATE_EXPANDED}, or 441 * {@link #STATE_HIDDEN}. 442 */ 443 public final void setState(@State int state) { 444 if (mViewRef == null) { 445 // The view is not laid out yet; modify mState and let onLayoutChild handle it later 446 if (state == STATE_COLLAPSED || state == STATE_EXPANDED || 447 (mHideable && state == STATE_HIDDEN)) { 448 mState = state; 449 } 450 return; 451 } 452 V child = mViewRef.get(); 453 if (child == null) { 454 return; 455 } 456 int top; 457 if (state == STATE_COLLAPSED) { 458 top = mMaxOffset; 459 } else if (state == STATE_EXPANDED) { 460 top = mMinOffset; 461 } else if (mHideable && state == STATE_HIDDEN) { 462 top = mParentHeight; 463 } else { 464 throw new IllegalArgumentException("Illegal state argument: " + state); 465 } 466 setStateInternal(STATE_SETTLING); 467 if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { 468 ViewCompat.postOnAnimation(child, new SettleRunnable(child, state)); 469 } 470 } 471 472 /** 473 * Gets the current state of the bottom sheet. 474 * 475 * @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING}, 476 * and {@link #STATE_SETTLING}. 477 */ 478 @State 479 public final int getState() { 480 return mState; 481 } 482 483 private void setStateInternal(@State int state) { 484 if (mState == state) { 485 return; 486 } 487 mState = state; 488 View bottomSheet = mViewRef.get(); 489 if (bottomSheet != null && mCallback != null) { 490 mCallback.onStateChanged(bottomSheet, state); 491 } 492 } 493 494 private void reset() { 495 mActivePointerId = ViewDragHelper.INVALID_POINTER; 496 if (mVelocityTracker != null) { 497 mVelocityTracker.recycle(); 498 mVelocityTracker = null; 499 } 500 } 501 502 private boolean shouldHide(View child, float yvel) { 503 if (child.getTop() < mMaxOffset) { 504 // It should not hide, but collapse. 505 return false; 506 } 507 final float newTop = child.getTop() + yvel * HIDE_FRICTION; 508 return Math.abs(newTop - mMaxOffset) / (float) mPeekHeight > HIDE_THRESHOLD; 509 } 510 511 private View findScrollingChild(View view) { 512 if (view instanceof NestedScrollingChild) { 513 return view; 514 } 515 if (view instanceof ViewGroup) { 516 ViewGroup group = (ViewGroup) view; 517 for (int i = 0, count = group.getChildCount(); i < count; i++) { 518 View scrollingChild = findScrollingChild(group.getChildAt(i)); 519 if (scrollingChild != null) { 520 return scrollingChild; 521 } 522 } 523 } 524 return null; 525 } 526 527 private float getYVelocity() { 528 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 529 return VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId); 530 } 531 532 private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { 533 534 @Override 535 public boolean tryCaptureView(View child, int pointerId) { 536 if (mState == STATE_DRAGGING) { 537 return false; 538 } 539 if (mTouchingScrollingChild) { 540 return false; 541 } 542 if (mState == STATE_EXPANDED && mActivePointerId == pointerId) { 543 View scroll = mNestedScrollingChildRef.get(); 544 if (scroll != null && ViewCompat.canScrollVertically(scroll, -1)) { 545 // Let the content scroll up 546 return false; 547 } 548 } 549 return mViewRef != null && mViewRef.get() == child; 550 } 551 552 @Override 553 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 554 dispatchOnSlide(top); 555 } 556 557 @Override 558 public void onViewDragStateChanged(int state) { 559 if (state == ViewDragHelper.STATE_DRAGGING) { 560 setStateInternal(STATE_DRAGGING); 561 } 562 } 563 564 @Override 565 public void onViewReleased(View releasedChild, float xvel, float yvel) { 566 int top; 567 @State int targetState; 568 if (yvel < 0) { // Moving up 569 top = mMinOffset; 570 targetState = STATE_EXPANDED; 571 } else if (mHideable && shouldHide(releasedChild, yvel)) { 572 top = mParentHeight; 573 targetState = STATE_HIDDEN; 574 } else if (yvel == 0.f) { 575 int currentTop = releasedChild.getTop(); 576 if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) { 577 top = mMinOffset; 578 targetState = STATE_EXPANDED; 579 } else { 580 top = mMaxOffset; 581 targetState = STATE_COLLAPSED; 582 } 583 } else { 584 top = mMaxOffset; 585 targetState = STATE_COLLAPSED; 586 } 587 if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) { 588 setStateInternal(STATE_SETTLING); 589 ViewCompat.postOnAnimation(releasedChild, 590 new SettleRunnable(releasedChild, targetState)); 591 } else { 592 setStateInternal(targetState); 593 } 594 } 595 596 @Override 597 public int clampViewPositionVertical(View child, int top, int dy) { 598 return MathUtils.constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset); 599 } 600 601 @Override 602 public int clampViewPositionHorizontal(View child, int left, int dx) { 603 return child.getLeft(); 604 } 605 606 @Override 607 public int getViewVerticalDragRange(View child) { 608 if (mHideable) { 609 return mParentHeight - mMinOffset; 610 } else { 611 return mMaxOffset - mMinOffset; 612 } 613 } 614 }; 615 616 private void dispatchOnSlide(int top) { 617 View bottomSheet = mViewRef.get(); 618 if (bottomSheet != null && mCallback != null) { 619 if (top > mMaxOffset) { 620 mCallback.onSlide(bottomSheet, (float) (mMaxOffset - top) / mPeekHeight); 621 } else { 622 mCallback.onSlide(bottomSheet, 623 (float) (mMaxOffset - top) / ((mMaxOffset - mMinOffset))); 624 } 625 } 626 } 627 628 private class SettleRunnable implements Runnable { 629 630 private final View mView; 631 632 @State 633 private final int mTargetState; 634 635 SettleRunnable(View view, @State int targetState) { 636 mView = view; 637 mTargetState = targetState; 638 } 639 640 @Override 641 public void run() { 642 if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) { 643 ViewCompat.postOnAnimation(mView, this); 644 } else { 645 setStateInternal(mTargetState); 646 } 647 } 648 } 649 650 protected static class SavedState extends View.BaseSavedState { 651 652 @State 653 final int state; 654 655 public SavedState(Parcel source) { 656 super(source); 657 //noinspection ResourceType 658 state = source.readInt(); 659 } 660 661 public SavedState(Parcelable superState, @State int state) { 662 super(superState); 663 this.state = state; 664 } 665 666 @Override 667 public void writeToParcel(Parcel out, int flags) { 668 super.writeToParcel(out, flags); 669 out.writeInt(state); 670 } 671 672 public static final Parcelable.Creator<SavedState> CREATOR = 673 new Parcelable.Creator<SavedState>() { 674 @Override 675 public SavedState createFromParcel(Parcel source) { 676 return new SavedState(source); 677 } 678 679 @Override 680 public SavedState[] newArray(int size) { 681 return new SavedState[size]; 682 } 683 }; 684 } 685 686 /** 687 * A utility function to get the {@link BottomSheetBehavior} associated with the {@code view}. 688 * 689 * @param view The {@link View} with {@link BottomSheetBehavior}. 690 * @return The {@link BottomSheetBehavior} associated with the {@code view}. 691 */ 692 @SuppressWarnings("unchecked") 693 public static <V extends View> BottomSheetBehavior<V> from(V view) { 694 ViewGroup.LayoutParams params = view.getLayoutParams(); 695 if (!(params instanceof CoordinatorLayout.LayoutParams)) { 696 throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); 697 } 698 CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) 699 .getBehavior(); 700 if (!(behavior instanceof BottomSheetBehavior)) { 701 throw new IllegalArgumentException( 702 "The view is not associated with BottomSheetBehavior"); 703 } 704 return (BottomSheetBehavior<V>) behavior; 705 } 706 707} 708