Skip to content
React Native Reanimated ガイドブック
Esc
navigateopen⌘Jpreview
On this page

ジェスチャーと組み合わせる

react-native-gesture-handlerのGestureDetectorとタップ・パンジェスチャーを組み合わせ、指の動きに追従するアニメーションとwithDecayによる慣性を実装します。

ここまでのアニメーションは、ボタンを押すと再生されるものが中心でした。第3部では、指の動きそのものにアニメーションを追従させます。ドラッグ量やスワイプの勢いといった「生きた値」を共有値に流し込み、コアAPIで動きを組み立てる。これがジェスチャー連携の基本です。

Reanimated自体はジェスチャーを検出しません。そのかわり、同じSoftware Mansionが開発するReact Native Gesture Handler(以下RNGH)と緊密に連携します。RNGHがジェスチャーを検出し、そのコールバックの中で共有値を書き換え、Reanimatedがそれをアニメーションへ変換する、という役割分担です。

RNGHにはTapPanのほか、Pinch(2本指の拡大縮小)やFling(素早いはじき)など多くのジェスチャーが用意されています。この章ではそのうち、もっとも使うTapPanを取り上げ、第2部で保留したwithDecayをジェスチャーと組み合わせて動かすところまで進めます。

指の動きがGesture Handlerと共有値を経由してAnimated Styleへ届く流れの図

ドラッグ中は位置を共有値へ流し、リリース時には速度をwithDecayへ渡して勢いを引き継ぎます。

ルートビューでラップする

RNGHを使うには、アプリのルートをGestureHandlerRootViewで包む必要があります。実際のルートビューにできるだけ近い位置に置くのが推奨です。ここが複数のジェスチャーを正しく協調させる土台になります。

import { GestureHandlerRootView } from "react-native-gesture-handler";

function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      {/* アプリの中身 */}
    </GestureHandlerRootView>
  );
}

タップジェスチャー

いちばん単純なジェスチャーがタップです。画面に指が短く触れたことを検出します。自作のボタンや押せる要素を、一から組み立てたいときに使えます。

まずは、触れると少し大きくなって色が変わる円を作ってみます。ジェスチャーはコンポーネントの本体でGesture.Tap()を呼んで定義します。振る舞いはonBeginonStartonEndonFinalizeといったメソッドをチェーンして組み立てます。ここでは触れ始めた直後に共有値をtrueにし、ジェスチャーが終わったらfalseへ戻します。

const pressed = useSharedValue(false);

const tap = Gesture.Tap()
  .onBegin(() => {
    pressed.value = true;
  })
  .onFinalize(() => {
    pressed.value = false;
  });

ジェスチャーに渡すコールバックは自動的にworklet化されるので、その中では共有値に安全にアクセスできます。特別なおまじないは要りません。

次に、pressedの状態に応じて色とスケールを変えるアニメーションをuseAnimatedStyleで定義します。バイオレットからイエローへ変わり、withTimingで20%だけなめらかに拡大します。

const animatedStyles = useAnimatedStyle(() => ({
  backgroundColor: pressed.value ? "#FFE04B" : "#B58DF1",
  transform: [{ scale: withTiming(pressed.value ? 1.2 : 1) }],
}));

定義したジェスチャーは、GestureDetectorgestureプロパティに渡します。GestureDetectorは、ジェスチャーを検出したいビューを包むコンポーネントです。アニメーションさせたいビューには、先ほどのanimatedStylesを渡しておきます。

<GestureDetector gesture={tap}>
  <Animated.View style={[styles.circle, animatedStyles]} />
</GestureDetector>

これで完成です。円に触れると、色が変わりながら少しふくらみます。

デモを読み込み中…

パンジェスチャー

今度は円をドラッグできるようにし、指を離すと元の位置に戻るようにしてみます。触れたときの色とスケールの変化はそのまま残します。この動きはタップでは表現できないので、パンジェスチャーに持ち替えます。

うれしいことに、ジェスチャーはどれも似たAPIを持っています。そのためTapPanに置き換え、onChangeメソッドを1つ足すだけでほぼ実装できます。

const pressed = useSharedValue(false);
const offset = useSharedValue(0);

const pan = Gesture.Pan()
  .onBegin(() => {
    pressed.value = true;
  })
  .onChange((event) => {
    offset.value = event.translationX;
  })
  .onFinalize(() => {
    offset.value = withSpring(0);
    pressed.value = false;
  });

onChangeに渡されるコールバックは、便利なプロパティを持つイベントデータを受け取ります。そのうちのtranslationXは、X軸方向にどれだけ移動したかを表します。これを共有値offsetに保存し、円を横に動かします。指を離したときに元の位置へ戻すには、onFinalizeの中でoffset.valueをリセットするだけです。ここではwithSpringで包み、バネのように戻します。

あとはuseAnimatedStyleoffsettranslateXに反映すれば完成です。

const animatedStyles = useAnimatedStyle(() => ({
  transform: [
    { translateX: offset.value },
    { scale: withTiming(pressed.value ? 1.2 : 1) },
  ],
  backgroundColor: pressed.value ? "#FFE04B" : "#b58df1",
}));

円をつかんで左右にドラッグし、離してみてください。バネで中央へ戻ります。

デモを読み込み中…

勢いを引き継ぐ

第2部のモディファイアの章で、withDecayは後であらためて扱うと述べました。ここがその場面です。

withDecayは、ジェスチャーの速度を引き継いで、そこから減速しながら止まる動きを作ります。指を離した瞬間の速度をそのまま初速として渡すと、スワイプの勢いを受け継いだまま、摩擦のある物体のように滑って止まります。

やることは、onFinalizeが受け取るイベントのvelocityXwithDecayvelocityに渡すだけです。ドラッグ中の位置はonChangechangeX(前フレームからの変化量)をoffsetに足し込んで追従させます。

const pan = Gesture.Pan()
  .onChange((event) => {
    offset.value += event.changeX;
  })
  .onFinalize((event) => {
    offset.value = withDecay({
      velocity: event.velocityX,
      rubberBandEffect: true,
      clamp: [
        -(width.value / 2) + SIZE / 2 + BOUNDARY_OFFSET,
        width.value / 2 - SIZE / 2 - BOUNDARY_OFFSET,
      ],
    });
  });

clampで四角形が動ける範囲を区切り、rubberBandEffecttrueにすることで、範囲の端に達したときゴムのように跳ね返らせています。残りのコードは、四角形が画面の外へ出ないよう幅を測っているだけです。

勢いをつけて離したとき、四角形がどう減速するか試してみてください。

デモを読み込み中…

withDecayの設定オプションは、公式のAPIリファレンスにひととおりまとまっています。

この章のまとめ

  • Reanimatedはジェスチャーを検出しません。検出はReact Native Gesture Handlerが担い、そのコールバックの中で共有値を書き換えます
  • Gesture.Tap()Gesture.Pan()でジェスチャーを作り、GestureDetectorに渡します。GestureDetectorは操作したい要素を包みます
  • ジェスチャーのコールバックは自動でworklet化されるので、共有値にそのままアクセスできます
  • withDecayにジェスチャーの速度を渡すと、勢いを引き継いで減速する動きになります

ここで扱ったのはRNGHのごく一部です。PinchFlingをはじめ、多くのジェスチャーやそれらを組み合わせる仕組みが用意されています。詳しくはReact Native Gesture Handlerのドキュメントを見てください。

次章では、ジェスチャーと並ぶもう一つの「生きた値」であるスクロール位置を扱い、スクロールに連動するアニメーションを組み立てます。

この章のもとになった公式ドキュメント