A SnapHelper

1.1 binding RecyclerView

public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { if (mRecyclerView == recyclerView) {// recyclerView = recyclerView; // nothing to do } if (mRecyclerView ! = null) {//1. Remove the last listening callback destroyCallbacks(); } mRecyclerView = recyclerView; if (mRecyclerView ! = null) {//2. Set setupCallbacks(); mGravityScroller = new Scroller(mRecyclerView.getContext(), new DecelerateInterpolator()); SnapToTargetExistingView (); //3. }}Copy the code

Remove the last listener callback:

Private void destroyCallbacks () {/ / 1.1 remove the scroll to monitor mRecyclerView removeOnScrollListener (mScrollListener); / / slide monitoring mRecyclerView. 1.2 remove inertia setOnFlingListener (null); }Copy the code

Set listener:

private void setupCallbacks() throws IllegalStateException { ... / / 2.1. Add the scroll to monitor mRecyclerView addOnScrollListener (mScrollListener); / / 2.2 adding inertial sliding to monitor mRecyclerView setOnFlingListener (this); }Copy the code

Adsorbed to target location:

void snapToTargetExistingView() { ... View snapView = findSnapView(layoutManager); . / / 3.2 to calculate the distance the implementation adsorption need to scroll the int [] snapDistance = calculateDistanceToFinalSnap (layoutManager, snapView); if (snapDistance[0] ! = 0 || snapDistance[1] ! = 0) {/ / 3.3 call RecyclerView method of smooth scrolling mRecyclerView. SmoothScrollBy (snapDistance [0], snapDistance [1]). }}Copy the code

1.2 Adsorption realization when the rolling stops

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; // Snap snapToTargetExistingView() when scrolling stops; } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (dx ! = 0 || dy ! = 0) { mScrolled = true; }}};Copy the code

1.3 Inertial rolling adsorption realization

public boolean onFling(int velocityX, int velocityY) { ... / / 1 when the horizontal or vertical speed reaches inertial scrolling threshold, triggering adsorption 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) { ... / / 2 to create smooth scrolling RecyclerView. SmoothScroller SmoothScroller = createScroller (layoutManager); . Int TargetSnapPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); . / / 4. Through SmoothScroller SmoothScroller smooth scrolling to the target position. The setTargetPosition (targetPosition); layoutManager.startSmoothScroll(smoothScroller); return true; }Copy the code

Create a smooth scroller:

protected LinearSmoothScroller createSnapScroller( @NonNull RecyclerView.LayoutManager layoutManager) { ... return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { ... / / 2.1 according to the speed calculation of rolling distance (abstract methods for subclass implementation) 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) {//2.2 The scroll speed is fixed, only related to the screen density return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; }}; }Copy the code

Two LinearSnapHelper

2.1 Determine the distance to scroll according to the target view (called when the scrolling stops)

@Override public int[] calculateDistanceToFinalSnap( @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; / / 1.1 if support horizontal scrolling, calculate target view center to RecyclerView x difference, assigned to the out [0] the if (layoutManager. CanScrollHorizontally ()) {out [0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } / / 1.2 if support vertical scroll, the calculation of target view center to RecyclerView y difference, assigned to the out [1] the if (layoutManager. CanScrollVertically ()) {out [1] = distanceToCenter(targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }Copy the code

Calculate the difference between the target View center and RecyclerView Center Y:

private int distanceToCenter(@NonNull View targetView, Final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2); / / 2.2 RecyclerrView center coordinate final int containerCenter = helper. GetStartAfterPadding () + helper. GetTotalSpace () / 2; //2.3 Return childCenter - containerCenter; }Copy the code

2.2 Calculate the target adsorption position according to the velocity (called when inertial sliding)

/ / skip horizontal scrolling, only look at vertical scrolling @ Override public int findTargetSnapPosition (RecyclerView. LayoutManager LayoutManager, int velocityX, int velocityY) { ... //1 RecyclerView final View currentView = findSnapView(layoutManager); . / / 2. The reference view layout index calculation final int currentPosition. = layoutManager getPosition (currentView); . RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; . if (layoutManager.canScrollVertically()) { //3. According to the speed and the average height of the item estimated to scrolling vDeltaJump = estimateNextPositionDiffForFling item number (layoutManager, getVerticalHelper(layoutManager), 0, velocityY); . }... Int targetPos = currentPosition + deltaJump; //4. . return targetPos; }Copy the code

Look for reference 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; }Copy the code

Take the item nearest to RecyclerView Center as a reference

private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { int childCount = layoutManager.getChildCount(); . View closestChild = null; 1.2 RecyclerView center coordinate final int center = helper. GetStartAfterPadding () + helper. GetTotalSpace () / 2; int absClosest = Integer.MAX_VALUE; For (int I = 0; int I = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); / / 1.4 item center coordinate int childCenter = helper. GetDecoratedStart (child) + (helper. GetDecoratedMeasurement (child) / 2); Int absDistance = math. abs(childcenter-center); //1.6 absclost = absDistance RecyclerView center return if (absDistance < absclost) {absclost = absDistance; closestChild = child; } } return closestChild; }Copy the code

Estimate the number of items to scroll

private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, OrientationHelper helper, Int velocityX, int velocityY) {//3.1 Calculate distance to roll according to speed int[] distances = calculateScrollDistance(velocityX, velocityY); Float distancePerChild = computeDistancePerChild(layoutManager, helper); . Return (int) Math.round(distance/distancePerChild); return (int) Math.round(distance/distancePerChild); }Copy the code

Calculate the average height of item:

private float computeDistancePerChild(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { View minPosView = null; View maxPosView = null; int minPos = Integer.MAX_VALUE; int maxPos = Integer.MIN_VALUE; int childCount = layoutManager.getChildCount(); . For (int I = 0; int I = 0; i < childCount; i++) { View child = layoutManager.getChildAt(i); final int pos = layoutManager.getPosition(child); . If (pos < minPos) {minPos = pos; minPosView = child; } if (pos > maxPos) {maxPos = pos; maxPosView = child; }}... Int start = math.min (helper.getDecoratedStart(minPosView), helper.getDecoratedStart(maxPosView)); Int end = math.max (helper. GetDecoratedEnd (minPosView), helper. GetDecoratedEnd (maxPosView)); Int distance = end-start; //3.3.5 Calculate the total height of the layout item; . Return 1f * distance/((maxpos-minpos) + 1); return 1f * distance/((maxpos-minpos) + 1); }Copy the code

Three PagerSnapHelper

3.1 Determine the distance to scroll according to the target view (called when the scrolling stops)

/ / the same logic and LinearSnapHelper, return to the target view center distance to the center of the RecyclerView public int [] calculateDistanceToFinalSnap (@ NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }Copy the code

3.2 Calculate the target adsorption position according to the speed (called when inertial sliding)

public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { ... // 3.2.1 RecyclerView center forward the nearest view view closestChildBeforeCenter = null; int distanceBefore = Integer.MIN_VALUE; // 3.2.2 RecyclerView closestChildAfterCenter = null; int distanceAfter = Integer.MAX_VALUE; / / 3.2.3 traversal is the layout of the item final int childCount = layoutManager. GetChildCount (); for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); . Final int distance = distanceToCenter(child, orientationHelper); RecyclerView final int distance = distanceToCenter(child, orientationHelper); If (distance <= 0 && distance > distanceBefore) {//3.2.5 renew the RecyclerView distanceBefore = distance; closestChildBeforeCenter = child; } if (distance >= 0 && distance < distanceAfter) {//3.2.6 update RecyclerView center distanceAfter = distance; closestChildAfterCenter = child; Final Boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY); if (forwardDirection && closestChildAfterCenter ! = null) { return layoutManager.getPosition(closestChildAfterCenter); } else if (! forwardDirection && closestChildBeforeCenter ! = null) { return layoutManager.getPosition(closestChildBeforeCenter); }... }Copy the code