Nested RecyclerView. How to prevent parent RecyclerView from getting scrolled while child RecyclerView is scrolling?

前端 未结 14 1275
余生分开走
余生分开走 2020-12-07 21:24

I am trying to implement a horizontal recyclerview and each item of the recyclerview will be a vertical recyclerview with a grid layou

相关标签:
14条回答
  • 2020-12-07 21:34

    IMO, you can try the following inside the Adapter of outer 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);
        }
    

    For API23, you can also try innerRV.setOnScrollChangeListener because setOnScrollListener is deprecated.

    UPDATE:

    Another option is using addOnScrollListener instead of setOnScrollListener

    Hope it helps!

    0 讨论(0)
  • 2020-12-07 21:35

    The problem seemed interesting to me. So I tried to implement and this is what I achieved (you can also see the video here) which is pretty smooth.

    So you can try something like this:

    Define CustomLinearLayoutManager extending LinearLayoutManager like this:

    public class CustomLinearLayoutManager extends LinearLayoutManager {
    
        public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
        }
    
        @Override
        public boolean canScrollVertically() {
            return false;
        }
    }
    

    and set this CustomLinearLayoutManager to your 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
    

    Now for child RecyclerView, define custom CustomGridLayoutManager extending 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;
        }
    }
    

    and set it as layoutManger to the child RecyclerView:

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

    So basically parent RecyclerView is only listening to horizontal scrolls and child RecyclerView is only listening to vertical scrolls.

    Along with that, if you also want to handle diagonal swipe (which is little skewed to either vertical or horizontal), you can include a gesture listener in the 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);
        }
    
    }
    

    Where XScrollDetector is

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

    Thus ParentRecyclerView asks child view (in our case, VerticalRecyclerView) to handle the scroll event. If the child view handles then parent does nothing else parent eventually handles the scroll.

    0 讨论(0)
  • 2020-12-07 21:37

    Try below code to scroll inner 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;
            }
    
        });
    
    0 讨论(0)
  • Try this. For my use-case it has worked:

    nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return true;
        }
    });
    
    0 讨论(0)
  • 2020-12-07 21:41

    setNestedScrollingEnabled(false) on the parent recyclerview

    What you could try is setNestedScrollingEnabled(false) on the child RecyclerView, if any. RecyclerView 's nestedscroll-ness is that of a child (that's why it implements NestedScrollingChild).

    In the onTouch() of the child recyclerview I disable touch events on the parent recyclerview by called requestdisallowinterceptTouchevent(false)

    This should work, but what you should do is requestDisallowInterceptTouchEvent(true), not false. If you subclass RecyclerView, you can override 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);
    }
    

    Or, with a touch listener from outside,

    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);
        }
    });
    
    0 讨论(0)
  • 2020-12-07 21:41

    try the below code, hope it will work.

    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;
                    }
                });
    
    0 讨论(0)
提交回复
热议问题