70

这是我正在尝试使用下面映射的所有元素构建的应用程序:

蔬菜应用

一切正常,但是,我希望内部水平 recyclerview 不捕获任何垂直滚动。所有垂直滚动都必须朝向外部垂直回收视图,而不是水平滚动视图,以便垂直滚动允许工具栏根据它的滚动标志退出视图。

当我将手指放在 recyclerview 的“StrawBerry Plant”部分并向上滚动时,它会滚动出工具栏:

草莓植物

如果我将手指放在水平滚动视图上并向上滚动,它根本不会滚动出工具栏。

以下是我到目前为止的 xml 布局代码。

活动xml 布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fragment_container"
    android:clipChildren="false">

    <android.support.design.widget.CoordinatorLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"
        >

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:minHeight="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlways">

        </android.support.v7.widget.Toolbar>

        <android.support.design.widget.TabLayout
            android:id="@+id/sliding_tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            style="@style/CustomTabLayout"
            />

    </android.support.design.widget.AppBarLayout>
    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />

    </android.support.design.widget.CoordinatorLayout>

</FrameLayout>

Fruits”片段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">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/progressBar"
        android:visibility="gone"
        android:layout_centerInParent="true"
        android:indeterminate="true"/>

<!--    <android.support.v7.widget.RecyclerView-->
    <com.example.simon.customshapes.VerticallyScrollRecyclerView
        android:id="@+id/main_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>

我使用了一个名为 VerticallyScrollRecyclerView 的自定义类,它遵循谷歌在视图组中处理触摸事件的示例。它的目的是拦截和消耗所有垂直滚动事件,以便它滚动进/出工具栏:http: //developer.android.com/training/gestures/viewgroup.html

VerticallyScrollRecyclerView的代码如下:

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 super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                Log.e("VRecView", "its moving");
                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 (Math.abs(yDiff) > 5) {
                    // Start scrolling!
                    Log.e("Scroll", "we are scrolling vertically");
                    mIsScrolling = true;
                    return true;
                }
                break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }


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

}

收藏夹”布局是垂直回收视图中的回收视图:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@color/white"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Favourite"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="16dp"
        android:id="@+id/header_fav"/>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_below="@+id/header_fav"
        android:id="@+id/recyclerview_fav">
    </android.support.v7.widget.RecyclerView>

</RelativeLayout>

这一直困扰着我一段时间,我还没有想出一个解决方案。有谁知道如何解决这个问题?

5 分给 Griffindor 的正确答案,当然还有 SO 上的声望分。

4

7 回答 7

126

测试解决方案:

所有你需要的是调用你的 mInnerRecycler.setNestedScrollingEnabled(false);内心RecyclerView


说明

RecyclerView支持API 21通过实现NestedScrollingChild接口引入的嵌套滚动。当您在另一个视图内部有一个沿相同方向滚动的滚动视图并且您只想View在聚焦时滚动内部视图时,这是一个有价值的功能。

在任何情况下,初始化时RecyclerView默认调用自身。RecyclerView.setNestedScrollingEnabled(true);现在,回到问题,因为你的两个RecyclerViews 都在同一个 s 中ViewPager,当你从内部滚动时AppBarBehaviorCoordinateLayout必须决定要响应哪个滚动RecyclerView;当您的 innerRecyclerView的嵌套滚动启用时,它会获得滚动焦点,并且CoordinateLayout将选择响应其在 outer 滚动上RecyclerView的滚动。问题是,由于您的内部RecyclerViews 不垂直滚动,因此没有垂直滚动变化(从CoordinateLayout' 的角度来看),如果没有变化, theAppBarLayout也不会改变。

在您的情况下,因为您的 innerRecyclerView正在向不同的方向滚动,您可以禁用它,从而导致CoordinateLayout忽略其滚动并响应 externalRecyclerView的滚动。


注意事项

xml 属性android:nestedScrollingEnabled="boolean"不打算与 一起使用RecyclerView,并且尝试使用android:nestedScrollingEnabled="false"将导致结果java.lang.NullPointerException,至少现在,您必须在代码中执行此操作。

于 2015-11-04T23:09:01.470 回答
7

如果有人还在寻找,试试这个:

private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f

mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
        override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {

            }

        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
                when (e.action) {
                    MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
                    MotionEvent.ACTION_MOVE -> {
                        if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
                            rv.parent.requestDisallowInterceptTouchEvent(true)
                        } else if (Math.abs(e.y - preY) > Y_BUFFER) {
                            rv.parent.requestDisallowInterceptTouchEvent(false)
                        }

                    }
                }
                preX = e.x
                preY = e.y
                return false
            }

            override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
            }
        })

它检查当前是否水平滚动然后不允许父处理事件

于 2019-04-13T09:57:22.453 回答
5

我有点晚了,但这肯定适用于面临同样问题的其他人

 mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                int action = e.getAction();
               // Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
                switch (action) {
                    case MotionEvent.ACTION_POINTER_UP:
                        rv.getParent().requestDisallowInterceptTouchEvent(true);

                        break;
                }
                return false;
            }
于 2016-05-28T17:13:56.843 回答
3

经过测试的解决方案,使用自定义的NestedScrollView().

代码:

public class CustomNestedScrollView extends NestedScrollView {
    public CustomNestedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
         // Explicitly call computeScroll() to make the Scroller compute itself
            computeScroll();
        }
        return super.onInterceptTouchEvent(ev);
    }
}
于 2016-04-28T12:56:16.843 回答
3

尝试

public OuterRecyclerViewAdapter(List<Item> items) {
    //Constructor stuff
    viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //Create viewHolder etc
    holder.innerRecyclerView.setRecycledViewPool(viewPool);

}

内部 recylerview 将使用相同的视图池,它会更流畅

于 2018-11-02T04:15:33.837 回答
1

我建议你在像谷歌应用程序这样的片段中添加水平回收器视图

于 2018-09-21T06:45:45.643 回答
-1

我通读了使用 setNestedScrollingEnabled 为 false 提供的答案,这对我来说很糟糕,因为它使 recyclerview 无法回收,如果你有一个巨大的列表,你可能会在内存中崩溃,可能会出现性能问题等。所以我会给你一个算法,让它在没有任何代码的情况下工作。

在垂直recyclerview上有一个监听器,比如滚动监听器。每当滚动列表时,您都会收到一个回调,表明它正在滚动。您还应该在空闲时收到回电。

现在,当垂直 recylerview 被滚动时,水平列表上的 setNestedScrollingEnabled = false

一旦垂直 recyclerview 空闲 setNestedScrollingEnabled = true 在同一个水平列表上。

还最初在xml中将水平recyclerview设置为setNestedScrollingEnabled = false

当 coordinatorLayout 与不同方向的两个 recyclerviews 混淆时,这也可以很好地与 appbarlayout 一起使用,但这是另一个问题。

让我们看一下 kotlin 中另一种更简单的方法,用一个真实的例子来完成。这里我们只关注水平的recyclerView:

假设我们有 RecyclerViewVertical & RecyclerViewHorizo​​ntal:

 RecyclerViewHorizontal.apply {
                addOnScrollListener(object : RecyclerView.OnScrollListener() {
                    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                        isNestedScrollingEnabled = RecyclerView.SCROLL_STATE_IDLE != newState
                    }
                })
            }

这段代码的意思是,如果 RecyclerViewHorizo​​ntal 不是空闲的,则启用nestedScrolling,否则禁用它。这意味着当它空闲时,我们现在可以在 coordinatorlayout 中使用 RecyclerViewVertical 而不会受到 RecyclerViewHorizo​​ntal 的任何干扰,因为我们在它空闲时禁用了它。

于 2021-06-20T07:43:54.660 回答