Android:垂直RecyclerView中的水平RecyclerView

5
这似乎是Android中的一个相当常见的模式。我试图创建类似于Facebook显示其广告的东西:

Facebook ads

你可以看到他们有一个外部的垂直RecyclerView,其中一个内部的水平RecyclerView是外部垂直RecyclerView适配器中的一项。我遵循了谷歌的“在ViewGroup中管理触摸事件”的指南 - http://developer.android.com/training/gestures/viewgroup.html,因为RecyclerView扩展了ViewGroup,那里的代码与我想要做的类似。我对其进行了一些自定义,以便它检测Y轴上的移动而不是X轴上的移动,并将其应用于外部垂直RecyclerView。
/**
 * Created by Simon on 10/11/2015.
 *///This is the recyclerview that would allow all vertical scrolls
public class VerticallyScrollRecyclerView extends RecyclerView {


    public VerticallyScrollRecyclerView(Context context) {
        super(context);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    ViewConfiguration vc = ViewConfiguration.get(this.getContext());
    private int mTouchSlop = vc.getScaledTouchSlop();
    private boolean mIsScrolling;
    private float startY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll.
            mIsScrolling = false;
            startY = ev.getY();
            return false; // Do not intercept touch event, let the child handle it
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                if (mIsScrolling) {
                    // We're currently scrolling, so yes, intercept the
                    // touch event!
                    return true;
                }

                // If the user has dragged her finger horizontally more than
                // the touch slop, start the scroll

                // left as an exercise for the reader
                final float yDiff = calculateDistanceY(ev.getY());
                Log.e("yDiff ", ""+yDiff);
                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (yDiff > mTouchSlop) {
                    // Start scrolling!
                    Log.e("Scroll", "we are scrolling vertically");
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
        }
        return false;
    }

    private float calculateDistanceY(float endY) {
        return startY - endY;
    }

}

我的xml布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_recyclerview_holder">

    <com.example.simon.customshapes.VerticallyScrollRecyclerView
        android:id="@+id/main_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
/>

</RelativeLayout>

当我尝试拖动内部的水平RecyclerView(希望触摸事件会被外部的垂直RecyclerView截取),但外部的RecyclerView完全不会垂直滚动。同时它还会出现以下错误提示:“处理滚动时出错;未找到ID为-1的指针索引。是否有任何MotionEvents被跳过?”。有人知道如何正确解决这个问题吗?

1
为什么需要创建自定义的VerticallyScrollRecyclerView?不能只使用水平的LinearLayoutManager吗? - rciovati
我在内部的水平RecyclerView中使用了一个水平的LinearLayoutManager。外部的RecyclerView使用垂直的LinearLayoutManager。这个问题是围绕着实现触摸事件的正确代码展开的,我需要VerticallyScrollRecyclerView,因为我需要它拦截所有移动的触摸事件,以便它可以正确地滚动。如果不这样做,内部和外部的RecyclerView之间会有触摸冲突。 - Simon
你能找到解决方案了吗? - Shubham
尝试在这里使用此解决方案:https://dev59.com/Lek6XIcBkEYKwwoYEP3x - Simon
1个回答

1

我曾经遇到了类似的问题,但在网上找不到任何解决方案。在我的应用程序中,我有一个自定义交互元素列表,您可以通过水平滑动与之交互(在这种情况下,应锁定滚动),但是当您垂直滑动时,它应该滚动。根据您的评论,您已经解决了您的问题,但对于那些问题类似于我的并且研究导致他们来到这里的人,我将发布我的解决方案。我稍微研究了一下 RecyclerView 的内部结构,以下是我的发现。“处理滚动时出错;找不到ID为-1的指针索引。是否跳过了任何MotionEvents?”- 问题在于变量mScrollPointerId用于获取index,在ACTION_DOWN发生时在onTouchEvent中设置。在我特定的情况下,当ACTION_DOWN发生时,我从onInterceptTouchEvent返回false,因为此时我不知道用户是想滚动还是与列表元素交互。在这种情况下,onTouchEvent不会被调用。后来,当我发现用户想要与元素交互时,我从onInterceptTouchEvent返回false,然后调用onTouchEvent,但是它无法处理滚动,因为mScrollPointerId未设置。这里的解决方案就是在ACTION_DOWN发生时从onInterceptTouchEvent调用onTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
    ...
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            onTouchEvent(e);
            ...
            break;
            }
        ...
        }
    ...
}

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