How to disable ViewPager scrolling on diagonal swipes in Android? - android

I have an Activity with a ViewPager, which hosts several pages, each containing a RecyclerView. Horizontal and vertical swipes are working as expected: Horizontal changes the page, vertical scrolls the RecyclerView.
When the user makes a diagonal swipe, the page changes.
How can I prevent this? Diagonal swipes should only scroll the RecyclerView.
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/page"
android:layout_width="match_parent"
android:layout_height="match_parent" />
I already tried this:
binding.page.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> binding.page.isUserInputEnabled = false
}
return v?.onTouchEvent(event) ?: true
}
})

I just tested out the behavior you described. In my opinion ViewPager2 and RecyclerView work together flawlessly:
On a diagonal swipe with less than 45°, the ViewPager2 gets scrolled.
On diagonal swipes with more than 45°, the RecyclerView gets scrolled.
However, this seems only true when the Activity is newly created. It also seems to depend a little bit on the prior scroll gesture.
When you first make a vertical swipe (RecyclerView gets scrolled), after that, diagonal scrolls again will scroll the RecyclerView.
When you just made a horizontal swipe (ViewPager2 gets scrolled), then diagonal scrolls tend to scroll the ViewPager2.
Maybe this is the intended behavior. To provide a more intuitive user experience,
I'd probably keep it as is, as I had to test quite some time to reproduce your described behavior.
Actually, all the scrolling felt very intuitive the way it is.
And keep in mind: If you really only want to allow exactly 0° horizontal swipes to scroll the ViewPager2, you're giving your users a very hard time.
It would be practically impossible to scroll the ViewPager2.

Related

Swipe in ViewPager2 not working while nested ScrollView is fling-scrolling

I have a ViewPager2 with default horizontal swipe-navigaton. Each "page" has a ScrollView so that content can be scrolled vertically. Normally this works well: When the touch movement starts horizontally it is handled by the ViewPager, when it starts vertically it is handled by the ScrollView.
However after a vertical fling gesture, while the ScrollView is scrolling on its own, any touch movement is handled by the ScrollView, whether it starts horizontally or vertically. So users first have to stop the fling before they can swipe to the next page. How can I change this?
In the Gmail app, when swiping between mails, the behaviour is as I expect it.
I finally found a simple solution. I override onInterceptTouchEvent of a parent view of the ViewPager2, there on ACTION_DOWN, I stop the fling of the ScrollView with fling(0).

Propagate up/down swipe event from ViewPager2 to parent BottomSheet

I'm having a BottomSheetBehavior view which contains a ViewPager2. Both work well and do what they need except trying to swipe the BottomSheet up/down on the ViewPager2.
Again, swipe up/down on BottomSheet works well if the finger is above or below the ViewPager2, except when the finger is ON the ViewPager2.
I need to find out how to propagate the up/down swipe events through the ViewPager2 down to the BottomSheet.
The code is very fragmented, so it'll be complicated to show it here and wouldn't really make sense. I just need the principle of how to propagate the events.
This is due to that the nested scrolling of the inner RecyclerView of the ViewPager2 is enabled by default. To disable it:
viewPager.children.find { it is RecyclerView }?.let {
(it as RecyclerView).isNestedScrollingEnabled = false
}

How to disable fragment scroll in horizontal recyclerview

I have a horizontal recyclerView that I populate with imageViews, so that when the user swipes from right to left, the rest of the images appear one by one. Everything is behaving correctly, images are loaded and horizontal scroll works as expected, even the clickListeners for individual items work fine...
BUT... if the touch gesture to scroll to the right is not a pure horizontal swipe (or left if the user reaches the end of the recyclerView and wants to go back), the fragment where the recyclerView is being loaded scrolls down, too. I want to avoid this behavior, so that when the user intends to reach the images swipping from right to left, the recyclerView only moves in the horizontal direction, ignoring any vertical movement that is performed over the recyclerView. If the user taps or swipes outside of the recyclerView screen area, I want usual behavior to occur (scroll down as expected). I have tried with other alternatives seen in this site, like using the setOnTouchListener option to detect a MotionEvent and act accordingly, as you can check below (this is actual code from my fragment):
advertPicturesRecyclerView.setOnTouchListener { recyclerView, motionEvent ->
when(motionEvent.action) {
MotionEvent.ACTION_UP -> {
Snackbar.make(recyclerView, "Touch down!", Snackbar.LENGTH_LONG).show()
recyclerView.parent.requestDisallowInterceptTouchEvent(true)
true
}
else -> {
recyclerView.parent.requestDisallowInterceptTouchEvent(false)
true
}
}
}
In this context, ACTION_UP means in the direction of the recyclerView, so this just detects movement inside the recyclerView and sort of "passes" vertical movement to the parent (fragment), which scrolls down.
How can I "block" the recyclerView to ignore any vertical swipe, and only move horizontally?
val viewManager = LinearLayoutManager(requireContext(),LinearLayoutManager.HORIZONTAL, false)
myRecylcerView?.layoutManager = viewManager
The above snippet will allow only a horizontal scroll based on your question but I guess there is something else that is causing the problem like you might be using a NestedScrollView/ScrollView, so please provide the layout files and a sample video to have a clear perspective of the problem.
Update - 25th September 2020
You can try the below to your NestedScrollView and recylcerview
<androidx.core.widget.NestedScrollView
android:id="#+id/nsvMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
// .....
// .....
</androidx.core.widget.NestedScrollView
myRecylcerView?.isNestedScrollingEnabled = false
It would be much better if you attach your layout file.

Conflict between recyclerview scroll and viewpager swipe

In my app I have a ViewPager that has four fragments. All the fragments are composed of RecyclerView which can be scrolled vertically. My problem is that when I try to navigate to other fragments and swipe left or right, the RecyclerView's scroll is detected first (mostly) and instead of going to other fragments the RecyclerView gets scrolled.
To be more clear, if I scroll the recyclerView, then suddenly swipe left or right, the viewpager never swipes.
What should I do?
When you scroll your RecyclerView vertically and cross a threshold to trigger scrolling, it consumes the TouchEvent. This means the default behavior is until you release your finger from the screen, the ViewPager will not be able to trigger a horizontal scroll. This is default behavior for how scrolling views interact with each other. You could attempt to override touch handling by extending RecyclerView and ViewPager or having a coordinating view that dispatches all TouchEvents to both views. However, either of these approaches could present a number of issues.
If you were to look at the Play Store for reference, its touch handling works the same as what you are seeing here.

Nested RecyclerView and ViewPager content wrapping and scrolling don't play nice together

I have nested ViewPagers and RecyclerViews as depicted in the image:
Requirements:
The first level ViewPager1 swipes from left to right. It uses FragmentStatePagerAdapter.
The first level RecyclerView1 scrolls vertically.
The second level ViewPager2 does not swipe - the swipe motion is controlled by a TabLayout. It uses a custom PagerAdapter that returns a View as a page.
The second level RecyclerView does not scroll - it simply wraps a list of dynamic items
What I have working so far:
The first level ViewPager1 and RecyclerView1 works as intended.
The ViewPager2 does not show because its height is defined as "wrap_content"
The ViewPager2/RecyclerView2 prevents RecyclerView1 to scroll up/down.
What I have tried:
Setting RecyclerView1.setNestedScrollingEnabled(false) stops it from passing the onTouch event to its children, but because the ViewPager2/RecyclerView2 wraps its content, it does not know what the size it needs to scroll.
Setting the ViewPager2 to a fixed height solves the scrolling problem. But because it is a fixed height, the content of RecyclerView2 is cut off.
Overriding OnMeasure as described here makes ViewPager2's content wrap, perfectly, but the scrolling no longer work again. I assume it is because OnMeasure is called "after" the View has already been attached?
So basically I need help on how to get the content to wrap but in such a way that RecyclerView 1 knows what the height is so that it can scroll.
EDIT
It turns out I was totally off base with point 3. The OnMeasure workaround DOES work as intended and the scrolling problem is NOT caused by recyclerView not knowing the height. It in fact does. The reason why it doesn't scroll is due to multiple nested scrollable view groups. I found this out by putting Log.i on onTouchEvent() and onInterceptTouchEvent() on all the scrollable view groups. Some surface of the views work, but if the surface has another scrollable child, it starts to cause problems.
Setting RecyclerView2.setNestedScrollingEnabled(false) fixed the vertical scrolling. However, now, the ViewPager2's touch behaviour is interfering with ViewPager1's
On closer inspection, ViewPager1 intercepts touch event when hitting non-scrollable surface, causing the ViewPager1 to call its onTouchEvent() to scroll left and right. However, if I start the touch event over a the ViewPager2's surface, ViewPager1 never intercept and it never handles the swipe left to right.
Unlike a RecyclerView, there is no simple method to disable nestedScrolling. So I tried disabling ViewPager2, but that didn't work and caused the inside views such as buttons not clickable.
I tried to return false in ViewPager2's OnTouchEvent so that it bubbles up the chain, but still, the ViewPager1's OnTouchEvent is never fired.
So I'm stuck again, how do I pass the touch event to the parent when the parent did not intercept the event when it should have. Again, I'm assuming, and again I might be off-base, that ViewPager1 might not intercept because ViewPager2 has requested a disallowInterceptTouchEvent() somewhere in its code? I just don't know where or how to begin to fix this problem.

Categories

Resources