An example for this article has been uploadedGitHub ,Click here to see

Requirements describe

When we are developing a list, we sometimes need to implement the scrolling effect of the list. List implementation everyone should use RecyclerView, but RecyclerView native is not support the whole page sliding. Recently RecyclerView added the SnapHelper API, which is used to help align ItemView. The SDK implements the LinearSnapHelper and PagerSnapHelper by default. Center alignment and slide ItemView one at a time, respectively.

We use the principle of SnapHelper to achieve a whole page slide RecyclerView. The effect is as follows:

SnapHelperintroduce

SnapHelper is used to help align itemViews, and we need to implement three methods from SnapHelper

View findSnapView(RecyclerView.LayoutManager layoutManager)
Copy the code

Find the ItemView that you need to align, which we’ll call snapView.

int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView)
Copy the code

Calculates the distance between the snapView and the position to be aligned.

int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY)
Copy the code

Find the position within the Adapter for the ItemView to align.

As shown above, suppose we need to swipe one page at a time: snapView is the ItemView that needs to be aligned, corresponding to the return value of findSnapView(); SnapView position in Adapter is the position to be aligned, corresponding to the return value of findTargetSnapPosition(); Snap short is aligned to the sliding distance, corresponding calculateDistanceToFinalSnap () returns a value.

PagerSnapHelperimplementation

We’re going to implement a SnapHelper that slides through the page.

  • First we need to find what we need to alignItemView
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?).: View? {
        if (mFlung) {
            resetCurrentScrolled()
            mFlung = false
            return null
        }
        if (layoutManager == null) return null
        // First find the position of the ItemView to be aligned in the Adapter
        val targetPosition = getTargetPosition()
        println("$TAG findSnapView, pos: $targetPosition")
        if (targetPosition == RecyclerView.NO_POSITION) return null
        / / under normal circumstances, we are here by layoutManager. FindViewByPosition (int pos, returns to the view, but there are two questions:
        // 1. If the view at this position does not already have a layout, it will return null.
        // 2. Even if the view is not empty, the sliding speed will be inconsistent, as discussed later;
        // So here we pass position to the LinearSmoothScroller to help us slide to the specified position.
        layoutManager.startSmoothScroll(createScroller(layoutManager).apply {
            this? .targetPosition = targetPosition })return null
    }
Copy the code

LinearSmoothScroller can set a targetPosition, then calls the layoutManager. StartSmoothScroll (LinearSmoothScroller scroller), This will help us to automatically align the ItemView corresponding to the targetPosition to the boundary, which is left aligned by default, as we need.

private fun getTargetPosition(a): Int {
        println("$TAG getTargetPosition, mScrolledX: $mScrolledX, mCurrentScrolledX: $mCurrentScrolledX")
        val page = when {
            mCurrentScrolledX > 0 -> mScrolledX / mRecyclerViewWidth + 1
            mCurrentScrolledX < 0 -> mScrolledX / mRecyclerViewWidth
            else -> RecyclerView.NO_POSITION
        }
        resetCurrentScrolled()
        return (if (page == RecyclerView.NO_POSITION) RecyclerView.NO_POSITION else page * itemCount)
    }
Copy the code

GetTargetPosition () is based on the distance and direction of RecyclerView sliding, find out the position of ItemView need to align after sliding a page.

private val mScrollListener = object : RecyclerView.OnScrollListener() {
        private var scrolledByUser = false

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) scrolledByUser = true
            if (newState == RecyclerView.SCROLL_STATE_IDLE && scrolledByUser) {
                scrolledByUser = false}}override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            mScrolledX += dx
            mScrolledY += dy
            if (scrolledByUser) {
                mCurrentScrolledX += dx
                mCurrentScrolledY += dy
            }
        }
    }
Copy the code

MScrolledX, mScrolledY is the total distance of RecyclerView sliding, mCurrentScrolledX, mCurrentScrolledY is the distance of RecyclerView sliding, Used to judge the direction of RecyclerView sliding.

  • Find what needs to be alignedItemViewAdapterPosition in:
override fun findTargetSnapPosition(
        layoutManager: RecyclerView.LayoutManager? , velocityX:Int,
        velocityY: Int
    ): Int {
        valtargetPosition = getTargetPosition() mFlung = targetPosition ! = RecyclerView.NO_POSITION println("$TAG findTargetSnapPosition, pos: $targetPosition")
        return targetPosition
    }
Copy the code

Simply, it’s the value returned by getTargetPosition(). To clarify, the findTargetSnapPosition() method is only called when the RecyclerView triggers the Fling. SnapHelper internally uses the LinearSmoothScroller sliding implementation, and the targetPosition set is the return value of findTargetSnapPosition(). This explains why we don’t return snapView directly in the findSnapView() method, just to keep the sliding speed consistent.

  • Calculate the distance you need to slide
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
        val out = IntArray(2)
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager))
            out[1] = 0
        } else if (layoutManager.canScrollVertically()) {
            out[0] = 0
            out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager))
        }
        return out
    }
Copy the code
private fun distanceToStart(targetView: View, orientationHelper: OrientationHelper): Int {
        return orientationHelper.getDecoratedStart(targetView) - orientationHelper.startAfterPadding
    }
Copy the code

To explain, OrientationHelper is a handy way to calculate the location of an ItemView.

An example for this article has been uploadedGitHub ,Click here to see