Skip to content
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

Closed
dutzi opened this issue Jan 24, 2019 · 41 comments
Closed

ScrollView nested within a PanGestureHandler #420

dutzi opened this issue Jan 24, 2019 · 41 comments
Assignees

Comments

@dutzi
Copy link

dutzi commented Jan 24, 2019

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?

@origamih
Copy link

origamih commented Jan 25, 2019

This is my implementation:

  • Having an enable state, set it to false when ScrollView offset <= 0
  • Make ScrollView wait for PanGesture, but this leads to the problem that when PanGesture is being dragged up (change to failed state) ScrollView still waits for it. So I have ScrollView wait for another one when enable = false, (for itself in this case)
    The downside is it needs to listen to ScrollView onScroll event to track scroll offset
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>
    );
  }
}

@hssrrw
Copy link

hssrrw commented Jan 25, 2019

I am trying to achieve the same effect as in the video above. Controlling enabled prop is too slow, because requires component update. It would be better if the feature would be implemented on the native side using PanGestureHandler.

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.

@pribeh
Copy link

pribeh commented Mar 2, 2019

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.

@pribeh
Copy link

pribeh commented Mar 5, 2019

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:

  render() {
    return (
      <Animated.View style={[
        this.props.style,
        {
          transform: [
            {
              translateX: this.props.verticalOnly ? 0 : this._transX,
              translateY: this.props.horizontalOnly ? 0 : this._transY,
            },
          ],
        },
      ]}>
        <PanGestureHandler
          maxPointers={1}
          enabled={this.props.dragEnabled}
          onGestureEvent={this._onGestureEvent}
          onHandlerStateChange={this._onGestureEvent}
        >
          <Animated.View>
            {this.props.header}
          </Animated.View>
        </PanGestureHandler>
        <Animated.View>
          {this.props.body}
        </Animated.View>
      </Animated.View>
    );
  }

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:

<Interactable.View
    header={this.renderHeader}
    body={this.renderBody}
    verticalOnly={true}
    style={[ styles.interactableContainer ]}
    snapPoints={[ { y:  this.topSnap }, { y: this.bottomSnap }, ]}
    boundaries={{ top: this.topSnap }}
    initialPosition={{ y: this.bottomSnap }}
    animatedValueY={this._deltaY} />

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.

@Noitidart
Copy link

I'm also stuck on Android. I have a PanGestureHandler which has a descendent which is a ScrollView, but I cannot scroll in this ScrollView, the pan always is taking precedence. I even created a ref and passed it deeply down and then did:

<PanGestureHandler waitFor={ref}>
    ....
    <ScrollView ref={ref} />
    ....
</PanGestureHandler>

I'm on Android, using RN 0.59.5.

@jmacindoe
Copy link

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.

@PierreCapo
Copy link

am trying to achieve the same effect as in the video above. Controlling enabled prop is too slow, because requires component update. It would be better if the feature would be implemented on the native side using PanGestureHandler.

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.

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 use-memo-one or move to class components to memoize all the animated values.

@pkf1994
Copy link

pkf1994 commented Oct 17, 2019

I'm also stuck on Android. I have a PanGestureHandler which has a descendent which is a ScrollView, but I cannot scroll in this ScrollView, the pan always is taking precedence. I even created a ref and passed it deeply down and then did:

<PanGestureHandler waitFor={ref}>
    ....
    <ScrollView ref={ref} />
    ....
</PanGestureHandler>

I'm on Android, using RN 0.59.5.

Did you fix it ? same issue on android

@Fantasim
Copy link

Any update about this case ?

@pribeh
Copy link

pribeh commented Nov 30, 2019

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.

@mayconmesquita
Copy link

@pribeh i got similar results.

@SudoPlz
Copy link

SudoPlz commented Feb 28, 2020

All I did was change my import from
import { ScrollView } from 'react-native';
to
import { ScrollView } from 'react-native-gesture-handler';

and it worked.

Edit:

For people experiencing issues on Android, make sure the hitbox of your ScrollView content is where you expect it to be, explaining:

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.
To troubleshoot, make sure you pass overflow: 'hidden' on all the child views of your ScrollView and check wether after you do that, a view that should respond to the gesture you're invoking becomes hidden.

In our case we had a View inside a LongPressGestureHandler that when passed overflow: 'hidden' became invisible. All we had to do was re-arrange our view structure, pass flexGrow: 1 to that view, and pretty much make it visible even with overflow: 'hidden' turned on.
As soon as we did that the hitbox was the same as the visible area of that view, and gestures started working fine again.

@Bigood
Copy link

Bigood commented Mar 6, 2020

Can confirm that what @SudoPlz suggested work as intended, my nested scrollview now captures touches. Thanks!

@pribeh
Copy link

pribeh commented Mar 7, 2020

@SudoPlz we tried the same but it doesn't work for Samsung S9 and S10s. Let us know if your seeing different results.

@JEGardner
Copy link

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:

<PanGestureHandler
  ref={this.panGestureHandlerRef}
  simultaneousHandlers={this.nativeHandlerRef}
>
    <Animated.View style={{ flex: 1 }}>
        <NativeViewGestureHandler
           ref={this.nativeHandlerRef}
           simultaneousHandlers={this.panGestureHandlerRef}
        >
            <Animated.ScrollView
              ref={this.wrapperRef}
              horizontal
            >
                // Array of flatlists also here
            </Animated.ScrollView>
        </NativeViewGestureHandler>
    </Animated.View>
</PanGestureHandler>

Hope this helps!

@SudoPlz
Copy link

SudoPlz commented Mar 26, 2020

@pribeh check my edited post, it may help you.

@RBrNx
Copy link

RBrNx commented Mar 28, 2020

@SudoPlz Do you have a working example of this nested scrollview that you could share with us?

@zabojad
Copy link

zabojad commented May 5, 2020

All I did was change my import from
import { ScrollView } from 'react-native';
to
import { ScrollView } from 'react-native-gesture-handler';

and it worked.

In my case too...

@neiker
Copy link

neiker commented Jul 19, 2020

This can be solved if ScrollView component gives access to pan gesture by implementing onGestureEvent prop.

TypeScript definitions says this is posible but in practice is not supported

@a-eid
Copy link

a-eid commented Aug 5, 2020

I'm gonna leave this here, it might help some one...

<PanGestureHandler 
  activeOffsetX={[-10, 10]}
  /* or */
  activeOffsetY={[-10, 10]}
>

@zeabdelkhalek
Copy link

@a-eid Works !!!!!

@ShaMan123
Copy link
Contributor

#1034 ?

@jakub-gonet jakub-gonet changed the title [Feature Request] ScrollView nested within a PanGestureHandler ScrollView nested within a PanGestureHandler Nov 26, 2020
@jakub-gonet
Copy link
Member

I think this can be closed now.

@jakub-gonet
Copy link
Member

@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.

@LinusU
Copy link

LinusU commented Sep 29, 2021

I can recommend using the no-restricted-imports ESLint rule to avoid importing the wrong ScrollView/FlatList:

'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'
    }
  ]
}]

@han-steve
Copy link

The best implementation I've seen to date is from https://github.com/gorhom/react-native-bottom-sheet
It basically uses 2 simultaneous handlers for the scrollview and the pan gesture, while continuously resetting the scrollview position to lock it in place.
I drew this diagram to help me understand it
I drew this diagram to help me understand it

@han-steve
Copy link

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)
Basically if I can fail my scroll down gesture conditioned on a reanimated value, and have the scrollview activate when the bottomsheet pan gesture fails (by using the wait for), I can implement this behavior. However, I couldn't get the manual gesture to work (possibly due to bugs in the library), so I couldn't achieve it.

@WintKhetSan
Copy link

WintKhetSan commented Sep 2, 2022

It might helps to anyone who stuck in swipe gesture within a scrollview :)

Reference to @origamih solution :

import { ScrollView, PanGestureHandler } from 'react-native-gesture-handler';
import useSwipe from 'hooks/useSwipe';

const ref = React.createRef();
const scrollRef = useRef(null);
const [enable, setEnable] = useState(true);

// swipe feature
const { onTouchStart, onTouchEnd } = useSwipe(onSwipeRight, 6);

function onSwipeRight() {
navigation.goBack();
}

const onScroll = (event: any) => {
if (event.nativeEvent.contentOffset.y <= 0 && !enable) {
setEnable(true);
}
if (event.nativeEvent.contentOffset.y > 0 && enable) {
setEnable(false);
}
};

const onGestureEnd = (event: any) => {
if (!enable) return;
// handle Gesture Event here
onSwipeRight();
onTouchEnd(event);
};

<ScrollView ref={scrollRef} onScroll={onScroll} waitFor={enable ? ref : scrollRef} scrollEventThrottle={40}>

<PanGestureHandler enabled={enable} ref={ref} activeOffsetX={5} failOffsetX={-15}

       onBegan={onTouchStart} onGestureEvent={onGestureEnd}>	
      <View>
           // your work is here
      </View>
</PanGestureHandler>

Here is my hook (useSwipe.tsx)

import { Dimensions } from 'react-native';
const windowWidth = Dimensions.get('window').width;

const useSwipe = (onSwipeRight?: any, rangeOffset = 4) => {
let firstTouch = 0;

// set user touch start position
function onTouchStart(e: any) {
	firstTouch = e.nativeEvent.pageX;
}

// when touch ends check for swipe directions
function onTouchEnd(e: any) {
	// get touch position and screen size
	const positionX = e.nativeEvent.pageX;
	const range = windowWidth / rangeOffset;

	// check if position is growing positively and has reached specified range
	if (positionX - firstTouch > range) {
		onSwipeRight && onSwipeRight();
	}

	// check if position is growing negatively and has reached specified range
	// if (firstTouch - positionX > range) {
	// 	onSwipeLeft && onSwipeLeft();
	// }
}

return { onTouchStart, onTouchEnd };

};

export default useSwipe;

@ucheNkadiCode
Copy link

I'm gonna leave this here, it might help some one...

<PanGestureHandler 
  activeOffsetX={[-10, 10]}
  /* or */
  activeOffsetY={[-10, 10]}
>

@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"?

@uwemneku
Copy link

uwemneku commented Dec 14, 2022

If you would like the PanGestureHandler to takes control only when the scrollOffset of the Scrollview is 0 or at the end, you could do so using shared values from reanimted 2 and onContentSizeChange. You can keep track of the scrollOffset and only take actions when scrollOffset is 0 or at the end of the scrollView. In the sample below, the Animated.View is only moved when this conditions are satisfied

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.
Note that it works better on android

@himanchau
Copy link

himanchau commented Dec 18, 2022

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).
https://snack.expo.dev/@himanshu266/bottom-sheet-scrollview

@okaybeydanol
Copy link

I'm gonna leave this here, it might help some one...

<PanGestureHandler 
  activeOffsetX={[-10, 10]}
  /* or */
  activeOffsetY={[-10, 10]}
>

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 :)

<PanGestureHandler onGestureEvent={onGestureEvent} activeOffsetX={[-30, 0]}>

It works very well

SuperStar0106 added a commit to SuperStar0106/delightful-photos that referenced this issue Jul 1, 2023
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)
@netojose
Copy link

All I did was change my import from import { ScrollView } from 'react-native'; to import { ScrollView } from 'react-native-gesture-handler';

and it worked.

Edit:

For people experiencing issues on Android, make sure the hitbox of your ScrollView content is where you expect it to be, explaining:

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. To troubleshoot, make sure you pass overflow: 'hidden' on all the child views of your ScrollView and check wether after you do that, a view that should respond to the gesture you're invoking becomes hidden.

In our case we had a View inside a LongPressGestureHandler that when passed overflow: 'hidden' became invisible. All we had to do was re-arrange our view structure, pass flexGrow: 1 to that view, and pretty much make it visible even with overflow: 'hidden' turned on. As soon as we did that the hitbox was the same as the visible area of that view, and gestures started working fine again.

This worked for me. Thanks!

kstar0707 added a commit to kstar0707/fx-fotos that referenced this issue Nov 29, 2023
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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
…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)
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
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
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
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
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
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
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
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
yayvery added a commit to discord/react-native-bottom-sheet that referenced this issue Mar 18, 2024
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
@neiker
Copy link

neiker commented Mar 28, 2024

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>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests