-
-
Notifications
You must be signed in to change notification settings - Fork 958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ScrollView nested within a PanGestureHandler #420
Comments
This is my implementation:
import React from "react";
import { PanGestureHandler, State, ScrollView } from 'react-native-gesture-handler';
class Example extends React.Component {
ref = React.createRef();
scrollRef = React.createRef();
state = {
enable: true
};
_onScrollDown = (event) => {
if (!this.state.enable) return;
const {translationY} = event.nativeEvent;
// handle PanGesture event here
};
_onScroll = ({nativeEvent}) => {
if (nativeEvent.contentOffset.y <= 0 && !this.state.enable) {
this.setState({enable: true });
}
if (nativeEvent.contentOffset.y > 0 && this.state.enable) {
this.setState({enable: false});
}
};
render () {
const { enable } = this.state;
return (
<ScrollView
ref={this.scrollRef}
waitFor={enable ? this.ref : this.scrollRef}
scrollEventThrottle={40}
onScroll={this._onScroll}
>
<PanGestureHandler
enabled={enable}
ref={this.ref}
activeOffsetY={5}
failOffsetY={-5}
onGestureEvent={this._onScrollDown}
>
</PanGestureHandler>
</ScrollView>
);
}
} |
I am trying to achieve the same effect as in the video above. Controlling Judging by the amount of apps with this kind of bottom panels with scrollable content, I'd say it's quite a popular case. Maybe the lib already allows to implement that somehow. Would really appreciate any feedback from the library authors. |
Using scrollview/flatlist from the gesture library permits this to work well on iOS but doesn't seem to work in Android. @hssrrw is correct that controlling enabled is too slow and the pangesturehandler responds to quick before its disabled. If we could define a region within a pangesturehandler that won't respond that could work in many if not most use cases. I achieved this in another way by using interpolate and transform on an outer view to track the pangesturehandler transY position. Then only wrap the upper region above the scrollview with the pangesturehandler. I've switched to using the interactable object now and I can't quite grasp how to achieve the same just yet but I'll work on it. |
I know this isn't ideal for everyone but this should cover various use cases. I've modified the render output of Interactable.js to something like this:
The above is just an inversion of the pangesturehandler and the transforming view with the body separated out of the pangesturehandler. The idea is that you take the header section of your draggable object, which includes the handler bar, and insert it as your this.props.header and the rest of the contents, including scrollview, likewise into this.props.body of your component like so:
This way, you can still have as large an area to let the user drag your object up or down but the pangesturehandler won't interfere with the user's scrolling of the contents of that object. The only disadvantage is that the user can't drag the object up or down directly in the scrollview area. In many use cases, like ours, this is exactly what you don't want to happen so this works. Tested on both Android and iOS and works great. |
I'm also stuck on Android. I have a
I'm on Android, using RN 0.59.5. |
Take a look at the bottom sheet example, it seems to be what you want https://github.com/kmagiera/react-native-gesture-handler/blob/master/Example/bottomSheet/index.js The behaviour is also implemented by this library https://github.com/osdnk/react-native-reanimated-bottom-sheet Although the bottom sheet example seemed a bit buggy when I tested it (jumped around if you scrolled it up and down really fast) and reanimated-bottom-sheet doesn't support nesting a ScrollView. The nested view will scroll, but it has to be a plain View without its own scrolling behaviour. |
I totally agree with @hssrrw , I think there are really common usecases where you want to toggle a gesture handler and right now you need to come back to the JavaScript thread to do that. And even by doing that, I have to admit it was really buggy when I needed to do a setState while doing a gesture. Moreover, because of those setState I've needed to use |
Did you fix it ? same issue on android |
Any update about this case ? |
Our results are confusing. On a Samsung S9 and S10, the scrollview from RN works. But on all other phones we tested including Pixel, scrollview from Gesture Handler works. The S9 doesn't work with scrollview when imported from Gesture Handler though. |
@pribeh i got similar results. |
All I did was change my import from and it worked. Edit:For people experiencing issues on Android, make sure the hitbox of your Sometimes eventhough you can see a view, the hitbox of that view (where the user can click/tap on) is not the same as what you're actually seeing. In our case we had a |
Can confirm that what @SudoPlz suggested work as intended, my nested scrollview now captures touches. Thanks! |
@SudoPlz we tried the same but it doesn't work for Samsung S9 and S10s. Let us know if your seeing different results. |
I've managed to get this working with a mixture of NativeViewGestureHandler (more info here: #492) and not directly nesting gesture handlers (more info here: #71). My eventual structure (with stuff removed) to nest a ScrollView with nested FlatLists inside a PanGestureHandler was:
Hope this helps! |
@SudoPlz Do you have a working example of this nested scrollview that you could share with us? |
In my case too... |
This can be solved if TypeScript definitions says this is posible but in practice is not supported |
I'm gonna leave this here, it might help some one...
|
@a-eid Works !!!!! |
#1034 ? |
I think this can be closed now. |
@levibuzolic did an excellent job explaining the current capabilities. If you can think of a better API we can implement to achieve that, please create a new issue. |
I can recommend using the 'no-restricted-imports': [1, {
paths: [
{
importNames: ['DrawerLayoutAndroid', 'FlatList', 'ScrollView', 'Switch', 'TextInput'],
message: 'Please use `DrawerLayoutAndroid`, `FlatList`, `ScrollView`, `Switch`, `TextInput` from `react-native-gesture-handler` instead',
name: 'react-native'
}
]
}] |
The best implementation I've seen to date is from https://github.com/gorhom/react-native-bottom-sheet |
I also considered a way to implement it by using Manual Gestures (https://docs.swmansion.com/react-native-gesture-handler/docs/manual-gestures/manual-gestures) |
It might helps to anyone who stuck in swipe gesture within a scrollview :) Reference to @origamih solution :
Here is my hook (useSwipe.tsx) import { Dimensions } from 'react-native'; const useSwipe = (onSwipeRight?: any, rangeOffset = 4) => {
}; export default useSwipe; |
@a-eid This pretty much works however, when I click and drag something, it doesn't cling directly to my finger IF i go purely vertically up or down. They have to go a bit sideways to get the object to move. I'm using longPress to first make the object movable. Do you know any hack ways to basically overcome the activeOffset as soon as the object is "picked up"? |
If you would like the PanGestureHandler to takes control only when the import React, { useRef } from 'react';
import {
ScrollView,
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
import { ScrollViewProps, View } from 'react-native';
const SCROLL_VIEW_HEIGHT = 300;
const NestedScrollView = () => {
const scrollView_ref = useRef<ScrollView>(null);
const gesture_ref = useRef<PanGestureHandler>(null);
const scrollOffset = useSharedValue(0);
const marginTop = useSharedValue(0);
const scrollViewContentHeight = useSharedValue(0);
const gestureHandler = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
{ start: number; prev: number }
>(
{
onActive(_, e) {
const isScrollingDownwards = e.start < _.y;
const isDownGestureActive =
isScrollingDownwards && scrollOffset.value === 0;
const isUpGestureActive =
!isScrollingDownwards &&
Math.round(scrollOffset.value) ===
Math.round(scrollViewContentHeight.value);
if (isDownGestureActive || isUpGestureActive) {
marginTop.value += _.translationY - e.prev || 0;
} else {
e.prev = _.translationY;
}
e.start = _.y;
},
onEnd(_, e) {
e.prev = 0;
},
},
[scrollOffset.value]
);
const scrollHandler: ScrollViewProps['onScroll'] = ({ nativeEvent }) => {
scrollOffset.value = Math.round(nativeEvent.contentOffset.y);
};
const setContentSize: ScrollViewProps['onContentSizeChange'] = (_, h) => {
scrollViewContentHeight.value = h - SCROLL_VIEW_HEIGHT;
};
const containerAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: marginTop.value }],
}));
return (
<PanGestureHandler
onGestureEvent={gestureHandler}
ref={gesture_ref}
simultaneousHandlers={scrollView_ref}>
<Animated.View
style={[
containerAnimatedStyle,
{ height: SCROLL_VIEW_HEIGHT, backgroundColor: 'red' },
]}>
<ScrollView
bounces={false}
onScroll={scrollHandler}
onContentSizeChange={setContentSize}
ref={scrollView_ref}
simultaneousHandlers={gesture_ref}>
{/* scrollview Content */}
</ScrollView>
</Animated.View>
</PanGestureHandler>
);
};
export default NestedScrollView; check out this snack. |
I've finally figured this out with new pan gesture handler. const gesture = Gesture.Pan()
.onBegin(() => {
// touching screen
moving.value = true;
})
.onUpdate((e) => {
// move sheet if top or scrollview or is closed state
if (scrollY.value === 0 || prevY.value === closed) {
transY.value = prevY.value + e.translationY - movedY.value;
// capture movement, but don't move sheet
} else {
movedY.value = e.translationY;
}
// simulate scroll if user continues touching screen
if (prevY.value !== open && transY.value < open) {
runOnJS(scrollRef?.current.scrollTo)({ y: -transY.value + open, animated: false });
}
})
.onEnd((e) => {
// close sheet if velocity or travel is good
if ((e.velocityY > 500 || e.translationY > 100) && scrollY.value < 1) {
transY.value = withTiming(closed, { duration: 200 });
prevY.value = closed;
// else open sheet on reverse
} else if (e.velocityY < -500 || e.translationY < -100) {
transY.value = withTiming(open, { duration: 200 });
prevY.value = open;
// don't do anything
} else {
transY.value = withTiming(prevY.value, { duration: 200 });
}
})
.onFinalize((e) => {
// stopped touching screen
moving.value = false;
movedY.value = 0;
})
.simultaneousWithExternalGesture(scrollRef) I've created a snack to show how it works (iOS). |
Thank you. That's exactly what I was looking for. I add a gesture to the shopify flash list. I was trying to make it deleted when dragging in x minus. I was looking for how to do it for an hour :)
It works very well |
Fixed issue of scrolling the single image. Before this fix scrollView inside the panGEsturehandler was not receiving the touches and could not scroll. followed below to fix: software-mansion/react-native-gesture-handler#420 (comment)
This worked for me. Thanks! |
Fixed issue of scrolling the single image. Before this fix scrollView inside the panGEsturehandler was not receiving the touches and could not scroll. followed below to fix: software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
…animated.ScrollView + createNativeWrapper remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction using animated scrollEnabled prop based on software-mansion/react-native-gesture-handler#420 (comment)
remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction based on software-mansion/react-native-gesture-handler#420 (comment) and approach from rngh example https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/new_api/bottom_sheet/index.tsx#L87
remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction based on software-mansion/react-native-gesture-handler#420 (comment) and approach from rngh example https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/new_api/bottom_sheet/index.tsx#L87
remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction based on software-mansion/react-native-gesture-handler#420 (comment) and approach from rngh example https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/new_api/bottom_sheet/index.tsx#L87
remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction based on software-mansion/react-native-gesture-handler#420 (comment) and approach from rngh example https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/new_api/bottom_sheet/index.tsx#L87 scrollEnabled inspired by react-native-swipe-modal https://github.com/birdwingo/react-native-swipe-modal
remove preserveScrollMomentum + scrollBuffer + lockableScrollableContentOffsetY props we're using `preserveScrollMomentum` in the media picker, but i'm removing it because i feel the behavior is actually detrimental to the UX, and to simplify the refactor to scrollEnabled in the next commit. `lockableScrollableContentOffsetY` is removed since it won't be necessary after the scrollEnabled refactor `scrollBuffer` was no longer used. removed custom gesture/scroll handling hooks after refactoring to use scrollEnabled, we can add other props to add custom behaviors if needed. refactored scroll/pan gesture interaction based on software-mansion/react-native-gesture-handler#420 (comment) and approach from rngh example https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/new_api/bottom_sheet/index.tsx#L87 scrollEnabled inspired by react-native-swipe-modal https://github.com/birdwingo/react-native-swipe-modal
With new Reanimated API roughly this is my solution: // Replace with your own snap points
const snapPoints = [-400, 0];
function AwesomeContainer({ children }) {
const scrollViewAnimatedRef = useAnimatedRef<Animated.ScrollView>();
const translationY = useSharedValue(0);
const scrollOffsetY = useSharedValue(0);
const handleContentScroll = useAnimatedScrollHandler(({ contentOffset }) => {
scrollOffsetY.value = contentOffset.y;
}, []);
const gesture = useMemo(() => {
const initialScrollOffsetY = makeMutable(0);
return Gesture.Pan()
.simultaneousWithExternalGesture(scrollViewAnimatedRef)
.onStart(() => {
initialScrollOffsetY.value = scrollOffsetY.value;
})
.onUpdate(event => {
translationY.value = clamp(
event.translationY - initialScrollOffsetY.value,
snapPoints[0],
snapPoints[1]
);
})
.onEnd(event => {
if (translationY.value === snapPoints[0]) {
return;
}
translationY.value = withTiming(snapTo(translationY.value, event.velocityY, snapPoints));
});
}, [scrollOffsetY, scrollViewAnimatedRef, translationY]);
const positionAnimatedStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: translationY.value }]
};
}, []);
return (
<GestureDetector gesture={gesture}>
<Animated.View style={positionAnimatedStyle}>
<Animated.ScrollView
ref={scrollViewAnimatedRef}
bounces={false}
showsVerticalScrollIndicator={false}
onScroll={handleContentScroll}
scrollEventThrottle={16}
>
{children}
</Animated.ScrollView>
</Animated.View>
</GestureDetector>
);
} |
I'd like to have a ScrollView within a PanGestureHandler where the ScrollView is scrollable but whenever scrollY <= 0 (and its not a momentum scroll) the PanGestureHandler should handle the gesture.
It's kind of hard to explain, so here is a video: https://streamable.com/hplw6
How could this be achieved?
The text was updated successfully, but these errors were encountered: