Android – RecyclerView horizontal scroll snap in center

androidandroid-recyclerviewhorizontal-scrolling

I'm trying to make a carousel-like view here using RecyclerView, I want the item to snap in the middle of the screen when scrolling, one item at a time. I've tried using recyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);

but the view is still scrolling smoothly, I've also tried to implement my own logic using scroll listener like so:

recyclerView.setOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    Log.v("Offset ", recyclerView.getWidth() + "");
                    if (newState == 0) {
                        try {
                               recyclerView.smoothScrollToPosition(layoutManager.findLastVisibleItemPosition());
                                recyclerView.scrollBy(20,0);
                            if (layoutManager.findLastVisibleItemPosition() >= recyclerView.getAdapter().getItemCount() - 1) {
                                Beam refresh = new Beam();
                                refresh.execute(createUrl());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

The swipe from right to left is working fine now, but not the other way around, what am I missing here ?

Best Answer

With LinearSnapHelper, this is now very easy.

All you need to do is this:

SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);

Update

Available since 25.1.0, PagerSnapHelper can help achieve ViewPager like effect. Use it as you would use the LinearSnapHelper.

Old workaround:

If you wish for it to behave akin to the ViewPager, try this instead:

LinearSnapHelper snapHelper = new LinearSnapHelper() {
    @Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
        View centerView = findSnapView(layoutManager);
        if (centerView == null) 
            return RecyclerView.NO_POSITION;

        int position = layoutManager.getPosition(centerView);
        int targetPosition = -1;
        if (layoutManager.canScrollHorizontally()) {
            if (velocityX < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        if (layoutManager.canScrollVertically()) {
            if (velocityY < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        final int firstItem = 0;
        final int lastItem = layoutManager.getItemCount() - 1;
        targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem));
        return targetPosition;
    }
};
snapHelper.attachToRecyclerView(recyclerView);

The implementation above just returns the position next to the current item (centered) based on the direction of the velocity, regardless of the magnitude.

The former one is a first party solution included in the Support Library version 24.2.0. Meaning you have to add this to your app module's build.gradle or update it.

compile "com.android.support:recyclerview-v7:24.2.0"