嵌套的RecyclerView.如何在子级RecyclerView滚动时阻止父级RecyclerView滚动?

nik*_*hil 28 android android-recyclerview nestedrecyclerview

我正在尝试实现一个水平recyclerview,每个项目recyclerview将是一个垂直recyclerview的网格布局.我面临的问题是,当我尝试recyclerview垂直滚动子项时,父项recyclerview会抓取滚动并开始水平滚动.我尝试解决这个问题的方法是,

  1. setNestedScrollingEnabled(false) 在父母身上 recyclerview
  2. onTouch()孩子的recyclerviewrecyclerview通过调用禁用父亲的触摸事件requestdisallowinterceptTouchevent(false)

以上解决方案都没有为问题提供完美的解决方案.任何帮助表示赞赏

Roh*_*rya 27

这个问题对我来说似乎很有趣.所以我试着实现,这就是我所取得的成果(你也可以看到这里视频),这非常流畅.

在此输入图像描述

所以你可以尝试这样的事情:

定义像这样的CustomLinearLayoutManager扩展LinearLayoutManager:

public class CustomLinearLayoutManager extends LinearLayoutManager {

    public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public boolean canScrollVertically() {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

并将此设置CustomLinearLayoutManager为您的父级RecyclerView.

RecyclerView parentRecyclerView = (RecyclerView)findViewById(R.id.parent_rv);
CustomLinearLayoutManager customLayoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false);
parentRecyclerView.setLayoutManager(customLayoutManager);
parentRecyclerView.setAdapter(new ParentAdapter(this)); // some adapter
Run Code Online (Sandbox Code Playgroud)

现在为child RecyclerView,定义自定义CustomGridLayoutManager扩展GridLayoutManager:

public class CustomGridLayoutManager extends GridLayoutManager {

    public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public CustomGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public CustomGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public boolean canScrollHorizontally() {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其设置为layoutManger孩子RecyclerView:

childRecyclerView = (RecyclerView)itemView.findViewById(R.id.child_rv);
childRecyclerView.setLayoutManager(new CustomGridLayoutManager(context, 3));
childRecyclerView.setAdapter(new ChildAdapter()); // some adapter
Run Code Online (Sandbox Code Playgroud)

所以基本上父母RecyclerView只是听水平卷轴而孩子RecyclerView只是听垂直卷轴.

除此之外,如果您还想处理对角线滑动(它几乎不会倾斜到垂直或水平),您可以在父级中 包含一个手势监听器RecylerView.

public class ParentRecyclerView extends RecyclerView {

    private GestureDetector mGestureDetector;

    public ParentRecyclerView(Context context) {
        super(context);
        mGestureDetector = new GestureDetector(this.getContext(), new XScrollDetector());
       // do the same in other constructors
    }

   // and override onInterceptTouchEvent

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

}
Run Code Online (Sandbox Code Playgroud)

哪里XScrollDetector

class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return Math.abs(distanceY) < Math.abs(distanceX);
        }
}
Run Code Online (Sandbox Code Playgroud)

因此ParentRecyclerView要求子视图(在我们的例子中,VerticalRecyclerView)来处理滚动事件.如果子视图处理,则父进程不执行任何其他操作,父进程最终处理滚动.

  • 令人惊奇的答案是,对于我而言,我不需要使用整个解决方案,只需使用SimpleOnGestureListener部分,它就可以完美地工作。谢谢。 (2认同)

nat*_*rio 8

父回收站视图上的setNestedScrollingEnabled(false)

你可以尝试是setNestedScrollingEnabled(false)孩子 RecyclerView,如果有的话.RecyclerView的nestedscroll-ness是一个孩子的(这就是它实现的原因NestedScrollingChild).

在子recyclelerview的onTouch()中,我通过调用requestdisallowinterceptTouchevent(false)禁用父recyclelerview上的触摸事件

这应该有效,但你应该做的requestDisallowInterceptTouchEvent(true)不是false.如果您继承RecyclerView,则可以覆盖onTouchEvent:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) {
        // ensure we release the disallow request when the finger is lifted
        getParent().requestDisallowInterceptTouchEvent(false);
    } else {
        getParent().requestDisallowInterceptTouchEvent(true);
    }
    // Call the super class to ensure touch handling
    return super.onTouchEvent(event);
}
Run Code Online (Sandbox Code Playgroud)

或者,从外面听一听,

child.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (v.getId() == child.getId()) {
            if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) {
                // ensure we release the disallow request when the finger is lifted
                child.getParent().requestDisallowInterceptTouchEvent(false);
            } else {
                child.getParent().requestDisallowInterceptTouchEvent(true);
            }
        }
        // Call the super class to ensure touch handling
        return super.onTouchEvent(event);
    }
});
Run Code Online (Sandbox Code Playgroud)


Nic*_*oso 7

我通过对你(以及其他所有人)采取相反的方法,在类似的项目中解决了这个问题.

我不让孩子告诉父母何时停止查看事件,而是让父母决定何时忽略(基于方向).这种方法需要一个自定义视图,虽然可以多做一点工作.下面是我创建的将用作外部/父视图的内容.

public class DirectionalRecyclerView extends RecyclerView {

    private static float LOCK_DIRECTION_THRESHOLD; //The slop
    private float startX;
    private float startY;
    private LockDirection mLockDirection = null;

    public DirectionalRecyclerView(Context context) {
        super(context);
        findThreshold(context);
    }

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

    public DirectionalRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        findThreshold(context);
    }

    private void findThreshold(Context context) {
        //last number is number of dp to move before deciding that's a direction not a tap, you might want to tweak it
        LOCK_DIRECTION_THRESHOLD = context.getResources().getDisplayMetrics().density * 12;
    }

    //events start at the top of the tree and then pass down to
    //each child view until they reach where they were initiated
    //unless the parent (this) method returns true for this visitor
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mLockDirection == null) {
                    float currentX = event.getX();
                    float currentY = event.getY();
                    float diffX = Math.abs(currentX - startX);
                    float diffY = Math.abs(currentY - startY);
                    if (diffX > LOCK_DIRECTION_THRESHOLD) {
                        mLockDirection = LockDirection.HORIZONTAL;
                    } else if (diffY > LOCK_DIRECTION_THRESHOLD) {
                        mLockDirection = LockDirection.VERTICAL;
                    }
                } else {
                    //we have locked a direction, check whether we intercept
                    //the future touches in this event chain
                    //(returning true steals children's events, otherwise we'll
                    // just let the event trickle down to the child as usual)
                    return mLockDirection == LockDirection.HORIZONTAL;
                }
                break;
            case MotionEvent.ACTION_UP:
                mLockDirection = null;
                break;
        }
        //dispatch cancel, clicks etc. normally
        return super.onInterceptTouchEvent(event);
    }

    private enum LockDirection {
        HORIZONTAL,
        VERTICAL
    }

}
Run Code Online (Sandbox Code Playgroud)


Ume*_*mer 5

现在您可以尝试,因为 Google 修复了使用(Issue 197932)android:nestedScrollingEnabled的崩溃nestedScrollingEnabled


Sha*_*auf 5

将侦听器设置为嵌套的RecyclerView

 View.OnTouchListener listener = new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_MOVE
                            ) {
                        v.getParent().requestDisallowInterceptTouchEvent(true);

                    } else {
                        v.getParent().requestDisallowInterceptTouchEvent(false);

                    }
                    return false;
                }
            };

            mRecyclerView.setOnTouchListener(listener);
Run Code Online (Sandbox Code Playgroud)


And*_*rey 3

尝试这个。对于我的用例,它有效:

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});
Run Code Online (Sandbox Code Playgroud)