RecyclerView bouton de défilement horizontal au centre

J’essaie de faire une vue semblable à un carrousel en utilisant RecyclerView, je veux que l’élément se positionne au milieu de l’écran lors du défilement, un élément à la fois. J’ai essayé d’utiliser recyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);

mais la vue défile toujours en douceur, j’ai également essayé de mettre en œuvre ma propre logique en utilisant listener scroll comme ça:

 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(); } } 

Le coup de droite à gauche fonctionne bien maintenant, mais pas l’inverse, qu’est-ce qui me manque ici?

Avec LinearSnapHelper , c’est maintenant très facile.

Tout ce que vous devez faire c’est ceci:

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

Mettre à jour

Disponible depuis le 25.1.0, PagerSnapHelper peut aider à obtenir l’effet de ViewPager . Utilisez-le comme vous le feriez avec LinearSnapHelper .

Ancienne solution de contournement:

Si vous souhaitez qu’il ressemble à ViewPager , essayez plutôt ceci:

 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); 

L'implémentation ci-dessus renvoie simplement la position à côté de l'élément actuel (centré) en fonction de la direction de la vitesse, indépendamment de la magnitude.

L'ancien est une solution de première partie incluse dans la version 24.2.0 de la bibliothèque de support. Cela signifie que vous devez l'append au build.gradle de votre module d' build.gradle ou le mettre à jour.

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

Mise à jour importante:

Google l’a implémenté dans Android Support Library 24.2.0 (publié en août 2016). Les notes de publication disent ceci:

RecyclerView.OnFlingListener a été ajouté pour prendre en charge le comportement personnalisé en réponse aux requêtes. La classe SnapHelper fournit une implémentation spécifique pour la capture des vues enfants et la classe LinearSnapHelper étend cette implémentation pour fournir un comportement de capture aligné sur le centre, similaire à ViewPager .

Notez que je n’ai pas encore essayé d’utiliser LinearSnapHelper . J’utilise toujours ma solution, qui fonctionne bien.

Réponse originale:

Après plusieurs heures d’essais de 3 solutions différentes trouvées ici dans SO, j’ai finalement conçu une solution qui reproduit de très près le comportement trouvé dans un ViewPager .

La solution est basée sur la solution @eDizzle, que j’estime avoir suffisamment améliorée pour dire qu’elle fonctionne presque comme un ViewPager .

Important: la largeur de mes éléments RecyclerView est identique à celle de l’écran. Je n’ai pas essayé avec d’autres tailles. Je l’utilise aussi avec un LinearLayoutManager horizontal. Je pense que vous devrez adapter le code si vous voulez un défilement vertical.

Ici vous avez le 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 AtsortingbuteSet attrs) { super(context, attrs); } public SnappyRecyclerView(Context context, @Nullable AtsortingbuteSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean fling(int velocityX, int velocityY) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); int screenWidth = Resources.getSystem().getDisplayMesortingcs().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 ssortingctly necessary but it improves the behaviour. if (state == SCROLL_STATE_IDLE) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); int screenWidth = Resources.getSystem().getDisplayMesortingcs().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); } } } } 

Prendre plaisir!

Vous devez utiliser findFirstVisibleItemPosition pour aller dans la direction opposée. Et pour détecter la direction dans laquelle le balayage a été effectué, vous devez obtenir soit la vitesse de transmission, soit la modification de x. J’ai abordé ce problème sous un angle légèrement différent de celui que vous avez.

Créez une nouvelle classe qui étend la classe RecyclerView, puis remplacez la méthode de transfert de RecyclerView comme suit:

 @Override public boolean fling(int velocityX, int velocityY) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); //these four variables identify the views you see on screen. int lastVisibleView = linearLayoutManager.findLastVisibleItemPosition(); int firstVisibleView = linearLayoutManager.findFirstVisibleItemPosition(); View firstView = linearLayoutManager.findViewByPosition(firstVisibleView); View lastView = linearLayoutManager.findViewByPosition(lastVisibleView); //these variables get the distance you need to scroll in order to center your views. //my views have variable sizes, so I need to calculate side margins separately. //note the subtle difference in how right and left margins are calculated, as well as //the resulting scroll distances. 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(user swipes to the left) if(velocityX > 0) smoothScrollBy(scrollDistanceLeft, 0); else smoothScrollBy(-scrollDistanceRight, 0); return true; } 

Si le but est de faire en sorte que RecyclerView imite le comportement de ViewPager , l’approche est assez simple.

 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); 

En utilisant PagerSnapHelper vous pouvez obtenir le comportement comme ViewPager

Ma solution:

 /** * Horizontal linear layout manager whose smoothScrollToPosition() centers * on the target item */ class ItemLayoutManager extends LinearLayoutManager { private int centeredItemOffset; public ItemLayoutManager(Context context) { super(context, LinearLayoutManager.HORIZONTAL, false); } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { LinearSmoothScroller linearSmoothScroller = new Scroller(recyclerView.getContext()); linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } public void setCenteredItemOffset(int centeredItemOffset) { this.centeredItemOffset = centeredItemOffset; } /** * ********** Inner Classes ********** */ private class Scroller extends LinearSmoothScroller { public Scroller(Context context) { super(context); } @Override public PointF computeScrollVectorForPosition(int targetPosition) { return ItemLayoutManager.this.computeScrollVectorForPosition(targetPosition); } @Override public int calculateDxToMakeVisible(View view, int snapPreference) { return super.calculateDxToMakeVisible(view, SNAP_TO_START) + centeredItemOffset; } } } 

Je transmets ce gestionnaire de disposition à RecycledView et définit le décalage requirejs pour centrer les éléments. Tous mes articles ont la même largeur, donc le décalage constant est correct