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).
Related
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.
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.
I have a viewpager, and each page has a scrollview. I want to be able to do scroll vertically and also change page horizontally without raising the finger. The problem is that if I start moving the finger vertically, the scrollview gets the touch events, but if I start moving the finger horizontally, the event is still consumed by scrollview. So I have to raise the finger, and start a new gesture horizontally.
I would like to change this behaviour creating a custom scrollview. If I override onTouchEvent(), I can detect the direction of the event. So I have made something like:
public boolean onTouchEvent(){
if(isVertical()){
return true;
}
return false;
}
My problem is that when I return false, the scrollview stops scrolling, but the touch events are not passed to the viewpager, so I can't swipe to next or previous page.
I have a complex layout made of a ViewPager with 3 (horizontal) fragments [PAGE].
Each PAGE is made of several (vertical) RecyclerView [CARD].
Each CARD, has a an (horizontal) RecyclerView with several items.
My problem is that, when swiping between the items, the touch event is ofter propagated to the viewPager, resulting in an unwanted page change.
Is there a way to block, when touching a specific area of the screen (i.e. All the area covered by the most inner RecyclerView) the propagation of the touch event, EVEN if no element is touched (there is free space bewteen items, and touching there cause the unwanted page change)
http://i.stack.imgur.com/8vfAI.jpg
Here there is my example, when swiping on the Scrollable Area, I do not want to be able to move to other pages.
Thanks for the help
Example of layout
If your CARD has a custom RecyclerView (for the horizontal recycler), you could call requestDisallowInterceptTouchEvent() on touch events.
This would have the parent RecyclerView as well as the ViewPager stop receiving inputs until the horizontal Recycler stops receiving touch events (the call cleans up itself if I remember right).
I have a ViewPager widget in every row of a ListView. This provides a shelf-like UI, so the user can scroll around searching for a shelf vertically, and then scroll horizontally amongst the contents of a shelf. This works.
But the scrolling experience is terrible: if I start to drag a shelf's ViewPager, scroll it horizontally, and accidentally drag a bit upwards/downwards, then the ListView "traps" this dragging action, and start to scroll vertically, ending my horizontal drag. In this state, the drag action won't "return" to the ViewPager, the ListView has it, and that's it. I have to start another drag action to affect the ViewPager again. So I guess the ListView has precedence in these cases.
How can this be fixed? I'd like to achieve the exact opposite: If the ViewPager inside a list row starts reacting to a horizontal drag, then it should trap that action, and this drag should stop affecting the ListView, no matter how the user moves his/her finger vertically. Can this be done?
I've found a solution in this thread. There the problem is to handle touch events properly for a HorizontalScrollView inside a regular ScrollView, but the solution to that problem seems to apply to this one too.