Stop in the middle automatically turn the page

Prologue: Recently received a task to do a function similar to the automatic page turning above. As you can see, there are three images displayed in this screen, there are two not shown completely, see the design first reaction is to be implemented with the viewpager, but the task is a big joke with you, demand is the most on the left side of the picture, that is, to the left of the image display completely, as shown below, Later, I thought carefully that viewpager did not seem to have such a function, or I did not know, I did not find such an article or information, I hope to know Jane’s private jab exchange, thank you very much, ok, back to the point

Stop on the left

Before we start, we will introduce a new thing called SnapHelper, which is an extension of RecyclerView. If you are interested, you can go to see its source code. SnapHelper. The principle is to monitor RecyclerView OnFlingListener onFling interface, can make RecyclerView similar ViewPager function, no matter how sliding lodged in a page right in the middle, So what’s the difference between it and ViewPager? The ViewPager can’t slide multiple images in a row at once, and it can’t be customized (stop on the left or right). Let’s take a look!

Import the required package first, the lowest version is v7-24.2.0, lower than this class does not have:

compile 'com. Android. Support: appcompat - v7:24.2.0'
compile 'com. Android. Support: recyclerview - v7:24.2.0'Copy the code

The LinearSnapHelper class inherits the LinearSnapHelper class. The default is to stop the view in the middle. You just need to bind the RecyclerView to the LinearSnapHelper class.

LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper();
mLinearSnapHelper.attachToRecyclerView(mRecyclerview);Copy the code

The effect is as follows:

Stop in the middle

You can also customize the LinearSnapHelper so that it stays on the left or the right. You don’t need to re-inherit the SnapHelper. You can inherit the LinearSnapHelper so that it stays on the right or the left. (1), calculateDistanceToFinalSnap: when drag and drop or sliding end correction, this method returns a out = int [2], the out [0] x axis, the out [1] y, this value is the need to modify the position offset of the you need. (2) findSnapView: This method is used to retrieve a particular view. When null is returned, no view was retrieved.

Complete code:

public class LeftSnapHelper extends LinearSnapHelper { private OrientationHelper mHorizontalHelper; /** * This method returns an array of length 2. Out [0] represents the horizontal axis and x[1] represents the vertical axis. These two values are the offset of the position you need to correct ** @param layoutManager * @param targetView * @return*/ @Override public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, Int [] out = new int[2]; int[] out = new int[2];if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }
        returnout; } /** * this method calculates the offset ** @param targetView * @param helper * @return
     */
    private int distanceToStart(View targetView, OrientationHelper helper) {
        return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
    }

    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        returnfindStartView(layoutManager, getHorizontalHelper(layoutManager)); } /** * Find the first view to display * @param layoutManager * @param helper * @return
     */
    private View findStartView(RecyclerView.LayoutManager layoutManager,
                               OrientationHelper helper) {
        if (layoutManager instanceof LinearLayoutManager) {
            int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            if (firstChild == RecyclerView.NO_POSITION) {
                returnnull; } // This is to solve the problem of the last Item not being displayed in its entirety when turning to the last pageif (lastChild == layoutManager.getItemCount() - 1) {
                returnlayoutManager.findViewByPosition(lastChild); } View child = layoutManager.findViewByPosition(firstChild); // Get the items that need to be left aligned to display at this pointif (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
                    && helper.getDecoratedEnd(child) > 0) {
                return child;
            } else {
                returnlayoutManager.findViewByPosition(firstChild + 1); }}returnsuper.findSnapView(layoutManager); } /** * get the direction of the view ** @param layoutManager * @return
     */
    private OrientationHelper getHorizontalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        returnmHorizontalHelper; }}Copy the code

Of course, you can stop it on the right: just modify the findSnapView method above:

public class RightSnapHelper extends LinearSnapHelper { private OrientationHelper mHorizontalHelper; /** * This method returns an array of length 2. Out [0] represents the horizontal axis and x[1] represents the vertical axis. These two values are the offset of the position you need to correct ** @param layoutManager * @param targetView * @return*/ @Override public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, Int [] out = new int[2]; int[] out = new int[2];if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToEnd(targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }
        returnout; } /** * this method calculates the offset ** @param targetView * @param helper * @return
     */
    private int distanceToEnd(View targetView, OrientationHelper helper) {
        return helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding();
    }

    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        returnfindEndView(layoutManager, getHorizontalHelper(layoutManager)); } /** * Find the first view to display ** @param layoutManager * @param helper * @return
     */
    private View findEndView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
        if (layoutManager instanceof LinearLayoutManager) {
            int lastChild = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            if (lastChild == RecyclerView.NO_POSITION) {
                returnnull; } View child = layoutManager.findViewByPosition(lastChild); // Get the items that need to be right-aligned to display at this pointif (helper.getDecoratedStart(child) >= helper.getDecoratedMeasurement(child) / 2
                    && helper.getDecoratedStart(child) > 0) {
                return child;
            } else {
                returnlayoutManager.findViewByPosition(lastChild - 1); }}returnsuper.findSnapView(layoutManager); } /** * get the direction of the view ** @param layoutManager * @return
     */
    private OrientationHelper getHorizontalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        returnmHorizontalHelper; }}Copy the code

Effect:

Stop on the right

So how do you make it slide infinitely?

Insert into the Adapter that gets the total number of items returns integer. MAX_VALUE:

@Override
public int getItemCount() {
    return Integer.MAX_VALUE;
}Copy the code

We then get the corresponding mod from the list in onBindViewHolder:

@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
    Glide.with(mContext).load(mList.get(position % mList.size())
            .getImageUrl()).placeholder(R.mipmap.ic_launcher)
            .into(holder.ivImage);
    holder.tvName.setText(mList.get(position % mList.size()).getName());
}Copy the code

Ok, so that’s about 80% of it, and then we want it to scroll automatically, so how do we scroll automatically? Here you can refer to the effect of automatic scrolling in ViewPager. Here LZ uses Timer to implement, Timer has a function of how often to execute, which is exactly here:

private int cnt = 2; Position Private Boolean isSlidingByHand = position private Boolean isSlidingByHand =false; Private Boolean isSlidingAuto =true; // Indicates whether timer.schedule(new) is automatically swipedTimerTask() {
        @Override
        public void run() {
            if(isSlidingAuto) { myHandler.sendEmptyMessage(CHANGE_ITEM); }}}, 1000, 3000);Copy the code

Considering that there are two kinds of sliding, one is the user’s manual sliding, the other is our Timer to start sliding, we also need to monitor the RecyclerView setting, to simply determine whether it is triggered by the user or the Timer:

alRecyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
            int firstVisibleItemPosition = manager.findFirstVisibleItemPosition();
            switch (newState) {
                caseSCROLL_STATE_IDLE: // (static without scrolling)if (isSlidingByHand) {
                        Message msg = myHandler.obtainMessage();
                        msg.arg1 = firstVisibleItemPosition;
                        msg.what = CHANGE_ITEM;
                        myHandler.sendMessage(msg);
                    }
                    break;
                caseSCROLL_STATE_DRAGGING: // (being dragged externally, usually by users scrolling with their fingers) isSlidingByHand =true;
                    isSlidingAuto = false;
                    break;
                caseScroll_state_demystified: // (Automatically scroll)if (isSlidingByHand) {
                        isSlidingAuto = false;
                    } else {
                        isSlidingAuto = true;
                    }
                    break; }}});Copy the code

The final result is passed to Handler:

Private static class MyHandler extends Handler {// WeakReference<CenterActivity> WeakReference; public MyHandler(CenterActivity mActivity) { this.weakReference = new WeakReference<>(mActivity); } @Override public void handleMessage(Message msg) { final CenterActivity mActivity = weakReference.get(); Log.d(TAG,"handleMessage: " + "handler is running");
        if (mActivity.isSlidingByHand) {
            mActivity.cnt = msg.arg1;
            mActivity.isSlidingByHand = false;
            mActivity.isSlidingAuto = true; mActivity.cnt+=2; / / let RecyclerView smooth scrolling mActivity alRecyclerview. SmoothScrollToPosition (+ + mActivity. CNT); }else{ mActivity.alRecyclerview.smoothScrollToPosition(++mActivity.cnt); }}}Copy the code

This is almost good, but there is a problem I don’t know if you have paid attention to, when rolling is not so smooth, looking for a long time do not know why, I hope to know the friends under the comment to inform.

OrientationHelper API: Android24.2.0 support library for SnapHelper learning and using

I have uploaded the source code to Github, want to know friends can download, of course, if you have better ideas, can also communicate with.

Public account: Android technology experience sharing