Swiping for a short distance and fast should change the page. This is working fine in my Pixel 2 (Android 10) and in Android the emulator. However, we tried a couple of devices where is not working correctly:
This is the behavior on for the vertical ViewPager:

Basically the user needs to scroll all the way down or up (manually) to trigger a change of page. With the horizontal ViewPager (recorded on the same phone), it works just as expected:

Would love to get some help from you @devvit if you know what might be causing this.
@ferrannp, What about this ?
I haven't these devices, only tested on my 5 devices(4.4 - 9.0) :(
Thanks for the reference @devvit! I'll be trying it tomorrow.
Mmm for now still that did not really help @devvit. That just seems to play with the threshold the page needs to be moved to swipe... But I think the problem here is different. If you drag very small distance but fast (with power), it should swipe to the next page. Still investigating... By the way, I was able to reproduce it on an emulator (Note 9 Android 9) using https://developer.samsung.com/remotetestlab/. The only drawback is that every time you want to try something you need to build the production apk.
Ok, I can reproduce the issue with the Android emulator (good to find a solution) on API level 28 (Android P). It seems to work before and after but not on Android P (most popular nowadays).
@ferrannp, Make ViewPager snap with shorter drag
My reflection solution, you can change the value of mMinimumVelocity and mFlingDistance.
public void setOrientation(boolean vertical) {
mVertical = vertical;
if (!mVertical) return;
// Make page transit vertical
setPageTransformer(true, new VerticalPageTransformer());
// Nested scroll issue, follow the link
// https://stackoverflow.com/questions/46828920/vertical-viewpager-with-horizontalscrollview-inside-fragment
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceY) > Math.abs(distanceX);
}
});
// reflection
try {
Field mFlingDistance = ViewPager.class.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.setInt(this, 10);
Field mMinimumVelocity = ViewPager.class.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.setInt(this, 5);
} catch (Exception e) {
Log.e("###", e.getMessage());
}
}
Could you try straya's answer? I'm not sure that.
@devvit I found a a perfect working solution modifying the source code. https://android.googlesource.com/platform/frameworks/support/+/5b614a46f6ffb3e9ca5ab6321c12412550a4e13a/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java#2251. Instead of getXVelocity, if pager is vertical would be getYVelocity.
Can we achieve this by reflection without copying the whole source code?
Looks impossible 馃槗, just change mMinimumVelocity.
Or maybe we commit a PR to android master dev 馃槄
Or I'll fork the internal code here (it will not change anyway as they are with ViewPager2 now). I'll prepare a PR.
Yes, fork it, freeze it, until we migrate to VP2
we can close it, when https://github.com/react-native-community/react-native-viewpager/pull/139 will be merged.
@troZee @ferrannp Is there any workaround to use vertical ViewPager now? I was so excited about native feel viewpager on react native, but found out that it's broken on Android...馃樋
PS: Thanks for working on it!
@todorone you can wait for https://github.com/react-native-community/react-native-viewpager/pull/139 or you can use this branch for now https://github.com/react-native-community/react-native-viewpager/tree/fix/vertical-pager-android-9-bug.
@todorone or you can create patch-package of #139. I need to fix one thing.
Ofc I am not guarantee, that it will work, but you can try.
PS: Thanks for working on it!
<3
@troZee @ferrannp Thanks for prompt feedback, you guys are awesome.
Just tried both fix/vertical-pager-android-9-bug branch and also viewpager2 branch. Both of them didn't help, I still need to swipe with finger for a almost full height distance to make vertical transition happen. Checked on both horizontal and vertical device alignment, Android 9 Mi 5 Note device(Emulator with Android 10 works perfectly)...
@todorone you can use a custom hook to handle touch events by yourself like below:
// the hook
import React, {useState} from 'react';
function TouchGesture(ref) {
const [duration, setDuration] = useState(0);
const [points, setPoints] = useState([0, 0]);
function onTouchStart(e) {
const {pageX, pageY} = e.nativeEvent;
setDuration(Date.now());
setPoints([pageX, pageY]);
}
function onTouchEnd(e) {
const {pageX, pageY} = e.nativeEvent;
if (Date.now() - duration > 200 || Math.abs(points[0] - pageX) > 50) {
return;
}
// this just an example
// you can use viewPager's position (add a onPageSelected function there) to judge which page you'll set;
if (points[1] - pageY > 30) {
ref?.current.setPage(1);
} else if (pageY - points[1] > 30) {
ref?.current.setPage(0);
}
}
return {onTouchEnd, onTouchStart};
}
export default TouchGesture;
// usage
const viewPagerRef = useRef(null);
const {onTouchEnd, onTouchStart} = useTouchGesture(viewPagerRef);
const handleTouchEnd = useCallback(e => onTouchEnd(e), [onTouchEnd]);
const handleTouchStart = useCallback(e => onTouchStart(e), [onTouchStart]);
......
<ViewPager
ref={viewPagerRef}
scrollEnabled={false}
orientation="vertical"
pageMargin={0}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
style={styles.viewPager}
initialPage={0}>
......
</ViewPager>
Most helpful comment
Or I'll fork the internal code here (it will not change anyway as they are with
ViewPager2now). I'll prepare a PR.