31

我正在尝试实现一个水平的recyclerview,每个项目都recyclerview将是一个recyclerview带有网格布局的垂直。我面临的问题是,当我尝试recyclerview垂直滚动孩子时,有时父母recyclerview会拿起滚动并开始水平滚动。我试图解决这个问题的方法是,

  1. setNestedScrollingEnabled(false)在父母身上recyclerview
  2. onTouch()孩子中,我通过调用recyclerview禁用父级的触摸事件recyclerviewrequestdisallowinterceptTouchevent(false)

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

4

14 回答 14

32

这个问题对我来说似乎很有趣。所以我尝试实现,这就是我实现的(你也可以在这里看到视频),非常顺利。

在此处输入图像描述

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

像这样定义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;
    }
}

并将其设置CustomLinearLayoutManager为您的 parent 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

现在对于 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;
    }
}

并将其设置为layoutManger孩子RecyclerView

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

所以基本上父母RecyclerView只听水平滚动,孩子RecyclerView只听垂直滚动。

除此之外,如果您还想处理对角滑动(几乎不偏向垂直或水平),您可以在parent 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);
    }

}

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);
        }
}

因此ParentRecyclerView要求子视图(在我们的例子中为 VerticalRecyclerView)来处理滚动事件。如果子视图处理,那么父视图不会做任何事情,父视图最终会处理滚动。

于 2016-04-19T09:41:04.823 回答
8

我在一个类似的项目中通过对您(以及这里的其他人)采取相反的方法解决了这个问题。

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

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
    }

}
于 2016-04-13T09:43:09.117 回答
8

父recyclerview上的setNestedScrollingEnabled(false)

如果有的话,您可以尝试setNestedScrollingEnabled(false)RecyclerView 上。RecyclerView 的嵌套滚动特性是一个孩子的特性(这就是它实现的原因NestedScrollingChild)。

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

这应该有效,但你应该做的是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);
}

或者,通过外部的触摸监听器,

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);
    }
});
于 2016-04-13T09:04:14.283 回答
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);
于 2016-04-19T13:36:35.510 回答
5

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

于 2016-04-17T13:24:25.187 回答
3

试试下面的代码,希望它能工作。

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                   switch (action) {
                  case MotionEvent.ACTION_DOWN:
                     // Disallow parent to intercept touch events.
                     v.getParent().requestDisallowInterceptTouchEvent(true);
                     break;

                 case MotionEvent.ACTION_UP:
                    // Allow parent to intercept touch events.
                    v.getParent().requestDisallowInterceptTouchEvent(false);
            break;
        }

                  // Handle inner(child) touch events.
                    v.onTouchEvent(event);
        return true;
                }
            });
于 2016-04-18T07:24:39.423 回答
3

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

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});
于 2016-04-13T06:57:56.303 回答
2

我对单元格使用卡片视图,并在子适配器中使用子单元 itemView 停用父回收器视图滚动setOnClickListener

holder.itemView.setOnTouchListener { view, _ ->
    view.parent.parent.requestDisallowInterceptTouchEvent(true)
    false
}

我们之所以打电话view.parent.parent是因为这itemView是一个单元格布局,它的父级是我们的子级recyclerView,而且我们需要到达子级 recyclerView 的父级以防止父recyclerView级滚动。

于 2020-01-17T09:58:52.737 回答
1

你应该这样做:

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {

        @Override
        public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
            // Handle on touch events here
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:

                    recycler.getParent().requestDisallowInterceptTouchEvent(true);

                    break;

                case MotionEvent.ACTION_UP:

                    recycler.getParent().requestDisallowInterceptTouchEvent(false);

                    break;

                case MotionEvent.ACTION_MOVE:

                    recycler.getParent().requestDisallowInterceptTouchEvent(true);

                    break;
            }


        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
            return false;
        }

    });

希望这会对你有所帮助。

于 2016-04-16T10:05:01.963 回答
1

尝试下面的代码滚动内部 RecyclerView。

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {

        @Override
        public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
            // Handle on touch events here
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    // Disallow Parent RecyclerView to intercept touch events.
                    recycler.getParent().requestDisallowInterceptTouchEvent(true);
                    break;

                case MotionEvent.ACTION_UP:
                    // Allow Parent RecyclerView to intercept touch events.
                    recycler.getParent().requestDisallowInterceptTouchEvent(false);
                    break;
            }


        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
            return false;
        }

    });
于 2016-04-13T07:26:14.397 回答
1

使用此代码关闭滚动recyclerview

recyclerView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});
于 2016-04-13T11:23:19.957 回答
1

IMO,您可以在外部 RecyclerView 的适配器内尝试以下操作:

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false);

        RVAdapter2 recyclerViewAdapter2 = new RVAdapter2();
        RecyclerView innerRV = (RecyclerView) v.findViewById(R.id.rv2);
        // Setup layout manager for items
        LinearLayoutManager layoutManager2 = new LinearLayoutManager(parent.getContext());
        // Control orientation of the items
        layoutManager2.setOrientation(LinearLayoutManager.VERTICAL);
        innerRV.setLayoutManager(layoutManager2);
        innerRV.setAdapter(recyclerViewAdapter2);

        innerRV.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                recyclerView.getParent().requestDisallowInterceptTouchEvent(true);
            }
        });

        return new MyViewHolder(v);
    }

对于 API23,您也可以尝试innerRV.setOnScrollChangeListener,因为setOnScrollListener已弃用。

更新:

另一种选择是使用addOnScrollListener而不是setOnScrollListener

希望能帮助到你!

于 2016-04-13T08:58:10.987 回答
0

问题存在于 Android 对 RecyclerView 的 onInterceptTouchEvent() 方法的实现中。该博客指出了问题并对其进行了修复 - http://nerds.headout.com/fix-horizo​​ntal -scrolling-in-your-android-app/ 。唯一的区别是父级垂直滚动,子级水平滚动。但是该解决方案需要注意它也应该适用于您的情况。

于 2017-03-28T12:20:04.017 回答
-1

像这样扩展自定义布局管理器

 public class CustomLayoutManager extends LinearLayoutManager {
 private boolean isScrollEnabled = true;

 public CustomGridLayoutManager(Context context) {
 super(context);
        }
  @Override
 public boolean canScrollVertically() {
 return false;
 }
}

将布局管理器设置为此“自定义布局管理器”

于 2016-04-13T18:57:15.640 回答