目录 概要 SnapHelper用于辅助RecyclerView在滚动结束时将Item对齐到某个位置。能让RecyclerView实现类似ViewPager等功能。
RecyclerView描述滚动状态的几个属性:
SCROLL_STATE_IDLE:滚动闲置状态,此时并没有手指滑动或者动画执行SCROLL_STATE_DRAGGING:滚动拖拽状态,由于用户触摸屏幕产生SCROLL_STATE_SETTLING:自动滚动状态,此时没有手指触摸,一般是由动画执行滚动到最终位置,包括smoothScrollTo等方法的调用当手指在屏幕上滑动RecyclerView然后松手,RecyclerView中的内容会顺着惯性继续往手指滑动的方向继续滚动直到停止,这个过程叫做Fling。
Fling 当触发MotionEvent.ACTION_UP时,RecyclerView会进行fling判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public boolean fling (int velocityX, int velocityY) { if (mLayout == null ) { Log.e(TAG, "Cannot fling without a LayoutManager set. " + "Call setLayoutManager with a non-null argument." ); return false ; } if (mLayoutFrozen) { return false ; } final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); final boolean canScrollVertical = mLayout.canScrollVertically(); if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { velocityX = 0 ; } if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { velocityY = 0 ; } if (velocityX == 0 && velocityY == 0 ) { return false ; } if (!dispatchNestedPreFling(velocityX, velocityY)) { final View firstChild = mLayout.getChildAt(0 ); final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1 ); boolean consumed = false ; if (velocityY < 0 ) { consumed = getChildAdapterPosition(firstChild) > 0 || firstChild.getTop() < getPaddingTop(); } if (velocityY > 0 ) { consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1 || lastChild.getBottom() > getHeight() - getPaddingBottom(); } dispatchNestedFling(velocityX, velocityY, consumed); if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { return true ; } final boolean canScroll = canScrollHorizontal || canScrollVertical; if (canScroll) { velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); /默认的Fling操作,最终到OverScroller计算滚动相关的值 mViewFlinger.fling(velocityX, velocityY); return true ; } } return false ; }
由源码可知,会来到SnapHelper进行判断。
SnapHelper SnapHelper是一个抽象类,具体实现有LinearSnapHelper、PagerSnapHelper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public abstract int [] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView); public abstract View findSnapView (RecyclerView.LayoutManager layoutManager) ;public abstract int findTargetSnapPosition (RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) ;
通过下面的代码,注册到RecyclerView中,可以实现相应的效果。
1 2 3 4 5 new LinearSnapHelper ().attachToRecyclerView(mRecyclerView);new PagerSnapHelper ().attachToRecyclerView(mRecyclerView);
来看看LinearSnapHelper的源码
依附RecyclerView流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public void attachToRecyclerView (@Nullable RecyclerView recyclerView) throws IllegalStateException { if (mRecyclerView == recyclerView) { return ; } if (mRecyclerView != null ) { destroyCallbacks(); } mRecyclerView = recyclerView; if (mRecyclerView != null ) { setupCallbacks(); mGravityScroller = new Scroller (mRecyclerView.getContext(), new DecelerateInterpolator ()); snapToTargetExistingView(); } } private void setupCallbacks () throws IllegalStateException { if (mRecyclerView.getOnFlingListener() != null ) { throw new IllegalStateException ("An instance of OnFlingListener already set." ); } mRecyclerView.addOnScrollListener(mScrollListener); mRecyclerView.setOnFlingListener(this ); } private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView .OnScrollListener() { boolean mScrolled = false ; @Override public void onScrollStateChanged (RecyclerView recyclerView, int newState) { super .onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { mScrolled = false ; snapToTargetExistingView(); } } @Override public void onScrolled (RecyclerView recyclerView, int dx, int dy) { if (dx != 0 || dy != 0 ) { mScrolled = true ; } } }; void snapToTargetExistingView () { if (mRecyclerView == null ) { return ; } RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null ) { return ; } View snapView = findSnapView(layoutManager); if (snapView == null ) { return ; } int [] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); if (snapDistance[0 ] != 0 || snapDistance[1 ] != 0 ) { mRecyclerView.smoothScrollBy(snapDistance[0 ], snapDistance[1 ]); } }
onFling 在RecyclerView中的fling方法中,如果依附来LinearSnapHelper,会调用LinearSnapHelper的onFling方法进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public boolean onFling (int velocityX, int velocityY) { RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null ) { return false ; } RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); if (adapter == null ) { return false ; } int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY); } private boolean snapFromFling (@NonNull RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return false ; } RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager); if (smoothScroller == null ) { return false ; } int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); if (targetPosition == RecyclerView.NO_POSITION) { return false ; } smoothScroller.setTargetPosition(targetPosition); layoutManager.startSmoothScroll(smoothScroller); return true ; }
总结 使用时使用attachToRecyclerView添加依附 onFling操作触发的时候首先通过findTargetSnapPosition找到最终需要滚动到的位置,然后启动平滑滚动器滚动到指定位置, 在指定位置找出来后,系统会回调onTargetFound,然后调用calculateDistanceToFinalSnap方法计算targetView需要减速滚动的距离,然后通过Action 更新给滚动器。 在滚动停止的时候,也就是state变成SCROLL_STATE_IDLE时会调用snapToTargetExistingView,通过findSnapView找到SnapView,然后通过 calculateDistanceToFinalSnap计算得到滚动的距离,做最后的对齐调整。 参考