RecyclerView水平滚动中心
我试图在这里使用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
, 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);
上面的实现只是基于速度方向返回当前项目(居中)旁边的位置,而不pipe幅度大小。
前者是支持库版本24.2.0中包含的第一方解决scheme。 这意味着你必须添加到你的应用程序模块的build.gradle
或更新它。
compile "com.android.support:recyclerview-v7:24.2.0"
重要更新:
Google已经在Android支持库24.2.0(2016年8月发布)中实现了这个function。 发行说明这样说:
RecyclerView.OnFlingListener已经被添加来支持自定义行为来回应啰嗦。 SnapHelper类提供了一个专门用于捕捉子视图的实现, LinearSnapHelper类扩展了该实现,以提供类似于ViewPager的中心alignment捕捉行为。
请注意,我还没有尝试使用LinearSnapHelper
。 我仍然使用我的解决scheme,工作正常。
原始答案:
在经过了几个小时的尝试之后,我find了3个不同的解决scheme。我终于build立了一个模拟非常接近ViewPager
的行为的解决scheme。
该解决scheme基于@eDizzle 解决scheme ,我相信我已经改进到足以说它几乎像一个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); } } } }
请享用!
您需要使用findFirstVisibleItemPosition进行相反的操作。 为了检测滑动的方向,你需要得到或者是fling的速度或者x的变化。 我从一个稍微不同的angular度来处理这个问题。
创build一个扩展RecyclerView类的新类,然后重写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; }
如果目标是使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
一样的行为
我的解决scheme
/** * 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; } } }
我将这个布局pipe理器传递给RecycledView,并设置中心项目所需的偏移量。 我所有的项目都有相同的宽度,所以常数偏移量是可以的