react-native-gesture-handler: Flashlist is not scrolling
Description
THE ISSUE IS ONLY ON ANDROID
Flashlist is not scrolling when I wrap the List component to BottomSheet component, However when I comment out the BottomSheet component it works fine. So the issue is related to the BottomSheet component.
The List component itself is been rendered inside a flashlist, This is how I’m using it
<GestureHandlerRootView style={{ height: '100%', width: '100%', position: 'absolute' }}>
<View style={{ position: 'absolute', height: '100%', width: '100%', }}>
<BottomSheet ref={ref}>
<List />
</BottomSheet>
</View>
</GestureHandlerRootView>
BottomSheet component:
import { Dimensions, StyleSheet, Text, View } from 'react-native';
import React, { useCallback, useEffect, useImperativeHandle } from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from 'react-native-reanimated';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;
type BottomSheetProps = {
children?: React.ReactNode;
};
export type BottomSheetRefProps = {
scrollTo: (destination: number) => void;
isActive: () => boolean;
};
const BottomSheet = React.forwardRef<BottomSheetRefProps, BottomSheetProps>(
({ children }, ref) => {
const translateY = useSharedValue(0);
const active = useSharedValue(false);
const scrollTo = useCallback((destination: number) => {
'worklet';
active.value = destination !== 0;
translateY.value = withSpring(destination, { damping: 50 });
}, []);
const isActive = useCallback(() => {
return active.value;
}, []);
useImperativeHandle(ref, () => ({ scrollTo, isActive }), [
scrollTo,
isActive,
]);
const context = useSharedValue({ y: 0 });
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
translateY.value = event.translationY + context.value.y;
translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y);
})
.onEnd(() => {
if (translateY.value > -SCREEN_HEIGHT / 3) {
scrollTo(0);
} else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y);
}
});
const rBottomSheetStyle = useAnimatedStyle(() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 50, MAX_TRANSLATE_Y],
[25, 5],
Extrapolate.CLAMP
);
return {
borderRadius,
transform: [{ translateY: translateY.value }],
};
});
return (
<View>
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.bottomSheetContainer, rBottomSheetStyle]}>
<View style={styles.line} />
{children}
</Animated.View>
</GestureDetector>
</View>
);
}
);
const styles = StyleSheet.create({
bottomSheetContainer: {
height: SCREEN_HEIGHT,
width: '100%',
backgroundColor: 'black',
position: 'absolute',
top: SCREEN_HEIGHT,
borderRadius: 25,
},
line: {
width: 75,
height: 4,
backgroundColor: 'grey',
alignSelf: 'center',
marginVertical: 15,
borderRadius: 2,
},
});
export default BottomSheet;
List Component:
import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
import { FlashList } from '@shopify/flash-list'
const List = () => {
const data = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 1' },
{ id: '243', title: 'Item 2' },
{ id: '14dff3', title: 'Item 1' },
{ id: '2564ewe36', title: 'Item 2' },
{ id: '24ere3', title: 'Item 2' },
{ id: '14drereff3', title: 'Item 1' },
{ id: '25erer6436', title: 'Item 2' },
{ id: '24rereer3', title: 'Item 2' },
{ id: '14dferrf3', title: 'Item 1' },
{ id: '2564er36', title: 'Item 2' }
];
return (
<View style={styles.container}>
<View style={{ flex: 1, maxHeight: '80%', }}>
<FlashList
nestedScrollEnabled={true}
data={data}
estimatedItemSize={100}
contentContainerStyle={{ padding: 37 }}
renderItem={({ item }) => (
<>
<Text style={styles.text}>
React Native
React Native
React Native
React Native
React Native
React Native
React Native
React Native
React Native
</Text>
</>
)}
keyExtractor={(item) => item.id}
/>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'black',
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: '100%',
width: '100%',
position: 'absolute'
},
text: {
color: 'gray',
fontWeight: '500',
fontSize: 14,
left: 15,
textAlign: 'left',
width: '90%',
marginBottom: 20
},
})
export default List
Steps to reproduce
Take your mouse in the list and just Drag down the list it won’t work on Andriod but works fine on iOS.
Snack or a link to a repository
null
Gesture Handler version
2.12.1
React Native version
0.72.4
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
Fabric (New Architecture)
Build type
Debug mode
Device
Android emulator
Device model
Google Pixel 4
Acknowledgements
Yes
About this issue
- Original URL
- State: closed
- Created 10 months ago
- Comments: 25 (8 by maintainers)
Yep, I did close this, But Thanks for the help Michał 😃
Okay, I’ve looked at it and noticed few things:
<GestureHandlerRootView>
at top level of your application. Also you can treat it as standard<View>
and for example apply styles to it. So instead ofin your
Home
component, do:so that gestures will work properly and you won’t have to use another
<GestureHandlerRootView>
BottomModal
should have<GestureDetector>
instead of<GestureHandlerRootView>
onPress
is not working because you use many<View>
components withposition: 'absolute'; width: 100%; height:100%;
. If you inspect that, they actually lie on top of your button hence it doesn’t work.I’ve managed to fix issues mentioned above and it seems to work fine with
activeOffsetY
. The thing is, if you setactiveOffsetY
,pan
won’t activate unless you exceed its activation threshold. In the meantimeFlashList
has its opportunity to activate, therefore cancellingpan
- this is why scrollingFlashList
works and BottomSheet is not moving.To summarize, I’ve fixed some problems with your repro and by adding
activeOffsetY
GestureHandler seems to work as expected. I haven’t seen it affecting the animation either. Note that you can manipulate withactiveOffsetY
and increase its value if the one that you’re using right now doesn’t satisfy you.Here is the corrected code: