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