10

I have a horizontal RecyclerView inside a Complex hierarchy that looks like this -

<ViewPager id="+@id/first">
    <ViewPager id="+@id/second"> this viewpager is taking away the scroll event
        <SwipeToRefreshLayout>
            <RecyclerView> //this one is vertical
                <RecyclerView id="@id/rv1"> //this one is horizontal
                <RecyclerView id="@id/rv2"> //this one is working fine (different type)
            </RecyclerView>
        </SwipeToRefreshLayout>
    </ViewPager>
</ViewPager>

Now the problem is that the second ViewPager is hijacking the scroll from Horizontal RV. There are 2 type of Horizontal RV (RV1 and RV2) in that vertical RV. But only one of them (RV1) is facing this problem. The second one (RV2) is working properly. Also when I press and hold then the scrolling is working fine. And when RV1 is already scrolling and has not settle, then also scrolling works fine. I have referred to other answers talking about setting nestedScrolling false. Nothing seems to be working.

Rishabh876
  • 3,010
  • 2
  • 20
  • 37

5 Answers5

16

You can achieve this by overriding onInterceptTouchEvent method:

 mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent event) {
                int action = event.getAction();
               
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        rv.getParent().requestDisallowInterceptTouchEvent(true);

                        break;
                
                }
                return false;
            }

            @Override
            public void onTouchEvent(@NonNull RecyclerView view, @NonNull MotionEvent event) {

            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

            }
        });
Astrit Veliu
  • 1,252
  • 1
  • 12
  • 22
  • 2
    But it blocks the vertical movement of the screen as well while interacting with recycler-view. – shehzy Aug 22 '21 at 10:23
6

This answer is Kotlin version of @Astril Veliu's answer

yourRecyclerView.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
            
            override fun onTouchEvent(view: RecyclerView, event: MotionEvent) {}
            
            override fun onInterceptTouchEvent(view: RecyclerView, event: MotionEvent): Boolean {
                when (event.action) {
                    MotionEvent.ACTION_DOWN -> {
                        yourRecyclerView.parent?.requestDisallowInterceptTouchEvent(true)
                    }
                }
                return false
            }
            
            override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
        })
Astrit Veliu
  • 1,252
  • 1
  • 12
  • 22
OhhhThatVarun
  • 3,981
  • 2
  • 26
  • 49
2

I propose a solution, following Max Zonov's answer:

addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
                @Override
                public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                    ViewParent vp = rv.getParent();
                    int action = e.getAction();
                    //check if can scroll horizontally both to right and to left
                    boolean tolLeft = rv.canScrollHorizontally(-1);
                    boolean tolRight = rv.canScrollHorizontally(1);
                    if (tolRight || tolLeft) {
                        if (action == MotionEvent.ACTION_MOVE) {
                            int historySize = e.getHistorySize();
                            boolean b = true;
                            if (historySize > 0) {
                                float x2 = e.getX();
                                float y2 = e.getY();
                                float x1 = e.getHistoricalX(0);
                                float y1 = e.getHistoricalY(0);
                                //check if user's touch is not going up or down the screen
                                //and if he/she's moving right if possible
                                //or left if possible
                                b = Math.abs(x2 - x1) > Math.abs(y2 - y1) &&
                                        (
                                                (tolLeft && (x2 - x1) > 0) ||
                                                        (tolRight && (x2 - x1) < 0)
                                        );
                            }
                            vp.requestDisallowInterceptTouchEvent(b);
                        }
                        return false;
                    }else {
                        if (action == MotionEvent.ACTION_MOVE)
                            vp.requestDisallowInterceptTouchEvent(false);
                        rv.removeOnItemTouchListener(this);
                        return true;
                    }
                }
            });

I just added some checking otherwise (at least in my case) the nested RecyclerView would stop vertical scrolling of the "nesting" RecyclerView. Besides, I think it's better to handle also backward scrolling in case the ViewPager's fragment is not the first one.

DharmanBot
  • 1,066
  • 2
  • 6
  • 10
livto
  • 39
  • 1
  • 7
1

View pager and horizontal recycler view both can scroll horizontally so it became difficult for the OS to find out which one to scroll. So You need to use non-swipeable ViewPager in place of your second ViewPager, check this answer for how to use non-swipeable ViewPager.

Suraj Vaishnav
  • 7,777
  • 4
  • 43
  • 46
0

You can use OhhhThatVarun's answer if you don't want to change ViewPager fragment after scrolling to the end.

If you want to change ViewPager fragment after scrolling to the end:

recyclerView.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {

    override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
        val action = e.action
        if (recyclerView.canScrollHorizontally(RecyclerView.FOCUS_FORWARD) || binding.photos.canScrollHorizontally(-RecyclerView.FOCUS_FORWARD)) {
            when (action) {
               MotionEvent.ACTION_MOVE -> rv.parent
                        .requestDisallowInterceptTouchEvent(true)
            }
            return false
        }
        else {
            when (action) {
                MotionEvent.ACTION_MOVE -> rv.parent
                        .requestDisallowInterceptTouchEvent(false)
            }
            recyclerView.removeOnItemTouchListener(this)
            return true
        }
    }

    override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
    override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
})
Neuron
  • 1,020
  • 2
  • 13
  • 28