BottomSheetBehavior

来源:互联网 发布:开淘宝企业店需要什么 编辑:程序博客网 时间:2024/05/23 00:00

BottomSheetBehavior

标签(空格分隔): android


BottomSheetBehavior支持如下属性

<declare-styleable name="BottomSheetBehavior_Layout"><attr format="dimension" name="behavior_peekHeight">    <enum name="auto" value="-1"/></attr><attr format="boolean" name="behavior_hideable"/><attr format="boolean" name="behavior_skipCollapsed"/></declare-styleable>

含义

//折叠的高度app:behavior_peekHeight="10dp"          setPeekHeight//是否可以隐藏app:behavior_hideable="true"            setHideable//是否跳过折叠状态app:behavior_skipCollapsed="true"       setSkipCollapsed

首先加载BottomSheetBehavior,根据属性配置,设置能否隐藏,折叠高度,是否跳过折叠状态

public static <V extends View> BottomSheetBehavior<V> from(V view) {    ViewGroup.LayoutParams params = view.getLayoutParams();    if (!(params instanceof CoordinatorLayout.LayoutParams)) {        throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");    }    CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)            .getBehavior();    if (!(behavior instanceof BottomSheetBehavior)) {        throw new IllegalArgumentException(                "The view is not associated with BottomSheetBehavior");    }    return (BottomSheetBehavior<V>) behavior;}
public BottomSheetBehavior(Context context, AttributeSet attrs) {        super(context, attrs);        TypedArray a = context.obtainStyledAttributes(attrs,                R.styleable.BottomSheetBehavior_Layout);        TypedValue value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight);        if (value != null && value.data == PEEK_HEIGHT_AUTO) {            setPeekHeight(value.data);        } else {            setPeekHeight(a.getDimensionPixelSize(                    R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, PEEK_HEIGHT_AUTO));        }        setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideable, false));        setSkipCollapsed(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed,                false));        a.recycle();        ViewConfiguration configuration = ViewConfiguration.get(context);        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();    }

然后就是安排布局

@Overridepublic boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {    if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {        ViewCompat.setFitsSystemWindows(child, true);    }    int savedTop = child.getTop();    // First let the parent lay it out    parent.onLayoutChild(child, layoutDirection);    // Offset the bottom sheet    mParentHeight = parent.getHeight();    int peekHeight;    if (mPeekHeightAuto) {        if (mPeekHeightMin == 0) {            mPeekHeightMin = parent.getResources().getDimensionPixelSize(                    R.dimen.design_bottom_sheet_peek_height_min);        }        peekHeight = Math.max(mPeekHeightMin, mParentHeight - parent.getWidth() * 9 / 16);    } else {        peekHeight = mPeekHeight;    }    mMinOffset = Math.max(0, mParentHeight - child.getHeight());    mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset);    if (mState == STATE_EXPANDED) {        //如果展开        ViewCompat.offsetTopAndBottom(child, mMinOffset);    } else if (mHideable && mState == STATE_HIDDEN) {        //如果隐藏        ViewCompat.offsetTopAndBottom(child, mParentHeight);    } else if (mState == STATE_COLLAPSED) {        //如果折叠,则以折叠高度为准(peekHeight)        ViewCompat.offsetTopAndBottom(child, mMaxOffset);    } else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) {        //如果是拖动或固定(拖动才会有位置变化)        ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());    }    if (mViewDragHelper == null) {        mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);    }    mViewRef = new WeakReference<>(child);    mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));    return true;}

控制显示变化

public final void setState(final @State int state) {    if (state == mState) {        return;    }    if (mViewRef == null) {        // The view is not laid out yet; modify mState and let onLayoutChild handle it later        if (state == STATE_COLLAPSED || state == STATE_EXPANDED ||                (mHideable && state == STATE_HIDDEN)) {            mState = state;        }        return;    }    final V child = mViewRef.get();    if (child == null) {        return;    }    // Start the animation; wait until a pending layout if there is one.    ViewParent parent = child.getParent();    //如果已经requested并且附加到window    if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) {        child.post(new Runnable() {            @Override            public void run() {                startSettlingAnimation(child, state);            }        });    } else {        startSettlingAnimation(child, state);    }}

具体的启动改变,setStateInternal中回调了BottomSheetCallback#onStateChanged

void startSettlingAnimation(View child, int state) {        //先根据state设置好top位置        int top;        if (state == STATE_COLLAPSED) {            top = mMaxOffset;        } else if (state == STATE_EXPANDED) {            top = mMinOffset;        } else if (mHideable && state == STATE_HIDDEN) {            top = mParentHeight;        } else {            throw new IllegalArgumentException("Illegal state argument: " + state);        }        //先改为settling        setStateInternal(STATE_SETTLING);        //移动到指定位置后改为指定state        if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {            ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));        }    }

设置状态很容易踩坑,使用的时候需要注意。

mMinOffset = Math.max(0, mParentHeight - child.getHeight());mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset);

注意上面2句,在结合上面的具体移动。在没有peekHeight的时候STATE_COLLAPSED和STATE_EXPANDED这两个状态切换会出现mMinOffset==mMaxOffset的情况,也就是top没改变的情况,从而导致mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)返回false导致最后state状态更新失败,回到STATE_SETTLING。总之在切换状态时注意2个状态之间top是否会变化,如果不能引起变化,则都会回到STATE_SETTLING

关于事件拦截与处理,滑动交给ViewDragHelper处理

//寻找behavior是否包含有NestedScrollingChildprivate View findScrollingChild(View view) {    if (view instanceof NestedScrollingChild) {        return view;    }    if (view instanceof ViewGroup) {        ViewGroup group = (ViewGroup) view;        for (int i = 0, count = group.getChildCount(); i < count; i++) {            View scrollingChild = findScrollingChild(group.getChildAt(i));            if (scrollingChild != null) {                return scrollingChild;            }        }    }    return null;}
@Overridepublic boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {    //如果不可见不拦截    if (!child.isShown()) {        mIgnoreEvents = true;        return false;    }    int action = MotionEventCompat.getActionMasked(event);    // Record the velocity    if (action == MotionEvent.ACTION_DOWN) {        reset();    }    if (mVelocityTracker == null) {        mVelocityTracker = VelocityTracker.obtain();    }    mVelocityTracker.addMovement(event);    switch (action) {        //恢复一些默认标记        case MotionEvent.ACTION_UP:        case MotionEvent.ACTION_CANCEL:            mTouchingScrollingChild = false;            mActivePointerId = MotionEvent.INVALID_POINTER_ID;            // Reset the ignore flag            if (mIgnoreEvents) {                mIgnoreEvents = false;                return false;            }            break;        case MotionEvent.ACTION_DOWN:            int initialX = (int) event.getX();            mInitialY = (int) event.getY();            View scroll = mNestedScrollingChildRef.get();            //如果有ns控件,并处于控件内            if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) {                mActivePointerId = event.getPointerId(event.getActionIndex());                mTouchingScrollingChild = true;            }            //判断是否忽略            mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID &&                    !parent.isPointInChildBounds(child, initialX, mInitialY);            break;    }    //不忽略,VDH要处理,拦截    if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) {        return true;    }    //VDH不处理情况    View scroll = mNestedScrollingChildRef.get();    return action == MotionEvent.ACTION_MOVE && scroll != null &&            !mIgnoreEvents && mState != STATE_DRAGGING &&            !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) &&            Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop();}

关于滑动的协同处理,前提是behavior内有ns控件。

//只协同处理竖直方向的滑动@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child,        View directTargetChild, View target, int nestedScrollAxes) {    mLastNestedScrollDy = 0;    mNestedScrolled = false;    return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;}
//这个方法主要处理的滑动控制,当遇到ns控件时,怎样协同处理ns的滚动,和自身的折叠展开等状态的控制@Overridepublic void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx,        int dy, int[] consumed) {    View scrollingChild = mNestedScrollingChildRef.get();    //如果不是ns控件不是发起者,不预处理    if (target != scrollingChild) {        return;    }    int currentTop = child.getTop();    int newTop = currentTop - dy;    if (dy > 0) { // Upward        //如果上移超过了阈值,则以阈值为准        if (newTop < mMinOffset) {            //计算消耗,移动,设置状态            consumed[1] = currentTop - mMinOffset;            ViewCompat.offsetTopAndBottom(child, -consumed[1]);            //此时属于展开状态            setStateInternal(STATE_EXPANDED);        } else {            consumed[1] = dy;            ViewCompat.offsetTopAndBottom(child, -dy);            //属于拖动状态            setStateInternal(STATE_DRAGGING);        }    } else if (dy < 0) { // Downward        //需要检测是否可以垂直滚动,如NestedScrollView。如果可以滚动,则不预处理,先让其滚动        //直到不能滚动时,则预处理接管事件,进行拖动和折叠        if (!ViewCompat.canScrollVertically(target, -1)) {            if (newTop <= mMaxOffset || mHideable) {                consumed[1] = dy;                ViewCompat.offsetTopAndBottom(child, -dy);                setStateInternal(STATE_DRAGGING);            } else {                consumed[1] = currentTop - mMaxOffset;                ViewCompat.offsetTopAndBottom(child, -consumed[1]);                setStateInternal(STATE_COLLAPSED);            }        }    }    //回调BottomSheetCallback#onSlide    dispatchOnSlide(child.getTop());    mLastNestedScrollDy = dy;    mNestedScrolled = true;}
0 0