Horizo​​ntal RecyclerView 滚动行为中的 Vertical RecyclerView 内部的 Horizo​​ntal ScrollView

Horizontal ScrollView inside Vertical RecyclerView inside Horizontal RecyclerView scrolling behavior

我下面有一个 Activity:

<RecyclerView> // with horizontal LinearLayoutManager and PagerSnapHelper
    <RecyclerView> // with vertical LinearLayoutManager
        <RecyclcerView /> // with horizontal LinearLayoutManager
        <HorizontalScrollView />
        // ... and there are other normal list items
    </RecyclerView>
    <RecyclerView> // another RV with vertical LinearLayoutManager
    </RecyclerView>
</RecyclerView>

问题是当我水平滚动时,内部水平 RecyclerViewHorizontalScrollView 没有按预期滚动。

为什么我必须这样做:

我有几个 ListFragment,每个都从不同的数据源获取数据,但具有相同类型的列表项。我不想要 ViewPager,它要么在创建 ViewPager 后立即保留所有页面(这可能会导致 OutOfMemoryError),要么在用户滚动到其他页面并向后滚动后从网络重新加载数据。此外,使用嵌套 RV,页面可以共享一个相同的 RecyclerViewPool,这有助于减少所有页面中的项目查看总数。

我在外横房车里做了什么:

private static final int INVALID_POINTER = -1;
private int mActivePointerId = INVALID_POINTER;
private float lastX;
private GestureDetector mVerticalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return Math.abs(distanceX) < Math.abs(distanceY);
    }
});
private GestureDetector mHorizontalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return Math.abs(distanceX) > Math.abs(distanceY);
    }
});

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    if (mVerticalScrollGestureDetector.onTouchEvent(e) || !mAllowPageScroll) {
        return false;
    }
    switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mActivePointerId = e.getPointerId(0);
            break;
        case MotionEvent.ACTION_MOVE:
            int pointerIndex = e.findPointerIndex(mActivePointerId);
            float x = e.getX(pointerIndex);
            float dx = x - lastX;
            lastX = x;
            if (childCanScroll(this, false, (int) dx, (int) x)) {
                Log.d("ScrollX", "not intercept");
                return false;
            }
            Log.d("ScrollX", "maybe intercept");
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mActivePointerId = INVALID_POINTER;
            break;
    }
    return super.onInterceptTouchEvent(e);
}

private boolean childCanScroll(View v, boolean considerSelf, int dx, int x) {
    if (v instanceof ViewGroup) {
        ViewGroup group = (ViewGroup) v;
        int scrollX = v.getScrollX();
        for (int i = group.getChildCount() - 1; i >= 0; i--) {
            View child = group.getChildAt(i);
            if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
                    && childCanScroll(child, true, dx, x + scrollX - child.getLeft())) {
                return true;
            }
        }
    }
    return considerSelf && v.canScrollHorizontally(-dx);
}

现在它与内部水平 RV 一起工作,偶尔不滚动根本不与 HorizontalScrollView 一起工作 .

如何实现让内部水平滚动视图先滚动的目标?

我犯了两个错误。

  1. ACTION_DOWN 的情况下,我需要记录 lastX 变量以及在 ACTION_MOVE 的情况下,以便 dx 的第一步] 是一个适当的值。这解释了为什么 childCanScroll 方法不适用于 ScrollView(因为 RecyclerViewgetScrollX() 总是 return 0)。

  2. 在方法childCanScroll中,里面的if语句不仅要考虑x坐标,还要考虑y坐标。如果不考虑 y,当我在内部水平滚动视图外滚动时,该方法也会 return true,这会使事情无法正常工作。

改正这两个错误后一切正常