question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Android: `<PanGestureHandler>` not working in `<Modal>`

See original GitHub issue

Description

Hi! I’ve read the docs about 20 times now, and I still can’t seem to get my <PanGestureHandler> working on Android.

o

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:closed
  • Created 3 years ago
  • Reactions:5
  • Comments:23 (4 by maintainers)

github_iconTop GitHub Comments

19reactions
shoki61commented, May 18, 2022
<Modal transparent> 
   <GestureHandlerRootView style={{flex:1}}> 
      <PanGestureHandler>
         <Animated.View>
            {/* Your components */}
         </Animated.View>
      </PanGestureHandler>
   </GestureHandlerRootView>
</Modal>

it worked for me when i did it like this

17reactions
raugustinascommented, Dec 31, 2020

Adding activeOffsetX={[0, 0]} to PanGestureHandler fixed it for me. 😃

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found