[英]RecyclerView horizontal scroll snap in center
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.我正在尝试使用 RecyclerView 在这里制作类似轮播的视图,我希望该项目在滚动时捕捉到屏幕中间,一次一个项目。 I've tried using
recyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
我试过使用
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 ?从右向左滑动现在工作正常,但反过来不行,我在这里错过了什么?
With LinearSnapHelper
, this is now very easy.使用
LinearSnapHelper
,这现在非常容易。
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.从 25.1.0 开始可用,
PagerSnapHelper
可以帮助实现类似ViewPager
效果。 Use it as you would use the LinearSnapHelper
.像使用
LinearSnapHelper
一样使用它。
Old workaround:旧的解决方法:
If you wish for it to behave akin to the ViewPager
, try this instead:如果您希望它的行为类似于
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);
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.前者是支持库版本 24.2.0 中包含的第一方解决方案。 Meaning you have to add this to your app module's
build.gradle
or update it.这意味着您必须将此添加到您的应用程序模块的
build.gradle
或更新它。
compile "com.android.support:recyclerview-v7:24.2.0"
ViewPager2 is here! ViewPager2来了!
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!谷歌刚刚在“Android 的新特性”(又名“Android 主题演讲”)演讲中宣布,他们正在开发基于 RecyclerView 的新 ViewPager!
From the slides:从幻灯片:
Like ViewPager, but better
像 ViewPager,但更好
- Easy migration from ViewPager
从 ViewPager 轻松迁移
- Based on RecyclerView
基于 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.我最近在
PagerSnapHelper
无限期PagerSnapHelper
摆动时遇到了很多麻烦 - 请参阅我打开的票证。
You can now just use a SnapHelper .您现在可以只使用SnapHelper 。
If you want a center-aligned snapping behavior similar to ViewPager then use PagerSnapHelper :如果您想要类似于ViewPager的居中对齐对齐行为, 请使用PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
There is also a LinearSnapHelper .还有一个LinearSnapHelper 。 I've tried it and if you fling with energy then it scrolls 2 items with 1 fling.
我试过了,如果你充满能量,那么它会用 1 次投掷滚动 2 个项目。 Personally I didn't like it, but just decide by yourself - trying it only takes seconds.
就我个人而言,我不喜欢它,但只能自己决定 - 尝试只需几秒钟。
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
.经过3个尝试不同的解决方案多小时发现这里于是我终于建立了一个解决方案,模拟非常密切的行为中发现
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
.该解决方案基于@eDizzle解决方案,我相信我已经改进到足以说它几乎像
ViewPager
一样ViewPager
。
Important: my RecyclerView
items width is exactly the same as the screen.重要提示:我的
RecyclerView
项目宽度与屏幕完全相同。 I haven't tried with other sizes.其他尺寸我没试过。 Also I use it with an horizontal
LinearLayoutManager
.我也将它与水平
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
模仿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);
By using PagerSnapHelper
you can get the behavior like ViewPager
通过使用
PagerSnapHelper
您可以获得类似ViewPager
的行为
You need to use findFirstVisibleItemPosition for going in the opposite direction.您需要使用 findFirstVisibleItemPosition 进行相反的方向。 And for detecting which direction the swipe was in, youll need to get either the fling velocity or the change in x.
为了检测滑动的方向,您需要获得投掷速度或 x 的变化。 I approached this problem from a slightly different angle than you have.
我从与您略有不同的角度来解决这个问题。
Create a new class that extends the RecyclerView class and then override RecyclerView's fling method like so:创建一个扩展 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;
}
Just add padding
and margin
to recyclerView
and recyclerView item
:只需向
recyclerView
和recyclerView item
添加padding
和margin
:
recyclerView item:回收者查看项目:
<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" />
and set PagerSnapHelper
:并设置
PagerSnapHelper
:
int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4;
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
dp to px: dp 到 px:
public static int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
result:结果:
My 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;
}
}
}
I pass this layout manager to the RecycledView and set the offset required to center items.我将此布局管理器传递给 RecycledView 并设置居中项目所需的偏移量。 All my items have the same width so constant offset is ok
我所有的项目都有相同的宽度,所以恒定的偏移量是可以的
PagerSnapHelper
doesn't work with GridLayoutManager
with spanCount > 1, so my solution under this circumstance is: 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.