207 Tech Blog

テクノロジーで物流を変える 207 (ニーマルナナ) 株式会社のテックブログ

React Native Gesture Handlerで画像ズーム&回転のライブラリを作った話

画像をズーム・回転するライブラリを作った

インラインで画像(Viewも可)の拡大、回転をするライブラリできちんと更新されているものがなかったので、 自分でライブラリを作ってnpmで公開してみました。

適当に公開した後、weeklyで150くらいダウンロードされていたので義務感が生じて一応ちゃんと動くレベルまでアップデートしました。 www.npmjs.com

↓ こんな感じの挙動になります。

f:id:wktq:20211002213324g:plain

ZoomableViewで対象のコンポーネントをwrapすると、拡大・縮小・回転がインラインで可能になります。 ↓ サンプルコード

import ZoomableView from "react-native-zoomable-view";

// ...
  const imageUrl =
    'https://images.unsplash.com/photo-xxx';

  return (
    <ZoomableView>
      <Image style={{ width: 300, height: 300 }} source={{ uri: imageUrl }} />
    </ZoomableView>
  )

タッチジェスチャをハンドリングして、対象のViewをreanimated v2系を使ってアニメーションさせています。

github.com

タッチジェスチャーのハンドリングを簡単に書くために、 こちらもSoftware MansionのReact Native Gesture Handlerを利用しています。

github.com

reanimatedについては特に特殊な実装はしていないので、 今回は主にReact Native Gesture Handlerの使い方について書いていきたいと思います。

React Native Gesture Handlerの使い方

react-native-gesture-handlerは、タッチジェスチャーを簡単にハンドリングするためのライブラリです。 以下のようなジェスチャハンドラが用意されています。

  • PanGestureHandler: 上下移動
  • TapGestureHandler: タップ
  • LongPressGestureHandler: ロングタップ
  • RotationGestureHandler: 回転
  • FlingGestureHandler: スワイプ
  • PinchGestureHandler: ピンチ(拡大縮小で使うつまむ動作)
  • ForceTouchGestureHandler: フォースタッチ(強く押し込むやつ)

React NativeのViewのResponderで書こうとするとタッチ位置などからジェスチャを判別する必要がありますが、 これを使うとJSXでラップするだけで、子コンポーネントのタッチジェスチャを勝手に判別して イベントを発火してくれます。(↓ 画像は長押しをハンドリングする例)

f:id:wktq:20211002215335p:plain
公式ドキュメントより

ハンドラが変更を検知した際はonHandlerStateChangeが発火されるのと、 onGestureEventでイベントの情報が渡されるので、それぞれのハンドラで渡される値を使ってリアクションを実装します。 ↓は例としてLongPressGestureHandlerに関するdocsです。

docs.swmansion.com

DiscreteとContinuousの二種類がある

上記のハンドラはDiscreteとContinuousの2種類があり、 Discrete系ハンドラはonActiveが発火したら直ちに終了します(forcePushなど)。

Continuous系ハンドラは例えば拡大縮小など、ジェスチャが続く限り連続的にonActiveイベントが発火し続けます。 終了した時にはonEndイベントが発火します。

f:id:wktq:20211002215337p:plain

どちらの場合もジェスチャが検知された瞬間にHandler Stateが変更されます。

複数のイベントハンドラ入れ子にする

複数のイベントハンドラをnestすると、一番ラップしたViewに近いハンドラが優先的に発火し、 onEndが発火するまでは他のイベントを排除します。

もし回転しながらzoomできるようにしたい場合は、Cross handler interactionsという機構があります。 以下のようにSimultaneousHandlersに別ハンドラのrefを指定すると、 そのハンドラが動作している間も別のイベントハンドラが並行して実行されるようになります。

f:id:wktq:20211002220344p:plain
公式ドキュメントより

SimultaneousHandlersには配列でも指定できるので、3つ以上のハンドラを並列で動かすことも可能です。

Androidで動かない問題

React Native Gesture Handlerの提供しているコンポーネントやwrapしたコンポーネント配下のTouchableOpacityなどが Android上で動かないことが多々あります。(react-native-modalのModal内など、wixのreact-native-navigationを利用している場合も)

対応として、GestureHandlerRootViewまたはgestureHandlerRootHOCを利用して対象のViewをwrapする必要があります。

export default function App() {
  return <GestureHandlerRootView>{/* content */}</GestureHandlerRootView>;
}

おわりに

今回はReact Nativeライブラリを作った話とライブラリ中で使っているReact Native Gesture Handlerの使い方について書いてみました。 Responderで書こうとすると面倒なジェスチャハンドリングが簡単にできるので、独自のインタラクティブアニメーションを作る場合などに便利です。 この記事の内容が少しでもご参考になれば幸いです。

We're Hiring

207株式会社では、レガシーな物流業界の変革に挑む配達員向け効率化アプリ「TODOCUサポーター」を開発しています。 開発チームでは一緒に開発してくれるアプリエンジニア(React Native)やバックエンドエンジニアの仲間を大絶賛募集中です!

もし少しでもご興味がありましたら、以下のnotionをご覧ください!

www.notion.so