memoirの不具合フィードバックで以下の報告があったので修正
- 上下スクロールしていると、意図せずスワイプイベントが発火して日付移動してしまう
- タッチインベントを設定している箇所でスワイプイベントが発火しない
PR
実装
元々はreact-native-swipe-gesturesを使用してスワイプイベントを実装していたが、2年前から更新されてなかったので移行えを検討
検索したところ、react-native-gesture-handlerで可能なことが分かったので実装
react-native-gesture-handlerの PanGestureHandlerで実装
コードは以下のようになった
■ src/components/organisms/Home/GestureRecognizerWrap.tsx
import React, { memo, useCallback } from 'react'; import { StyleSheet, ViewStyle } from 'react-native'; import View from 'components/atoms/View'; import dayjs from 'lib/dayjs'; import { ConnectedType } from 'components/pages/Home/Connected'; import { GestureHandlerRootView, PanGestureHandler, ScrollView, HandlerStateChangeEvent, } from 'react-native-gesture-handler'; type Props = { date: string; children: React.ReactNode; } & Pick<ConnectedType, 'onChangeDate' | 'items'>; const velocityThreshold = 0.3; const directionalOffsetThreshold = 80; const isValidSwipe = (velocity: number, directionalOffset: number) => { return ( Math.abs(velocity) > velocityThreshold && Math.abs(directionalOffset) < directionalOffsetThreshold ); }; const GestureRecognizerWrap: React.FC<Props> = (props) => { const onSwipeLeft = useCallback(() => { props.onChangeDate(dayjs(props.date).add(1, 'day').format('YYYY-MM-DD')); }, [props]); const onSwipeRight = useCallback(() => { props.onChangeDate(dayjs(props.date).add(-1, 'day').format('YYYY-MM-DD')); }, [props]); const onPanGestureEvent = useCallback( (event: HandlerStateChangeEvent<any>) => { const { nativeEvent } = event; if (Math.abs(nativeEvent.velocityY) > 300) { return; } if (!isValidSwipe(nativeEvent.velocityX, nativeEvent.translationX)) { return; } if (nativeEvent.velocityX > 0) { onSwipeRight(); } else { onSwipeLeft(); } }, [onSwipeRight, onSwipeLeft] ); const style: ViewStyle[] = [styles.inner]; if (props.items.length > 3) { style.push({ paddingBottom: (props.items.length - 2) * 55 }); } return ( <View style={styles.root}> <GestureHandlerRootView> <PanGestureHandler onActivated={onPanGestureEvent}> <ScrollView removeClippedSubviews style={styles.scroll}> <View style={style}>{props.children}</View> </ScrollView> </PanGestureHandler> </GestureHandlerRootView> </View> ); }; export default memo<React.FC<Props>>(GestureRecognizerWrap); const styles = StyleSheet.create({ inner: { height: '100%', }, scroll: { height: '100%', }, root: { height: '100%', }, });
コードの説明をすると、スワイプのイベントが欲しいコンポーネントを以下でラップする
<GestureHandlerRootView> <PanGestureHandler onActivated={onPanGestureEvent}> .... </PanGestureHandler> </GestureHandlerRootView>
PanGestureHandlerのonActivatedでジェスチャーのイベントを取得できるので、その情報を元に右スワイプ、左スワイプを判定
const onPanGestureEvent = useCallback( (event: HandlerStateChangeEvent<any>) => { const { nativeEvent } = event; if (Math.abs(nativeEvent.velocityY) > 300) { return; } if (!isValidSwipe(nativeEvent.velocityX, nativeEvent.translationX)) { return; } if (nativeEvent.velocityX > 0) { onSwipeRight(); } else { onSwipeLeft(); } }, [onSwipeRight, onSwipeLeft] );
また、スワイプとスクロールが同時に発生しないようにreact-native-gesture-handlerのScrollViewを使用
import { GestureHandlerRootView, PanGestureHandler, ScrollView, // ← こちらを使用 HandlerStateChangeEvent, } from 'react-native-gesture-handler';
■ 参考 - https://github.com/software-mansion/react-native-gesture-handler/issues/420#issuecomment-592686502
これで、スクロールとタッチイベントとスワイプイベントが同時に存在する画面でも快適動作できるようになった。