RecyclerView horizontal scroll snap in center
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"
Google I/O 2019 Update
ViewPager2 is here!
Google just announced at the talk 'What's New in Android' (aka 'The Android keynote') that they are working on a new ViewPager based on RecyclerView!
From the slides:
Like ViewPager, but better
- Easy migration from ViewPager
- Based on RecyclerView
- Right-to-Left mode support
- Allows vertical paging
- Improved dataset change notifications
You can check the latest version here and the release notes here. There is also an official sample.
Personal opinion: I think this is a really needed addition. I've recently had a lot of trouble with the PagerSnapHelper
oscillating left right indefinitely - see the ticket I've opened.
New answer (2016)
You can now just use a SnapHelper.
If you want a center-aligned snapping behavior similar to ViewPager then use PagerSnapHelper:
SnapHelper snapHelper = new PagerSnapHelper();snapHelper.attachToRecyclerView(recyclerView);
There is also a LinearSnapHelper. I've tried it and if you fling with energy then it scrolls 2 items with 1 fling. Personally I didn't like it, but just decide by yourself - trying it only takes seconds.
Original answer (2016)
After many hours of trying 3 different solutions found here in SO I've finally built a solution that mimics very closely the behavior found in a ViewPager
.
The solution is based on the @eDizzle solution, which I believe I've improved enough to say that it works almost like a ViewPager
.
Important: my RecyclerView
items width is exactly the same as the screen. I haven't tried with other sizes. Also I use it with an horizontal LinearLayoutManager
. I think that you will need to adapt the code if you want vertical scroll.
Here you have the code:
public class SnappyRecyclerView extends RecyclerView { // Use it with a horizontal LinearLayoutManager // Based on https://stackoverflow.com/a/29171652/4034572 public SnappyRecyclerView(Context context) { super(context); } public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean fling(int velocityX, int velocityY) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; // views on the screen int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition(); View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition); int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition(); View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition); // distance we need to scroll int leftMargin = (screenWidth - lastView.getWidth()) / 2; int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth(); int leftEdge = lastView.getLeft(); int rightEdge = firstView.getRight(); int scrollDistanceLeft = leftEdge - leftMargin; int scrollDistanceRight = rightMargin - rightEdge; if (Math.abs(velocityX) < 1000) { // The fling is slow -> stay at the current page if we are less than half through, // or go to the next page if more than half through if (leftEdge > screenWidth / 2) { // go to next page smoothScrollBy(-scrollDistanceRight, 0); } else if (rightEdge < screenWidth / 2) { // go to next page smoothScrollBy(scrollDistanceLeft, 0); } else { // stay at current page if (velocityX > 0) { smoothScrollBy(-scrollDistanceRight, 0); } else { smoothScrollBy(scrollDistanceLeft, 0); } } return true; } else { // The fling is fast -> go to next page if (velocityX > 0) { smoothScrollBy(scrollDistanceLeft, 0); } else { smoothScrollBy(-scrollDistanceRight, 0); } return true; } } @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); // If you tap on the phone while the RecyclerView is scrolling it will stop in the middle. // This code fixes this. This code is not strictly necessary but it improves the behaviour. if (state == SCROLL_STATE_IDLE) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; // views on the screen int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition(); View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition); int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition(); View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition); // distance we need to scroll int leftMargin = (screenWidth - lastView.getWidth()) / 2; int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth(); int leftEdge = lastView.getLeft(); int rightEdge = firstView.getRight(); int scrollDistanceLeft = leftEdge - leftMargin; int scrollDistanceRight = rightMargin - rightEdge; if (leftEdge > screenWidth / 2) { smoothScrollBy(-scrollDistanceRight, 0); } else if (rightEdge < screenWidth / 2) { smoothScrollBy(scrollDistanceLeft, 0); } } }}
Enjoy!
If the goal is to make the RecyclerView
mimic the behavior of ViewPager
there is quite easy approach
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);SnapHelper snapHelper = new PagerSnapHelper();recyclerView.setLayoutManager(layoutManager);snapHelper.attachToRecyclerView(mRecyclerView);
By using PagerSnapHelper
you can get the behavior like ViewPager