Android で react-native-gesture handler が一部動かなくなって詰まっていたので忘備録。
tl;dr;
- Android にて、Modal Component の中でreact-native-gesture-handler を使うには、
gestureHandlerRootHOC
を Modal の中で使う必要がある。 - https://docs.swmansion.com/react-native-gesture-handler/docs/#usage-with-modals-on-android
import * as React from 'react'
import { Text, Modal } from 'react-native'
import { TouchableOpacity, gestureHandlerRootHOC } from 'react-native-gesture-handler'
const ModalChild = gestureHandlerRootHOC(()=>{
return (
<TouchableOpacity onPress={()=>{/* some event */}}>
<Text>Button with Gesture</Text>
</TouchableOpacity>
)
})
export const GestureInModal = () => {
return (
<Modal>
<ModalChild />
</Modal>
)
}
react-native-gesture-handler について。
React Native は基本的に画面の描画は JS 層で書いた React のコードをうまく変換して描画されます。(2020年9月現在)
どの様に描画されるかは JS のコードを読み込んで、それを ViewConfig という json に変換してそれぞれ iOS や Android のコードに変換すると言った処理が走ります。(これらの描画は、現在進行中の Fabric プロジェクトによって方針が変わって速度も改善されますが今の所は話を進めます。)
これは Animation や Gesture など、ユーザーの動きに合わせて画面を動かすのには適していなく、特に Android ではそのまま書くと大きなパフォーマンス減少の原因になりがちとなります。
react-native-gesture-handler はこの問題を解決すべく作成されたライブラリで、今では react-navigation など、多くの react-native の主要ライブラリで使用されています。
react-native-gesture-handler はスワイプやフリックなどの gesture を行った際に行われる処理をネイティブ層に閉じ込めることによって、本来のネイティブのパフォーマンスで動作が行えるようにするライブラリです。メール一覧で左にスワイプすることで削除するなどと言ったユースケースの時に使用されます。
詰まった箇所
react-native-gesture-handler が提供している Swipeable
や TouchableOpacity
を使って、ボタンを押したら遷移、スワイプしたら削除のような動作を行おうとした時、一部の画面で iOS では問題なく動いていたのが Android では Touch event も Swipe event も発生していなかった。
今まで他の箇所では問題なく動いていたのだが、どうやら react-native が提供している Modal
Component のみ Android で動作していなかった。
Android では、最初に react-native-gesture-handler を読み込んだ際、Gesture が行える範囲が MainActivity の1画面に限られているようで、react-nativeのModalを使った画面はそれとは別画面扱いになり、react-native-gesture-handlerの適応範囲外になっていた模様。
React Native Gesture Handler の公式ドキュメントによれば、もし Modal の中で react-native-gesture-handler を使う必要がある場合は、Modal の中でさらに gestureHandlerHOC
を使って、Modal のページの中でも gesture handler を読み込む様に設定する必要がある模様。
import * as React from 'react'
import { Text, Modal } from 'react-native'
import { TouchableOpacity, gestureHandlerRootHOC } from 'react-native-gesture-handler'
const ModalChild = gestureHandlerRootHOC(()=>{
return (
<TouchableOpacity onPress={()=>{/* some event */}}>
<Text>Button with Gesture</Text>
</TouchableOpacity>
)
})
export const GestureInModal = () => {
return (
<Modal>
<ModalChild />
</Modal>
)
}
これで問題なく Android の modal の中でも Gesture Handler を動かすことができた。