Android: `<PanGestureHandler>` not working in `<Modal>`
See original GitHub issueDescription
Hi!
I’ve read the docs about 20 times now, and I still can’t seem to get my <PanGestureHandler>
working on Android.
The View is in a Modal, but that Modal is wrapped with the gesture handler root HOC. I’ve also updated my MainActivity. Other PanGestureHandlers work, so it must be something with my code specifically, but I can’t pinpoint it.
Code
import React, { useMemo, useEffect, useCallback, useState } from 'react';
import { View, StyleSheet, LayoutChangeEvent, LayoutRectangle, ViewStyle, StyleProp, TextInput, Platform } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import { usePanGestureHandler } from 'react-native-redash';
import Reanimated, {
useValue,
useCode,
cond,
eq,
set,
Extrapolate,
call,
interpolate,
concat,
round,
divide,
neq,
and,
greaterThan,
sub,
} from 'react-native-reanimated';
const THUMB_DIAMETER = Platform.OS === 'ios' ? 30 : 20;
const THUMB_RADIUS = THUMB_DIAMETER / 2;
const RAIL_HEIGHT = 3;
const GESTURE_HANDLER_ACTIVE_OFFSET_X = [-3, 3];
const GESTURE_HANDLER_FAIL_OFFSET_Y = [-5, 5];
const ReanimatedTextInput = Reanimated.createAnimatedComponent(TextInput);
export interface HighlightedSliderProps {
style?: StyleProp<ViewStyle>;
minValue: number;
maxValue: number;
value: number;
onValueChange: (value: number) => void;
colors: SliderColors;
showLabel?: boolean;
onReanimatedValueNodeChange?: (reanimatedValueNode: Reanimated.Node<number>) => void;
textPrefix?: string;
textSuffix?: string;
}
export interface SliderColors {
thumbColor: string;
activeRailColor: string;
inactiveRailColor: string;
}
// TODO: Set reanimated values thumbX, thumbValue and offsetX if prop "value" changes.
export default function HighlightedSlider(props: HighlightedSliderProps): JSX.Element {
const { style, minValue, maxValue, value, onValueChange, colors, textPrefix, textSuffix, onReanimatedValueNodeChange, showLabel } = props;
const { gestureHandler, state, position } = usePanGestureHandler();
const [layout, setLayout] = useState<LayoutRectangle>({ height: 0, width: 0, x: 0, y: 0 });
const step = useMemo(() => (layout.width > 0 ? layout.width / (maxValue - minValue) : 1), [layout.width, maxValue, minValue]);
const lower = useMemo(() => Math.max(minValue * step, 0), [minValue, step]);
const upper = useMemo(() => Math.max(maxValue * step - THUMB_DIAMETER, 0), [maxValue, step]);
const upperWidth = useMemo(() => Math.max(layout.width - THUMB_DIAMETER, 0), [layout.width]);
//#region Animations
const thumbX = useValue(value);
const thumbValue = useMemo(() => {
return interpolate(thumbX, {
inputRange: [lower, upper],
outputRange: [minValue, maxValue],
extrapolate: Extrapolate.CLAMP,
});
}, [lower, maxValue, minValue, thumbX, upper]);
const thumbValueString = useMemo(
() =>
showLabel
? concat(
textPrefix ?? '',
round(
interpolate(thumbX, {
inputRange: [lower, upper],
outputRange: [minValue, maxValue],
extrapolate: Extrapolate.CLAMP,
}),
),
textSuffix ?? '',
)
: undefined,
[showLabel, lower, maxValue, minValue, textPrefix, textSuffix, thumbX, upper],
);
useCode(
() => [
cond(greaterThan(layout.width, 1), [
cond(and(neq(state, State.ACTIVE), neq(state, State.END), neq(round(thumbValue), round(value))), [
set(
thumbX,
interpolate(value, {
inputRange: [minValue, maxValue],
outputRange: [lower, upper],
extrapolate: Extrapolate.CLAMP,
}),
),
]),
cond(eq(state, State.ACTIVE), [
set(
thumbX,
interpolate(sub(position.x, THUMB_RADIUS), {
inputRange: [0, upperWidth],
outputRange: [lower, upper],
extrapolate: Extrapolate.CLAMP,
}),
),
]),
cond(eq(state, State.END), [call([thumbValue], ([_thumbValue]) => onValueChange(_thumbValue))]),
]),
],
[layout.width, lower, maxValue, minValue, onValueChange, position.x, state, thumbValue, thumbX, upper, upperWidth, value],
);
const activeRailScaleX = useMemo(() => {
return Reanimated.interpolate(thumbX, {
inputRange: [lower, upper],
outputRange: [0, layout.width],
});
}, [thumbX, lower, upper, layout.width]);
const activeRailTranslateX = useMemo(() => {
return Reanimated.interpolate(thumbX, {
inputRange: [lower, upper],
outputRange: [0, divide(layout.width, 2, activeRailScaleX)],
});
}, [activeRailScaleX, layout.width, lower, thumbX, upper]);
//#endregion
//#region Memos
const inactiveRailStyle = useMemo(() => [styles.inactiveRail, { backgroundColor: colors.inactiveRailColor }], [colors.inactiveRailColor]);
const activeRailStyle = useMemo(
() => [
styles.activeRail,
{ backgroundColor: colors.activeRailColor, transform: [{ scaleX: activeRailScaleX }, { translateX: activeRailTranslateX }] },
],
[activeRailScaleX, activeRailTranslateX, colors.activeRailColor],
);
const thumbStyle = useMemo(
() => [styles.thumb, { shadowColor: colors.thumbColor, backgroundColor: colors.thumbColor, transform: [{ translateX: thumbX }] }],
[colors.thumbColor, thumbX],
);
const viewStyle = useMemo(() => [styles.slider, style], [style]);
//#endregion
//#region Callbacks
const onViewLayout = useCallback(
({ nativeEvent }: LayoutChangeEvent) => {
if (JSON.stringify(layout) !== JSON.stringify(nativeEvent.layout)) setLayout(nativeEvent.layout);
},
[layout],
);
//#endregion
//#region Effects
useEffect(() => {
if (onReanimatedValueNodeChange != null) onReanimatedValueNodeChange(thumbValue);
}, [onReanimatedValueNodeChange, thumbValue]);
//#endregion
return (
<PanGestureHandler {...gestureHandler} activeOffsetX={GESTURE_HANDLER_ACTIVE_OFFSET_X} failOffsetY={GESTURE_HANDLER_FAIL_OFFSET_Y}>
<Reanimated.View style={viewStyle} onLayout={onViewLayout}>
{showLabel && <ReanimatedTextInput style={styles.text} text={thumbValueString} editable={false} underlineColorAndroid="transparent" />}
<View style={inactiveRailStyle}>
<Reanimated.View style={activeRailStyle} />
<Reanimated.View style={thumbStyle} />
</View>
</Reanimated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
slider: {
width: '100%',
alignItems: 'center',
},
text: {
paddingVertical: 0,
fontSize: 12,
marginBottom: THUMB_RADIUS + 7,
fontWeight: 'bold',
color: 'black',
},
thumb: {
height: THUMB_DIAMETER,
width: THUMB_DIAMETER,
borderRadius: THUMB_RADIUS,
top: -THUMB_RADIUS + 1,
position: 'absolute',
shadowOffset: {
height: 1,
width: 0,
},
shadowOpacity: 0.7,
shadowRadius: 2,
},
inactiveRail: {
width: '100%',
height: RAIL_HEIGHT,
borderRadius: RAIL_HEIGHT,
},
activeRail: {
width: 1,
height: RAIL_HEIGHT,
borderRadius: RAIL_HEIGHT,
position: 'absolute',
left: 0,
},
});
Package versions
- React: 16.13.1
- React Native: 0.63.2
- React Native Gesture Handler: 1.7.0
Issue Analytics
- State:
- Created 3 years ago
- Reactions:5
- Comments:23 (4 by maintainers)
Top Results From Across the Web
react native gesture handler is not working with modal in ...
I am having an issue in using PanGestureHandler from react-native-gesture-handler with Modal . This is perfectly working in iOS but not in ......
Read more >react native gesture handler is not working with modal in ...
Usage with modals on Android# On Android RNGH does not work by default because modals are not located under React Native Root view...
Read more >Troubleshooting | React Native Bottom Sheet - GitHub Pages
Pressables / Touchables are not working on Android. Due to wrapping the content and handle with TapGestureHandler & PanGestureHandler , any gesture ...
Read more >Building a BottomSheet from scratch in React Native - YouTube
In this tutorial we'll learn how to create from scratch a BottomSheet component by using the reanimated and the react-native-gesture-handler ...
Read more >Introduction | React Native Gesture Handler - Software Mansion
We recommend this talk by Krzysztof Magiera in which he explains issues with the ... On Android RNGH does not work by default...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
it worked for me when i did it like this
Adding
activeOffsetX={[0, 0]}
toPanGestureHandler
fixed it for me. 😃