如何修复垂直 RecyclerView 内的水平 ViewPager2 和 RecyclerView 的滚动问题?
Posted
技术标签:
【中文标题】如何修复垂直 RecyclerView 内的水平 ViewPager2 和 RecyclerView 的滚动问题?【英文标题】:How to fix Scrolling issue of horizontal ViewPager2 and RecyclerView that are inside a vertical RecyclerView? 【发布时间】:2020-06-04 09:14:45 【问题描述】:我有一个 RecyclerView(例如,rootRecyclerView
),它可以根据某些 API 响应具有不同类型的行。我实现了其中一个是水平的ViewPager2
,另一个是水平的RecyclerView
(比如childRecyclerView
)。
rootRecyclerView 垂直滑动,而 viewPager2 和 childRecyclerView 水平滑动。
问题:
当我在屏幕上滑动时,如果滑动是在viewPager2
或childRecyclerView
上,则滑动必须完全水平直线。否则,它们不会水平滚动;滑动是由rootRecyclerView
进行的,因此您会看到垂直移动。
因此,发生这种情况是因为您的拇指会沿弯曲/圆形方向移动,从而在 X 轴和 Y 轴上产生移动,因此 rootRecyclerView 拦截了滑动,从而产生了这种令人不快的用户体验。
我确实尝试过解决这个问题,例如像这样向 childRecyclerView 添加OnItemTouchListener
:
private float Y_BUFFER = ViewConfiguration.get(getContext())
.getScaledPagingTouchSlop(); // 10;
private float preX = 0f;
private float preY = 0f;
childRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e)
if(e.getAction()==MotionEvent.ACTION_DOWN)
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
if(e.getAction() == MotionEvent.ACTION_MOVE)
if (Math.abs(e.getX() - preX) > Math.abs(e.getY() - preY))
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
else if (Math.abs(e.getY() - preY) > Y_BUFFER)
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
preX = e.getX();
preY = e.getY();
return false;
// ... rest of the code
它只解决了childRecyclerView
的问题,但我无法解决ViewPager2
的问题。
我也尝试过使用GestureDetector
,如answer link 中所述,以及其他一些代码组合,但我无法使其工作。
谁能帮帮我?
【问题讨论】:
请注意,您的Y_BUFFER
被硬编码为 10 个像素。我建议您使用像 ViewConfiguration.get(getContext()).getScaledPagingTouchSlop()
这样的预烘焙缩放值
感谢您的建议。我以前不知道。我已经更新了代码。
【参考方案1】:
好的,所以经过一些研究,我得出的结论是用recyclerView
替换我的recyclerView
,这将'表现得像'viewPager:/。
首先我用水平的recyclerView
替换了我的viewPager2
。要使其表现得像一个浏览器,请使用SnapHelper
。
RecyclerView childRecyclerView2 = findViewById(R.id.previously_viewPager);
// other init like setup layout manager, adapter etc
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(replacedRecyclerView); // <-- this makes out rv behave like a viewPager
之后,您必须添加一个OnItemTouchListener
并覆盖onInterceptTouchEvent
,就像我的问题中的代码段一样:
childRecyclerView2.addOnItemTouchListener(new RecyclerView.OnItemTouchListener()
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e)
// same as the code segment in the question,
//so skipping this part.
//just copy it from my question
// ...
可选:
在viewPager2中,你可以用getCurrentItem()
获取当前焦点,但是由于我们已经用recyclerview替换了viewpager2,所以我们没有那个方法。所以,我们需要实现我们自己的等效版本。如果你是 Kotlin 人,可以直接跳转到reference 2,跳过这一部分。如果需要,这里是 java 版本,不过我会跳过解释。
创建SnapHelperExt.java
public class SnapHelperExt
public SnapHelperExt()
public int getSnapPosition(RecyclerView recyclerView, SnapHelper snapHelper)
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
View snapView = snapHelper.findSnapView(layoutManager);
if (snapView != null)
return layoutManager.getPosition(snapView);
else
return -1;
接下来创建一个接口OnSnapPositionChangeListener
作为我们的监听器:
public interface OnSnapPositionChangeListener
void onSnapPositionChange(int position);
之后,创建SnapOnScrollListener.java
:
public class SnapOnScrollListener extends RecyclerView.OnScrollListener
public enum Behavior
NOTIFY_ON_SCROLL,
NOTIFY_ON_SCROLL_STATE_IDLE
private SnapHelperExt snapHelperExt;
private SnapHelper snapHelper;
private Behavior behavior;
private OnSnapPositionChangeListener onSnapPositionChangeListener;
private int snapPosition = RecyclerView.NO_POSITION;
public SnapOnScrollListener(SnapHelper snapHelper, Behavior behavior, OnSnapPositionChangeListener onSnapPositionChangeListener)
this.snapHelper = snapHelper;
this.behavior = behavior;
this.onSnapPositionChangeListener = onSnapPositionChangeListener;
this.snapHelperExt = new SnapHelperExt();
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy)
super.onScrolled(recyclerView, dx, dy);
if (behavior == Behavior.NOTIFY_ON_SCROLL)
maybeNotifySnapPositionChange(recyclerView);
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState)
super.onScrollStateChanged(recyclerView, newState);
if (behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
&& newState == RecyclerView.SCROLL_STATE_IDLE)
maybeNotifySnapPositionChange(recyclerView);
private void maybeNotifySnapPositionChange(RecyclerView recyclerView)
int prevPosition = this.snapHelperExt.getSnapPosition(recyclerView, snapHelper);
boolean snapPositionIsChanged = (this.snapPosition!=prevPosition);
if(snapPositionIsChanged)
onSnapPositionChangeListener.onSnapPositionChange(prevPosition);
this.snapPosition = prevPosition;
最后这样使用:
SnapOnScrollListener snapOnScrollListener = new SnapOnScrollListener(
snapHelper,
SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL,
position ->
Log.e(TAG, "currently focused page no = "+position);
// your code here, do whatever you want
);
childRecyclerView2.addOnScrollListener(snapOnScrollListener);
参考资料:
-
create-viewpager-using-recyclerview
detecting-snap-changes-with-androids-recyclerview
【讨论】:
以上是关于如何修复垂直 RecyclerView 内的水平 ViewPager2 和 RecyclerView 的滚动问题?的主要内容,如果未能解决你的问题,请参考以下文章
垂直 RecyclerView 内的水平 ViewPager2
Android 布局:具有滚动行为的 Viewpager 内的垂直 Recyclerview 内的水平 Recyclerview
NestedScrollView 内的 RecyclerView :使水平滚动更容易