mbcsdn 2019-06-28
01.SnapHelper简单介绍
02.SnapHelper源码分析
03.LinearSnapHelper源码分析
04.自定义SnapHelper类
查阅可知,SnapHelper继承自RecyclerView.OnFlingListener,并且重写了onFling方法,这个类代码并不多,下面会对重要方法一一解析。
支持SnapHelper的RecyclerView.LayoutManager必须实现的方式:
SnapHelper类重要的方法
什么是Fling操作
最简单的使用就是,如下代码
private void initRecyclerView() { LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(manager); LinearSnapHelper snapHelper = new LinearSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView); SnapAdapter adapter = new SnapAdapter(this); mRecyclerView.setAdapter(adapter); adapter.addAll(getData()); }
最简单的使用就是,如下代码
private void initRecyclerView() { LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(manager); PagerSnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView); SnapAdapter adapter = new SnapAdapter(this); mRecyclerView.setAdapter(adapter); adapter.addAll(getData()); }
通过attachToRecyclerView方法将SnapHelper attach 到RecyclerView,看一下这个方法的源代码
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { if (mRecyclerView == recyclerView) { return; // nothing to do } if (mRecyclerView != null) { destroyCallbacks(); } mRecyclerView = recyclerView; if (mRecyclerView != null) { setupCallbacks(); mGravityScroller = new Scroller(mRecyclerView.getContext(), new DecelerateInterpolator()); snapToTargetExistingView(); } }
接着看看setupCallbacks()源码
private void setupCallbacks() throws IllegalStateException { if (mRecyclerView.getOnFlingListener() != null) { throw new IllegalStateException("An instance of OnFlingListener already set."); } mRecyclerView.addOnScrollListener(mScrollListener); mRecyclerView.setOnFlingListener(this); }
接着看看snapToTargetExistingView()方法
void snapToTargetExistingView() { if (mRecyclerView == null) { return; } 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]); } }
然后来看一下mScrollListener监听里面做了什么
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; } } };
calculateDistanceToFinalSnap抽象方法
计算最终对齐要移动的距离
@SuppressWarnings("WeakerAccess") @Nullable public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView);
findSnapView抽象方法
找到要对齐的View
@SuppressWarnings("WeakerAccess") @Nullable public abstract View findSnapView(LayoutManager layoutManager);
findTargetSnapPosition抽象方法
找到需要对齐的目标View的的Position。
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY);
SnapHelper继承了 RecyclerView.OnFlingListener,实现了onFling方法。
@Override public boolean onFling(int velocityX, int velocityY) { 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); }
接着看看snapFromFling方法源代码,就是通过该方法实现平滑滚动并使得在滚动停止时itemView对齐到目的坐标位置
private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof ScrollVectorProvider)) { return false; } RecyclerView.SmoothScroller smoothScroller = createSnapScroller(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; }
接着看下createSnapScroller这个方法源码
在创建该LinearSmoothScroller的时候主要考虑两个方面:
@Nullable protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) { if (!(layoutManager instanceof ScrollVectorProvider)) { return null; } return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView); final int dx = snapDistances[0]; final int dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); if (time > 0) { action.update(dx, dy, time, mDecelerateInterpolator); } } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; }
calculateDistanceToFinalSnap源码如下所示
@Override public int[] calculateDistanceToFinalSnap( @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }
接着看看distanceToCenter方法
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2); final int containerCenter; if (layoutManager.getClipToPadding()) { containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { containerCenter = helper.getEnd() / 2; } return childCenter - containerCenter; }
也就是找到要对齐的View
@Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findCenterView(layoutManager, getVerticalHelper(layoutManager)); } else if (layoutManager.canScrollHorizontally()) { return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); } return null; }
接着看看findCenterView方法源代码
@Nullable private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } View closestChild = null; final int center; if (layoutManager.getClipToPadding()) { center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { center = helper.getEnd() / 2; } int absClosest = Integer.MAX_VALUE; for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2); int absDistance = Math.abs(childCenter - center); /** if child center is closer than previous closest, set it as closest **/ if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } return closestChild; }
LinearSnapHelper实现了SnapHelper,来看一下在findTargetSnapPosition操作了什么
@Override public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return RecyclerView.NO_POSITION; } final int itemCount = layoutManager.getItemCount(); if (itemCount == 0) { return RecyclerView.NO_POSITION; } final View currentView = findSnapView(layoutManager); if (currentView == null) { return RecyclerView.NO_POSITION; } final int currentPosition = layoutManager.getPosition(currentView); if (currentPosition == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; // deltaJumps sign comes from the velocity which may not match the order of children in // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to // get the direction. PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); if (vectorForEnd == null) { // cannot get a vector for the given position. return RecyclerView.NO_POSITION; } int vDeltaJump, hDeltaJump; if (layoutManager.canScrollHorizontally()) { hDeltaJump = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0); if (vectorForEnd.x < 0) { hDeltaJump = -hDeltaJump; } } else { hDeltaJump = 0; } if (layoutManager.canScrollVertically()) { vDeltaJump = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY); if (vectorForEnd.y < 0) { vDeltaJump = -vDeltaJump; } } else { vDeltaJump = 0; } int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump; if (deltaJump == 0) { return RecyclerView.NO_POSITION; } int targetPos = currentPosition + deltaJump; if (targetPos < 0) { targetPos = 0; } if (targetPos >= itemCount) { targetPos = itemCount - 1; } return targetPos; }
如何创建OrientationHelper对象呢?如下所示
@NonNull private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); } return mVerticalHelper; } @NonNull private OrientationHelper getHorizontalHelper( @NonNull RecyclerView.LayoutManager layoutManager) { if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); } return mHorizontalHelper; }
如下所示
private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, OrientationHelper helper, int velocityX, int velocityY) { int[] distances = calculateScrollDistance(velocityX, velocityY); float distancePerChild = computeDistancePerChild(layoutManager, helper); if (distancePerChild <= 0) { return 0; } int distance = Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1]; return (int) Math.round(distance / distancePerChild); }
大概流程
重写calculateDistanceToFinalSnap方法
@Nullable @Override public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; } private int distanceToStart(View targetView, OrientationHelper helper) { return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); }
写findSnapView方法,找到当前时刻的SnapView
@Nullable @Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof LinearLayoutManager) { if (layoutManager.canScrollHorizontally()) { return findStartView(layoutManager, getHorizontalHelper(layoutManager)); } else { return findStartView(layoutManager, getVerticalHelper(layoutManager)); } } return super.findSnapView(layoutManager); } private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { if (layoutManager instanceof LinearLayoutManager) { int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); //需要判断是否是最后一个Item,如果是最后一个则不让对齐,以免出现最后一个显示不完全。 boolean isLastItem = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1; if (firstChild == RecyclerView.NO_POSITION || isLastItem) { return null; } View child = layoutManager.findViewByPosition(firstChild); if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2 && helper.getDecoratedEnd(child) > 0) { return child; } else { if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { return null; } else { return layoutManager.findViewByPosition(firstChild + 1); } } } return super.findSnapView(layoutManager); }
修改滚动速率
@Nullable protected LinearSmoothScroller createSnapScroller(final RecyclerView.LayoutManager layoutManager) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return null; } return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) { int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView); final int dx; final int dy; if (snapDistances != null) { dx = snapDistances[0]; dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); if (time > 0) { action.update(dx, dy, time, mDecelerateInterpolator); } } } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { //这个地方可以自己设置 return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; }