android5.0协调布局CoordinatorLayout(第三篇BottomSheetBehavior源码详解)

在开始看源码之前,咱们先来重温一下RecycleView是怎么将触摸事件传到CoordinatorLayout问它消不消耗事件的!根据事件的分发机制是从Activity的(dispatchTouchEvent)事件开始传入给PhoneWindowDecorView的(dispatchTouchEvent),传入口如下:

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

如果DecorView里面有消耗事件的控件存在,则由它消耗接下来的事件,如果没有,最后事件交个ActivityonTouchEvent来消耗,传入口如下:

   public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

最后通过DecorViewsuperDispatchTouchEvent进入ViewGroupdispatchTouchEvent分发事件中开始分发事件

 public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

那么接下里就开始从View树的根部一级一级的开始传分发事件,如果点击事件是在ViewGroup类型的View的范围内将会触发自身分发事件中去调用onInterceptTouchEvent,如果返回true的话事件将会被当前的ViewGroup消耗,当然子View可以调用requestDisallowInterceptTouchEvent方法让父View不要拦截事件,接下来的事件直接传到它的onTouchEvent方法里面进行处理,如果拦截事件返回false则接着向下传递,最终执行到最小的叶子节点的onTouchEvent方法处理,如果返回true则这个叶子View消耗接下来的事件,如果返回false,则传给它的父View进行onTouchEvent进行处理,以此类推,最终没有View消耗事件,则传到ActivityonTouchEvent处理。

那么接下来看一下RecycleView怎么将事件传递给CoordinatorLayout,因为CoordinatorLayout 包裹RecycleView,所以事件首先传到CoordinatorLayout,CoordinatorLayout没有重写父类ViewGroupdispatchTouchEvent方法,则事件分发到它的时候走正常的分发事件,那么接下来它会判断onInterTouchEvent是否会拦截

 public boolean onInterceptTouchEvent(MotionEvent ev) {
        MotionEvent cancelEvent = null;
       省略若干行...
执行绑定了behavier的子View的拦截事件
**/
        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
  省略若干行...
        return intercepted;
    }

它的拦截事件完全交给它的子View的Behavior行为接口来处理,也就是说CoordinatorLayout是否会消耗掉事件完全由用户自定义的BeHavior来处理,MD风格的组件库自带的两个BeHaviorBottomSheetBehaviorScrollingViewBehavior,BottomSheetBehavior实现了onInterceptTouchEvent,在生明了BottomSheetBehavior的View,如果它实现了NestedScrollingChild接口的话,或者它有子类实现了这个接口的话并且触摸事件在这个子View内将不会拦截这个事件,也就是说RecycleView直接实现BottomSheetBehavior行为,分发事件将交由RecycleView处理,或者它的父类实现的话,触摸事件只要在RecycleView控件内都分发事件都将进入RecycleView中,换句话说如果实现BottomSheetBehavior行为的不是RecycleView的话,事件将会交于BottomSheetBehavior处理,(这里指的是移动事件的拦截,子View设置点击事件还是可以正常触发的);ScrollingViewBehavior并未实现拦截事件所以最终滑动事件还是交给RecycleView来处理。

那么只要是NestedScrollingChild接口的对象,滑动事件首先都是经过自身,也就是说滑动事件是RecycleView-》CoordinatorLayout,怎么到的?这里是通过NestedScrollingParent(现在源码中用NestedScrollingParent2(它继承NestedScrollingParent))接口来回调的,也就是CoordinatorLayout实现了这个接口,RecycleView在滑动的时候利用这个接口将滑动距离交给CoordinatorLayout,CoordinatorLayout再交给它所有子View的Behavier来处理这个距离,取所有处理距离的最大值返回给RecycleView,RecycleView滑动剩下的距离。NestedScrollingParentNestedScrollingChild用法,大家请查一下资料这里不再详细讨论。下面列出NestedScrollingParent接口的方法的用法

public interface NestedScrollingParent {
     /**在Recycleview拦截事件down当中就调用了(onTouchevent事件的down也会调用)
     nestedScrollAxes:1是水平滑动,2是垂直滑动澹(滑动标记)
     target是RecycleView,而child是继承NestedScrollingParent接口的直接子View**/
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

     onNestedScrollAccepted方法在onStartNestedScroll之后调用,可以做一些初始化的工作
     **/
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

     拦截事件中MotionEvent.ACTION_UP调用,Recylview拦截事件处理的时候调用,ACTION_CANCEL的时候调用**/
    public void onStopNestedScroll(View target);

     /**
     onNestedScroll:recycleView滑动之后调用的方法
    target:recycleView,dxConsumed:RecycleView已经滚动的距离 ,dxUnconsumed:还未滚动的距离**/
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
  
     /**
     velocityX,velocityY:速度
     target:RecycleView
     **/
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
     /**
     返回滚动类型是垂直还是水平
     **/
    public int getNestedScrollAxes();

只要是实现了BottomSheetBehavior行为的子View,都可以快速的实现底部抽屉的功能,那么通过上面的分析咱们可以了解到,

如果实现的ViewRecycleViewNestedScrollView等实现NestedScrollingChild接口的时候,滑动的View会先将事件交给CoordinatorLayout,CoordinatorLayout再交给BottomSheetBehavior;相反的,实现BottomSheetBehavior的View是普通的View的话,事件的处理直接是CoordinatorLayout通过onInterceptTouchEventonTouchEvent交给BottomSheetBehavior,当然Behavior这个类已经生明了这些方法,你要自定义的话,只要实现就好了。那么接下来的重点就是直接看BottomSheetBehavior就好了。

现在先来分析第一条线:RecycleView-》CoordinatorLayout-》BottomSheetBehavior;首先第一个问题为什么生明了BottomSheetBehaviorView会自动跑到底部?CoordinatorLayout在布局子View的时候(在onLayout方法中)会触发Behavior的onLayoutchild方法看子View是否能确定自己的位置,否则CoordinatorLayout帮它确定位置,这里先来了解一下这面的属性

app:behavior_peekHeight 抽屉处在关闭的时候的显示高度

app:behavior_hideable:抽屉是否可以隐藏(就是完全滑出屏幕)

app:behavior_skipCollapsed:是否跳过折叠状态,跳过折叠状态直接滑出屏幕

BottomSheetDialog的app:behavior_hideableapp:behavior_skipCollapsed属性都为true,所以说可以滑出屏幕。

接下来看一下onLayoutchild方法

public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
        if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
            ViewCompat.setFitsSystemWindows(child, true);
        }
		//这里的child是实现了BottomSheetBehavior的child
        int savedTop = child.getTop();
        // First let the parent lay it out
		//先调用CoordinatorLayout默认的排版布局
        parent.onLayoutChild(child, layoutDirection);
        // Offset the bottom sheet
        mParentHeight = parent.getHeight();
		//计算最小的偏移量CoordinatorLayout的高度减去child的高度》=0
        mMinOffset = Math.max(0, mParentHeight - child.getHeight());
		//计算最大的偏移量CoordinatorLayout高度减去你设置的属性的高度mPeekHeight
        mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);
		//mState默认状态是折叠,而BottomSheetDilog的默认状态是隐藏(STATE_HIDDEN)
		//如果是展开设置最小偏移量
        if (mState == STATE_EXPANDED) {
            ViewCompat.offsetTopAndBottom(child, mMinOffset);
        } else if (mHideable && mState == STATE_HIDDEN) {
		//如果是隐藏直接将top设置为CoordinatorLayout的高度
            ViewCompat.offsetTopAndBottom(child, mParentHeight);
        } 
		//如果是折叠状态top的值为mParentHeight - mPeekHeight
		else if (mState == STATE_COLLAPSED) {
            ViewCompat.offsetTopAndBottom(child, mMaxOffset);
        } else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) {
		//如果是拖动或滑动惯性不移动
            ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
        }
	//创建ViewDragHelper
        if (mViewDragHelper == null) {
            mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
        }
		//将child保存到弱引用中
        mViewRef = new WeakReference<>(child);
		//将child中的滑动view或自己保存到弱引用mNestedScrollingChildRef
        mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
        return true;
    }

这里介绍一下BottomSheetBehavior的所有状态:

拖动状态,当前View处在拖动中
    STATE_DRAGGING = 1;

    /**
     * 当前View处在惯性中(手指抬起还在惯性滑动)
     */
    STATE_SETTLING = 2;

    /**
     * 当前View是展开状态
     */
    public static final int STATE_EXPANDED = 3;

    /**
     * 当前View 是关闭状态.
     */
    STATE_COLLAPSED = 4;

    /**
     * 当前View是隐藏状态
     */
     STATE_HIDDEN = 5;

 

如果不设置BottomSheetBehavior的状态的话,默认是STATE_COLLAPSED状态,那么onLayoutChild方法就会将子View的Top设置为你设置的CoordinatorLayout的高度减去app:behavior_peekHeight的高度,最终我们在手机上看到的View就是只显示了app:behavior_peekHeight高度的View,源码通过ViewCompat.offsetTopAndBottom(child, mParentHeight);实现。

ok,第一个问题解决了,接下来是手指滑动RecycleView先滑动到展开位置,然后再滑动自身的效果是怎么产生的?

首先在RecycleView监听的down的时候先通过onStartNestedScroll回调到CoordinatorLayout的onStartNestedScroll最后传给子View的BehavioronStartNestedScroll,如下:

 public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child,
            View directTargetChild, View target, int nestedScrollAxes) {
        mLastNestedScrollDy = 0;
        mNestedScrolled = false;
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

BottomSheetBehavior的这个方法只要RecycleView的是垂直滚动的的那么就会返回true,如果这个方法不返回true接下来的回调方法就不会执行,如下代码注释处

public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
			//这里将onStartNestedScroll返回的true设置给LayoutParams做为接下来的判断
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }

正常情况下调用完onStartNestedScroll之后,接下来回调的就是onNestedScrollAccepted方法,这个方法可以设置自己需要用到的变量,而BottomSheetBehavior没有实现这个方法,假如触发了垂直滑动的最小距离则会回调BottomSheetBehavior的onNestedPreScroll方法

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx,
            int dy, int[] consumed) {
        View scrollingChild = mNestedScrollingChildRef.get();
        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
           //朝下滑的时候,先判断邋RecycleView是否滑动到底部
            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);
                }
            }
        }
        dispatchOnSlide(child.getTop());
        mLastNestedScrollDy = dy;
        mNestedScrolled = true;
    }

这个方法的意思就是,如果手指向上滑动的话,如果当前的View的top没有到扩展的状态的话(CoordinatorLayout的高度减去自己的高度的话将消耗掉手指滑动的距离,从而RecycleView将不滑动只改变他的Top值);如果手指朝下滑动并且当前的子View没的top没有到关闭状态则消耗掉手指滑动的距离(看起来RecycleView没有滑动,当时位置改变了);其他的情况则执行RecycleView的滑动,这就是上面说的那个问题的原因了。

在手指抬起来之后RecycleView会判断手速如果速度达到它认为的最低速度的话将会回调onNestedPreFling方法,onNestedPreFling方法返回false才会回调onNestedFling方法,也就是说这两个方法你都可以在里面实现惯性滑动逻辑,BottomSheetBehavior的onNestedPreFling方法如下:

public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
            float velocityX, float velocityY) {
        return target == mNestedScrollingChildRef.get() &&
                (mState != STATE_EXPANDED ||
                        super.onNestedPreFling(coordinatorLayout, child, target,
                                velocityX, velocityY));
    }

    /**

这个方法只有在扩展的状态和mNestedScrollingChildRef中的滑动View不为null时返回true,BottomSheetBehavior没有实现onNestedFling方法,也就是说BottomSheetBehavior不处理自然滑动

当自然滑动也处理完了之后,会调用onStopNestedScroll这个方法,这里需要在这个方法最终确定BottomSheetBehavior状态,代码如下:

 public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
   //如果子View的top值已经是mMinOffset,则直接返回,设置当前状态为展开
        if (child.getTop() == mMinOffset) {
            setStateInternal(STATE_EXPANDED);
            return;
        }
	//如果没有滑动的View则直接返回,或者view已经被回收了
        if (mNestedScrollingChildRef == null || target != mNestedScrollingChildRef.get()
                || !mNestedScrolled) {
            return;
        }
        int top;
        int targetState;
		//mLastNestedScrollDy最后一次滑动的距离>0向上滑动
        if (mLastNestedScrollDy > 0) {
            top = mMinOffset;
            targetState = STATE_EXPANDED;
        } 
		//只有设置了那两个参数的情况下,状态才设置为隐藏
		else if (mHideable && shouldHide(child, getYVelocity())) {
            top = mParentHeight;
            targetState = STATE_HIDDEN;
        } 
		else if (mLastNestedScrollDy == 0) {
	    //子View离那边近就设置那种状态
            int currentTop = child.getTop();
            if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
                top = mMinOffset;
                targetState = STATE_EXPANDED;
            } else {
                top = mMaxOffset;
                targetState = STATE_COLLAPSED;
            }
        } else {
		//朝下滑动则设置状态为关闭状态
            top = mMaxOffset;
            targetState = STATE_COLLAPSED;
        }
		//进行子View的Top偏移
        if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
            setStateInternal(STATE_SETTLING);
            ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
        } else {
            setStateInternal(targetState);
        }
        mNestedScrolled = false;
		}

这个方法也很简单,就是根据mLastNestedScrollDy(记录最后滑动的距离)来确定BottomSheetBehavior的最后的状态,那么很

如果mLastNestedScrollDy大于0(向上滑动),那么最后的状态就是展开状态,子View将top变为展开的值,同理如果小于0则变为关闭状态,如果等于0的话状态就变成离那个状态近变成那个状态。

好了,现在第一条线已经走完了,接下来走第二条线CoordinatorLayout通过onInterceptTouchEventonTouchEvent交给BottomSheetBehavior。这种场景就是你实现的BottomSheetBehavior的View不是滑动的View(实现ScrollingView接口的View),或者子类中没有这种View,或者触发事件不在这个View区域内。BottomSheetBehavior的拦截实现方法如下

public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
            省略若干行....
            case MotionEvent.ACTION_DOWN:
                int initialX = (int) event.getX();
                mInitialY = (int) event.getY();
			//如果是普通的View的话scroll为null
                View scroll = mNestedScrollingChildRef != null
                        ? mNestedScrollingChildRef.get() : null;
                if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) {
                    mActivePointerId = event.getPointerId(event.getActionIndex());
                    mTouchingScrollingChild = true;
                }
				//但是触摸事件又在当前的View中的话mIgnoreEvents还是为false
                mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID &&
                        !parent.isPointInChildBounds(child, initialX, mInitialY);
                break;
        }
		//不是recycleView等滑动事件直接返回true拦截,这里滑动通过ViewDragHelper来实现
        if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) {
            return true;
        }
        // We have to handle cases that the ViewDragHelper does not capture the bottom sheet because
        // it is not the top most view of its parent. This is not necessary when the touch event is
        // happening over the scrolling content as nested scrolling logic handles that case.
        View scroll = mNestedScrollingChildRef.get();
		//不管那种情况走到这都返回false
        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();
    }

这里利用了ViewDragHelper工具类来实现拖动,如果持有BottomSheetBehavior的不是ScrollingView那么mViewDragHelper.shouldInterceptTouchEvent(event)方法返回true的话将拦截事件交给BottomSheetBehavior的onTouchEvent的方法来实现拖动,先来看一下shouldInterceptTouchEvent方法实现:

   public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
           //省略若干行.....
            case MotionEvent.ACTION_MOVE: {
			 //省略若干行.....
                    if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                        break;
                    }
                }
                saveLastMotion(ev);
                break;
            }
            //省略若干行.....

			return  return mDragState == STATE_DRAGGING;

这里只要满足ViewDragHelper状态为STATE_DRAGGING就会进行拦截,然而要满足这个状态最重要的就是tryCaptureViewForDrag方法是否满足,代码如下:

boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
//当前的View就是拖扯的View直接返回true
        if (toCapture == mCapturedView && mActivePointerId == pointerId) {
            // Already done!
            return true;
        }
		//toCapture为当前触摸事件所在区域的子View
        if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
            mActivePointerId = pointerId;
            captureChildView(toCapture, pointerId);
            return true;
        }
        return false;
    }

这边如果还没有可拖扯的控件则通过mCallback.tryCaptureView来判断是否可以拖扯,也就是是否可以拦截,那么只要它满足条件事件将交于BottomSheetBehavior处理,代码如下:

 public boolean tryCaptureView(View child, int pointerId) {
            if (mState == STATE_DRAGGING) {
                return false;
            }
            if (mTouchingScrollingChild) {
                return false;
            }
            if (mState == STATE_EXPANDED && mActivePointerId == pointerId) {
                View scroll = mNestedScrollingChildRef.get();
                if (scroll != null && scroll.canScrollVertically(-1)) {
                    // Let the content scroll up
                    return false;
                }
            }
            return mViewRef != null && mViewRef.get() == child;
        }

可以看出如果此View是ScrollingView直接返回false,不拦截,如果已经是拖扯状态不拦截,那么只有当前View不是ScrollingView的情况下将实现拦截,从中我们得出一个结论,如果自己使用ViewDragHelper的时候,重写方法之一就是tryCaptureView方法(确定什么情况下该view可拖扯),最后来看一下BottomSheetBehavior的OntouchEvent事件:

public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
 //省略若干行....
     /**
	 事件处理交给ViewDragHelper来处理
	 **/

        if (mViewDragHelper != null) {
            mViewDragHelper.processTouchEvent(event);
        }
        // Record the velocity
        if (action == MotionEvent.ACTION_DOWN) {
            reset();
        }
       
        return !mIgnoreEvents;
    }

很显然事件处理它完全交给了ViewDragHelper.processTouchEvent(event)来处理。接下来介绍一下ViewDragHelper的使用

第一步创建ViewDragHelper:// 参数2是拖动的灵敏度一般设为1,第一个参数为当前绑定的View,第三个参数需要我们写的回调接口
        
                dragHelper = ViewDragHelper.create(this, 1,
                        new ViewDragHelperCallback());

第二步绑定拦截://绑定拖动手势
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        return dragHelper.shouldInterceptTouchEvent(ev);
    }

第三步onTouchEvent自动化处理:public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        dragHelper.processTouchEvent(event);
        return true;
    }

class ViewDragHelperCallback extends Callback {
		/**
		 * 此方法返回子view是否可以拖动 true可拖动
		 */
		@Override
		public boolean tryCaptureView(View arg0, int arg1) {
			// TODO Auto-generated method stub
			return true;
		}

		/**
		 * clampViewPositionHorizontal调用后调用onViewPositionChanged(移动距离由DragViewHelp自己来移动),当view的位置变化的时候的调用,可以在一个View中带动另外一个View
		 */
		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			
		}
// 手指拖动的时候先调用它,来确定最大的Top的边界
public int clampViewPositionVertical(View child, int left, int dx) {
}
		// 手指拖动的时候先调用它,来确定最大的left的边界
		@Override
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			// TODO Auto-generated method stub
			//边界检查假如左边可以拖车的最大位置
			
			
			return left;
		}
		/**
		 * 这两个方法不影响子控件的点击事件
		 */
		/*public int getViewHorizontalDragRange(View child)
		{
		     return getMeasuredWidth()-child.getMeasuredWidth();
		}
       //返回垂直拖动的范围
		@Override
		public int getViewVerticalDragRange(View child)
		{
		     return getMeasuredHeight()-child.getMeasuredHeight();
		}*/
		// 手指抬起时的()回调方法,ACTION_UP和ACTION_CANCEL后的回调方法来处理惯性滑动(最终确定View的状态)
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			// TODO Auto-generated method stub
		
		
			}

		}

	}

接下来看一下BottomSheetBehavior实现的ViewDragHelper的回调方法,怎么实现拖动的,先确定子View可以拖扯的边界:

  public int clampViewPositionVertical(View child, int top, int dy) {
            return MathUtils.clamp(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
        }

很显然和最大值和最小值做比较,也就是展开的高度和收缩的高度做比较,给出需要移动的合理值,那么ViewDragHelper就帮你自动移动了,最后手指释放时确定BottomSheetBehavior所属的状态:

  public void onViewReleased(View releasedChild, float xvel, float yvel) {
            int top;
            @State int targetState;
            if (yvel < 0) { // Moving up
                top = mMinOffset;
                targetState = STATE_EXPANDED;
            } else if (mHideable && shouldHide(releasedChild, yvel)) {
                top = mParentHeight;
                targetState = STATE_HIDDEN;
            } else if (yvel == 0.f) {
                int currentTop = releasedChild.getTop();
                if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
                    top = mMinOffset;
                    targetState = STATE_EXPANDED;
                } else {
                    top = mMaxOffset;
                    targetState = STATE_COLLAPSED;
                }
            } else {
                top = mMaxOffset;
                targetState = STATE_COLLAPSED;
            }
            if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) {
                setStateInternal(STATE_SETTLING);
                ViewCompat.postOnAnimation(releasedChild,
                        new SettleRunnable(releasedChild, targetState));
            } else {
                setStateInternal(targetState);
            }
        }

这个代码就是根据当前的y速度的正负值来判断偏向与那边滑动,假如偏向上方滑动,则最后是展开状态,偏向下方滑动最后是关闭状态。如果y=0则看最后View的top是偏向与展开状态的值还是关闭状态的值从而确定状态,最后通过mViewDragHelper.settleCapturedViewAt来实现最后的惯性滚动,这就是非ScrollingView实现BottomSheetBehavior的滚动效果实现的(第二条线的实现)逻辑了。

ok,到这里BottomSheetBehavior源码就介绍完了。

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值