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

时间:2018-11-07 05:01:05

标签: android-recyclerview android-nestedscrollview

我下面有一个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,这有助于减少所有页面中项目视图的总数。

我在外侧水平RV中所做的事情:

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 个答案:

答案 0 :(得分:0)

我犯了两个错误。

  1. ACTION_DOWN的情况下,我还需要记录lastX变量,在ACTION_MOVE的情况下,因此dx的第一步是合适的值。这就解释了为什么childCanScroll方法在ScrollView之类的东西上不起作用(因为RecyclerView的{​​{1}}总是返回0)。

  2. 在方法getScrollX()中,内部childCanScroll语句不仅应考虑x坐标,还应考虑y坐标。如果不考虑y,则当我在内部水平滚动视图外部滚动时,该方法还会返回if,这会使事情无法正常工作。

我纠正了这两个错误后,一切都正确了。