Я пытаюсь создать здесь вид карусели, используя RecyclerView, я хочу, чтобы элемент щелкал по середине экрана при прокрутке, по одному элементу за раз. Я пробовал использоватьrecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
но представление по-прежнему прокручивается плавно, я также попытался реализовать свою собственную логику, используя прослушиватель прокрутки, например:
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();
}
}
Свайп справа налево теперь работает нормально, но не наоборот, что мне здесь не хватает?
источник
LinearLayoutManager
где все виды были стандартного размера. Больше ничего, кроме приведенного выше фрагмента, не требуется.LinearSnapHelper
.Обновление Google I / O 2019
ViewPager2 уже здесь!
Google только что объявил на своем выступлении «Что нового в Android» (также известное как «Тема Android»), что они работают над новым ViewPager на основе RecyclerView!
Со слайдов:
Вы можете проверить последнюю версию здесь и примечания к выпуску здесь . Также есть официальный образец .
Личное мнение: думаю, это действительно нужное дополнение. У меня недавно было много проблем с
PagerSnapHelper
бесконечно колеблющимся левым и правым движением - посмотрите билет, который я открыл.Новый ответ (2016)
Теперь вы можете просто использовать SnapHelper .
Если вы хотите, чтобы поведение привязки по центру было похоже на ViewPager, используйте PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
Также существует LinearSnapHelper . Я пробовал, и если вы бросаете с энергией, то он прокручивает 2 элемента за 1 бросок. Лично мне это не понравилось, но решайте сами - проба занимает секунды.
Оригинальный ответ (2016)
После многих часов пробных 3 различных решений, найденных здесь, в SO, я наконец создал решение, которое очень точно имитирует поведение, обнаруженное в файле
ViewPager
.Решение основано на решении @eDizzle , которое, как мне кажется, я улучшил достаточно, чтобы сказать, что оно работает почти как
ViewPager
.Важно:
RecyclerView
ширина моих предметов точно такая же, как у экрана. С другими размерами не пробовал. Также использую с горизонтальюLinearLayoutManager
. Я думаю, что вам нужно будет адаптировать код, если вы хотите вертикальную прокрутку.Вот код:
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); } } } }
Наслаждайтесь!
источник
Если цель состоит в том, чтобы
RecyclerView
имитировать поведение,ViewPager
есть довольно простой подход.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);
Используя,
PagerSnapHelper
вы можете получить такое поведение, какViewPager
источник
LinearSnapHelper
вместо,PagerSnapHelper
и у меня это работаетLinearSnapHelper
LinearSnapHelper
, и он переключается на средний элемент.PagerSnapHelper
мешает легко прокручивать (по крайней мере, список изображений).Вам нужно использовать findFirstVisibleItemPosition для движения в противоположном направлении. А чтобы определить, в каком направлении было движение, вам нужно получить либо скорость броска, либо изменение x. Я подошел к этой проблеме под несколько иным углом, чем вы.
Создайте новый класс, который расширяет класс RecyclerView, а затем переопределите метод fling RecyclerView следующим образом:
@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; }
источник
Просто добавьте
padding
иmargin
кrecyclerView
иrecyclerView item
:Элемент recyclerView:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parentLayout" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_marginLeft="8dp" <!-- here --> android:layout_marginRight="8dp" <!-- here --> android:layout_width="match_parent" android:layout_height="200dp"> <!-- child views --> </RelativeLayout>
recyclerView:
<androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="8dp" <!-- here --> android:paddingRight="8dp" <!-- here --> android:clipToPadding="false" <!-- important!--> android:scrollbars="none" />
и установите
PagerSnapHelper
:int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels; parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4; SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
dp в px:
public static int dpToPx(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics()); }
результат:
источник
Мое решение:
/** * 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; } } }
Я передаю этот менеджер компоновки в RecycledView и устанавливаю смещение, необходимое для центрирования элементов. Все мои предметы имеют одинаковую ширину, поэтому постоянное смещение в порядке
источник
PagerSnapHelper
не работает сGridLayoutManager
spanCount> 1, поэтому мое решение в этом случае:class GridPagerSnapHelper : PagerSnapHelper() { override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int { val forwardDirection = if (layoutManager?.canScrollHorizontally() == true) { velocityX > 0 } else { velocityY > 0 } val centerPosition = super.findTargetSnapPosition(layoutManager, velocityX, velocityY) return centerPosition + if (forwardDirection) (layoutManager as GridLayoutManager).spanCount - 1 else 0 } }
источник