如何修复嵌套在垂直RecyclerView内的水平ViewPager2和RecyclerView滚动问题?

4

我有一个 RecyclerView(称为rootRecyclerView),它可以根据某些API响应具有不同类型的行。其中一个行是使用水平ViewPager2实现的,另一个行是使用水平的RecyclerView实现的(称为childRecyclerView)。

rootRecyclerView在垂直方向上滑动,而viewPager2childRecyclerView在水平方向上滑动。

问题:

当我在屏幕上滑动时,如果滑动位于viewPager2childRecyclerView上,则滑动必须完全水平直行。否则,它们将无法水平滚动;滑动被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和其他代码组合,但我无法使其正常工作。
有人可以帮我吗?

1
请注意,您的 Y_BUFFER 硬编码为 10 个像素。我建议您使用预先缩放的值,例如 ViewConfiguration.get(getContext()).getScaledPagingTouchSlop() - Miha_x64
谢谢您的建议。我之前不知道这个。我已经更新了代码。 - Qazi Fahim Farhan
1个回答

6

好的,经过一些研究,我得出了一个结论:用一个“行为类似”的recyclerView来替换我的ViewPager2 :/。

首先,我用一个水平的recyclerView替换了我的viewPager2。为了使它的行为类似于一个viewpager,使用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开发人员,可以直接跳转到参考 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);

参考资料:

  1. 使用RecyclerView创建ViewPager
  2. 使用Android的RecyclerView Snaphelper检测快照更改

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接