[英]Horizontal recyclerview snap to center while previous/next items are visible
[英]RecyclerView horizontal scroll snap in center
我正在嘗試使用 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();
}
}
從右向左滑動現在工作正常,但反過來不行,我在這里錯過了什么?
使用LinearSnapHelper
,這現在非常容易。
您需要做的就是:
SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);
更新
從 25.1.0 開始可用, PagerSnapHelper
可以幫助實現類似ViewPager
效果。 像使用LinearSnapHelper
一樣使用它。
舊的解決方法:
如果您希望它的行為類似於ViewPager
,請嘗試以下操作:
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);
上面的實現只是根據速度的方向返回當前項目旁邊的位置(居中),而不管大小。
前者是支持庫版本 24.2.0 中包含的第一方解決方案。 這意味着您必須將此添加到您的應用程序模塊的build.gradle
或更新它。
compile "com.android.support:recyclerview-v7:24.2.0"
ViewPager2來了!
谷歌剛剛在“Android 的新特性”(又名“Android 主題演講”)演講中宣布,他們正在開發基於 RecyclerView 的新 ViewPager!
從幻燈片:
像 ViewPager,但更好
- 從 ViewPager 輕松遷移
- 基於 RecyclerView
- 從右到左模式支持
- 允許垂直分頁
- 改進的數據集更改通知
您可以在此處查看最新版本以及在此處查看發行說明。 還有一個官方樣本。
個人意見:我認為這是一個非常需要的補充。 我最近在PagerSnapHelper
無限期PagerSnapHelper
擺動時遇到了很多麻煩 - 請參閱我打開的票證。
您現在可以只使用SnapHelper 。
如果您想要類似於ViewPager的居中對齊對齊行為, 請使用PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
還有一個LinearSnapHelper 。 我試過了,如果你充滿能量,那么它會用 1 次投擲滾動 2 個項目。 就我個人而言,我不喜歡它,但只能自己決定 - 嘗試只需幾秒鍾。
經過3個嘗試不同的解決方案多小時發現這里於是我終於建立了一個解決方案,模擬非常密切的行為中發現ViewPager
。
該解決方案基於@eDizzle解決方案,我相信我已經改進到足以說它幾乎像ViewPager
一樣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
的行為, 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
的行為
您需要使用 findFirstVisibleItemPosition 進行相反的方向。 為了檢測滑動的方向,您需要獲得投擲速度或 x 的變化。 我從與您略有不同的角度來解決這個問題。
創建一個擴展 RecyclerView 類的新類,然后像這樣覆蓋 RecyclerView 的 fling 方法:
@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;
}
只需向recyclerView
和recyclerView item
添加padding
和margin
:
回收者查看項目:
<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>
回收者視圖:
<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
不適用於 spanCount > 1 的GridLayoutManager
,所以我在這種情況下的解決方案是:
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.