React Native: Text#onMoveShouldSetResponder徹底解説 - ジェスチャー制御の基本

2025-06-06

もう少し詳しく説明します。

React Native では、タッチイベントは「レスポンダーシステム」という仕組みで管理されています。ユーザーが画面に触れると、どのコンポーネントがそのタッチイベントに応答すべきかをシステムが判断します。

onMoveShouldSetResponder は、以下のような挙動をします。

  1. タッチが開始され、指が移動したとき
    ユーザーが画面に触れ、その指が移動(ドラッグなど)し始めたときに、Text コンポーネントに対してこの関数が呼び出されます。
  2. 真偽値の返却
    この関数は true または false を返します。
    • true を返した場合
      その Text コンポーネントが「レスポンダー」になるべきだとシステムに伝えます。レスポンダーになると、その後の指の移動(onResponderMove)、指が離れた(onResponderRelease)、タッチがキャンセルされた(onResponderTerminate)などのイベントをそのコンポーネントが受け取れるようになります。これにより、テキストのドラッグ選択や、テキストを使ったジェスチャーなどを実装できます。
    • false を返した場合
      その Text コンポーネントはレスポンダーになるべきではないとシステムに伝えます。この場合、そのタッチイベントは親コンポーネントや他の兄弟コンポーネントに伝播され、他のコンポーネントがレスポンダーになる機会を得ます。
  3. イベントオブジェクトの引数
    この関数には、タッチイベントに関する情報を含むイベントオブジェクトが引数として渡されます。これを使って、指の移動量や現在の位置などを確認し、レスポンダーになるべきかをより詳細に判断できます。

なぜこれが必要なのか?

すべてのタッチイベントをすべてのコンポーネントが処理する必要はありません。例えば、テキストをタップしてリンクに飛ぶ場合は onPress で十分です。しかし、テキストを選択したり、テキスト上でドラッグして何かを操作するような場合には、指の動きを継続的に追跡する必要があります。onMoveShouldSetResponder は、そういった「指の移動」を伴う操作に対して、どのコンポーネントが責任を持つべきかを柔軟に制御するために提供されています。

簡単な使用例のイメージ

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';

const MyTextComponent = () => {
  return (
    <View style={styles.container}>
      <Text
        style={styles.text}
        onMoveShouldSetResponder={() => {
          console.log('Text: onMoveShouldSetResponder が呼び出されました。');
          // ここで、指の動きに応じてレスポンダーになるべきかを判断
          // 例えば、ある閾値を超えて指が動いたら true を返すなど
          return true; // とりあえず true を返して、このTextコンポーネントがレスポンダーになるようにする
        }}
        onResponderGrant={() => {
          console.log('Text: レスポンダーになりました!');
        }}
        onResponderMove={(event) => {
          console.log('Text: 指が動いています!', event.nativeEvent.pageX, event.nativeEvent.pageY);
        }}
        onResponderRelease={() => {
          console.log('Text: 指が離れました。');
        }}
      >
        このテキストをドラッグしてみてください。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 24,
    padding: 20,
    backgroundColor: 'lightblue',
  },
});

export default MyTextComponent;

この例では、Text コンポーネントが onMoveShouldSetRespondertrue を返しているため、指が動くとこの Text コンポーネントがレスポンダーになり、その後の onResponderMove などのイベントを受け取ることができます。



イベントが発火しない、または期待通りに動作しない

エラー/症状

  • テキストのドラッグや選択ができない。
  • onResponderMoveonResponderRelease などの後続のレスポンダーイベントが発火しない。
  • onMoveShouldSetResponder が全く呼び出されない。

考えられる原因とトラブルシューティング

  • タッチ領域が狭すぎる (hitSlop の不足)

    • 説明
      Text コンポーネントの表示領域が非常に小さい場合、ユーザーが正確にタッチするのが難しいことがあります。onMoveShouldSetResponder が発火する前に、タッチがコンポーネントのバウンディングボックスを外れてしまう可能性があります。
    • 解決策
      View ラッパーに hitSlop プロパティを設定して、タッチ可能な領域を広げます。Text コンポーネント自体には hitSlop はありませんが、それを囲む View に設定できます。
    <View hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}>
      <Text
        onMoveShouldSetResponder={() => true}
        // ...
      >
        小さなテキスト
      </Text>
    </View>
    
  • レスポンダーの奪い合い (onResponderTerminationRequest)

    • 説明
      一度レスポンダーになったコンポーネントから、他のコンポーネントがレスポンダーを奪おうとする場合があります(例: スクローラーがユーザーの指がスクロールの動きだと判断した場合)。この時、現在のレスポンダーの onResponderTerminationRequest が呼び出され、true を返すとレスポンダーの移譲を許可し、false を返すと拒否します。
    • 解決策
      もしあなたの Text が一時的にレスポンダーになるものの、すぐに他のコンポーネントに奪われてしまう場合、onResponderTerminationRequest を確認してください。もしレスポンダーを保持したいなら、ここで false を返す必要があります。
    <Text
      // ...
      onResponderTerminationRequest={() => false} // レスポンダーを奪われるのを拒否
    >
      My Text
    </Text>
    
  • 他のコンポーネントが先にレスポンダーになっている

    • 説明
      React Native のレスポンダーシステムは、原則として一つのコンポーネントのみがアクティブなレスポンダーになれます。もし親コンポーネントや兄弟コンポーネントに onStartShouldSetResponderonMoveShouldSetResponder が設定されており、それが先に true を返した場合、あなたの Text コンポーネントはレスポンダーになれません。
    • 解決策
      • 階層を遡って、親や兄弟コンポーネントに設定されているレスポンダー関連のプロパティを確認します。
      • 特に ScrollViewFlatList のようなスクロール可能なコンポーネントは、内部でタッチイベントを処理するため、干渉しやすいです。これらのコンポーネント内で Text を使用する場合は、ジェスチャーの競合に注意が必要です。GestureHandler ライブラリ(React Native Gesture Handler)を使用することで、より柔軟なジェスチャーの制御が可能です。
      • テストのために、他のコンポーネントのレスポンダープロパティを一時的に無効にしてみてください。
    • 説明
      onMoveShouldSetResponder は、そのコンポーネントがレスポンダーになるべきかをシステムに伝える唯一の手段です。ここで true を返さなければ、その後のレスポンダーイベントは一切発火しません。
    • 解決策
      まず、onMoveShouldSetResponder が必ず true を返すようにして、イベントが発火するかどうかを確認します。デバッグのために console.log を入れて、関数が呼び出されているか、そして何が返されているかを確認してください。
    <Text
      onMoveShouldSetResponder={(evt) => {
        console.log('onMoveShouldSetResponder called, returning true');
        return true; // ここが重要
      }}
      // ...他のレスポンダープロパティ
    >
      My Text
    </Text>
    

パフォーマンスの問題

エラー/症状

  • タッチの反応が遅い、またはUIがカクつく。
  • onMoveShouldSetResponder 内での処理が重い。

考えられる原因とトラブルシューティング

  • onMoveShouldSetResponder 内での複雑な計算
    • 説明
      この関数は指の移動中に頻繁に呼び出される可能性があるため、内部で重い計算や多くの状態更新を行うとパフォーマンスに悪影響が出ます。
    • 解決策
      onMoveShouldSetResponder は、あくまで「レスポンダーになるべきか」を高速に判断するためのものです。計算は最小限に抑え、複雑なロジックは onResponderMove など、レスポンダーになった後のイベントハンドラで行うように設計してください。

ロジックの問題

エラー/症状

  • レスポンダーが解除されない。
  • 特定の条件でのみレスポンダーにしたいが、意図しない時にレスポンダーになってしまう。

考えられる原因とトラブルシューティング

  • onResponderRelease や onResponderTerminate の処理不足

    • 説明
      レスポンダーになった後の状態管理(例: 選択状態、ハイライト状態など)を、指が離れたりタッチがキャンセルされたときにリセットしないと、UIが意図しない状態のままになることがあります。
    • 解決策
      onResponderRelease (指が正常に離れたとき) や onResponderTerminate (他のコンポーネントにレスポンダーが奪われたり、アプリがバックグラウンドに移行したりしてタッチが中断されたとき) で、適切に状態をリセットする処理を記述してください。
  • onMoveShouldSetResponder の条件が不適切

    • 説明
      例えば、指が少しでも動いたら true を返してしまうと、意図しないジェスチャーでレスポンダーになってしまうことがあります。
    • 解決策
      event.nativeEvent に含まれる dx (X軸方向の移動量) や dy (Y軸方向の移動量) を利用して、指の移動量が特定の閾値を超えた場合にのみ true を返すように条件を設定します。
    <Text
      onMoveShouldSetResponder={(evt) => {
        const { dx, dy } = evt.nativeEvent;
        // 例えば、XまたはY方向に5ピクセル以上動いたらレスポンダーになる
        return Math.abs(dx) > 5 || Math.abs(dy) > 5;
      }}
      // ...
    >
      My Text
    </Text>
    

デバッグのヒント

  • シンプルな例で切り分ける

    • 複雑なコンポーネントの中で問題が発生している場合、TextonMoveShouldSetResponder だけを持つ最もシンプルなコンポーネントを作成し、それが期待通りに動作するかどうかをテストします。これにより、問題が特定のコンポーネントのロジックにあるのか、それとも全体的な環境や他のコンポーネントとの競合にあるのかを切り分けることができます。
  • React DevTools を使用する

    • React DevTools を使って、コンポーネントのプロパティや状態が正しく更新されているかを確認します。
  • console.log を活用する

    • onMoveShouldSetResponderonResponderGrantonResponderMoveonResponderReleaseonResponderTerminationRequestonResponderTerminate の各関数内で console.log を使って、いつ、どのイベントが呼び出され、どのような値が返されているかを確認します。
    • 特に event.nativeEvent の中身(pageX, pageY, locationX, locationY, dx, dy など)をログに出力すると、タッチの動きを視覚的に把握するのに役立ちます。


以下に、いくつかの一般的なプログラミング例と、それぞれのコードの説明を示します。

基本的なドラッグ検出

この例では、ユーザーがテキストをドラッグすると、そのテキストがレスポンダーになり、指の動きに応じてログが出力されます。

import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';

const BasicDragText = () => {
  const [logMessages, setLogMessages] = useState([]);

  const handleMoveShouldSetResponder = (event) => {
    // 指が動いたら、このTextコンポーネントがレスポンダーになるべきだと伝える
    const { dx, dy } = event.nativeEvent;
    const distance = Math.sqrt(dx * dx + dy * dy); // 移動距離を計算
    
    // ある程度の距離(例: 5ピクセル)以上移動した場合のみレスポンダーになる
    if (distance > 5) {
      addLog('onMoveShouldSetResponder: true (moved ' + distance.toFixed(2) + 'px)');
      return true;
    }
    addLog('onMoveShouldSetResponder: false (moved ' + distance.toFixed(2) + 'px)');
    return false;
  };

  const handleResponderGrant = (event) => {
    // レスポンダーになったときに呼び出される
    addLog('onResponderGrant: レスポンダーになりました!');
  };

  const handleResponderMove = (event) => {
    // 指が移動している間、継続的に呼び出される
    const { pageX, pageY } = event.nativeEvent;
    addLog(`onResponderMove: 現在の位置 (${pageX.toFixed(0)}, ${pageY.toFixed(0)})`);
  };

  const handleResponderRelease = (event) => {
    // 指が離されたときに呼び出される
    addLog('onResponderRelease: 指が離されました。');
  };

  const handleResponderTerminationRequest = (event) => {
    // 他のコンポーネントがレスポンダーを奪おうとしたときに呼び出される
    addLog('onResponderTerminationRequest: レスポンダーを奪おうとしています。');
    return true; // レスポンダーの奪取を許可する
  };

  const handleResponderTerminate = (event) => {
    // レスポンダーが他のコンポーネントに奪われたときに呼び出される
    addLog('onResponderTerminate: レスポンダーが奪われました。');
  };

  const addLog = (message) => {
    setLogMessages((prevMessages) => [...prevMessages, message]);
  };

  return (
    <View style={styles.container}>
      <Text
        style={styles.draggableText}
        onMoveShouldSetResponder={handleMoveShouldSetResponder}
        onResponderGrant={handleResponderGrant}
        onResponderMove={handleResponderMove}
        onResponderRelease={handleResponderRelease}
        onResponderTerminationRequest={handleResponderTerminationRequest}
        onResponderTerminate={handleResponderTerminate}
      >
        このテキストをドラッグしてみてください
      </Text>
      <View style={styles.logContainer}>
        <Text style={styles.logTitle}>イベントログ:</Text>
        {logMessages.slice(-10).map((msg, index) => ( // 最新10件を表示
          <Text key={index} style={styles.logText}>{msg}</Text>
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f0f0f0',
  },
  draggableText: {
    fontSize: 20,
    fontWeight: 'bold',
    padding: 30,
    backgroundColor: '#aaffaa',
    borderRadius: 10,
    marginBottom: 20,
    overflow: 'hidden', // Androidでテキストがはみ出さないように
  },
  logContainer: {
    width: '100%',
    height: 200,
    backgroundColor: '#fff',
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    overflow: 'scroll',
  },
  logTitle: {
    fontWeight: 'bold',
    marginBottom: 5,
  },
  logText: {
    fontSize: 12,
  },
});

export default BasicDragText;

解説

  • onResponderTerminate: レスポンダーが他のコンポーネントに奪われたり、OSによってタッチが中断されたりしたときに呼び出されます。
  • onResponderTerminationRequest: 他のコンポーネント(例えば、親の ScrollView など)が現在のレスポンダーからタッチイベントを奪おうとしたときに呼び出されます。true を返すと奪取を許可し、false を返すと拒否します。
  • onResponderRelease: ユーザーが指を離したときに呼び出されます。
  • onResponderMove: レスポンダーになった後、ユーザーが指を動かし続けている間、継続的に呼び出されます。event.nativeEvent.pageXevent.nativeEvent.pageY は、画面全体の座標における現在の指の位置を示します。
  • onResponderGrant: Text コンポーネントがタッチイベントのレスポンダーになった瞬間に呼び出されます。ここでは、レスポンダーになったことをログに記録しています。
  • onMoveShouldSetResponder: ユーザーが指を動かしたときに呼び出されます。ここでは、event.nativeEvent.dxevent.nativeEvent.dy を使って指の移動量を計算し、ある程度の距離(5ピクセル)以上動いた場合にのみ true を返して、この Text コンポーネントがレスポンダーになるようにしています。これにより、意図しない微細な動きでレスポンダーになってしまうのを防ぎます。

テキスト選択のシミュレーション(概念)

Text コンポーネントの直接的なテキスト選択機能はネイティブに組み込まれていますが、onMoveShouldSetResponder を使って独自の選択ロジックをシミュレートする概念を示します。実際のアプリケーションでは、より複雑なロジックとUIが必要です。

import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';

const SelectableText = () => {
  const [isSelecting, setIsSelecting] = useState(false);
  const [selectionStart, setSelectionStart] = useState({ x: 0, y: 0 });
  const [selectionEnd, setSelectionEnd] = useState({ x: 0, y: 0 });

  const handleMoveShouldSetResponder = (event) => {
    // 指が動いたら、このTextコンポーネントがレスポンダーになるべきだと伝える
    // 選択開始のために true を返す
    return true;
  };

  const handleResponderGrant = (event) => {
    // 選択開始
    setIsSelecting(true);
    const { pageX, pageY } = event.nativeEvent;
    setSelectionStart({ x: pageX, y: pageY });
    setSelectionEnd({ x: pageX, y: pageY });
    console.log('選択開始');
  };

  const handleResponderMove = (event) => {
    if (isSelecting) {
      // 選択範囲を更新
      const { pageX, pageY } = event.nativeEvent;
      setSelectionEnd({ x: pageX, y: pageY });
      console.log(`選択中: (${selectionStart.x.toFixed(0)},${selectionStart.y.toFixed(0)}) - (${pageX.toFixed(0)},${pageY.toFixed(0)})`);
    }
  };

  const handleResponderRelease = () => {
    // 選択終了
    setIsSelecting(false);
    console.log('選択終了');
    // ここで選択範囲確定などのロジックを実行
  };

  return (
    <View style={styles.container}>
      <Text
        style={[
          styles.selectableText,
          isSelecting && styles.selectingText // 選択中はスタイルを変更
        ]}
        onMoveShouldSetResponder={handleMoveShouldSetResponder}
        onResponderGrant={handleResponderGrant}
        onResponderMove={handleResponderMove}
        onResponderRelease={handleResponderRelease}
      >
        このテキストを選択してみてください。ドラッグで範囲を模擬表示します。
      </Text>
      {isSelecting && (
        <View
          style={[
            styles.selectionOverlay,
            {
              left: Math.min(selectionStart.x, selectionEnd.x),
              top: Math.min(selectionStart.y, selectionEnd.y),
              width: Math.abs(selectionStart.x - selectionEnd.x),
              height: Math.abs(selectionStart.y - selectionEnd.y),
            },
          ]}
        />
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f0f0f0',
  },
  selectableText: {
    fontSize: 20,
    padding: 30,
    backgroundColor: '#e0f7fa',
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#b2ebf2',
  },
  selectingText: {
    backgroundColor: '#a7d9f7', // 選択中の背景色
  },
  selectionOverlay: {
    position: 'absolute',
    backgroundColor: 'rgba(0, 0, 255, 0.2)', // 選択範囲を示すオーバーレイ
    pointerEvents: 'none', // オーバーレイ自体がタッチイベントをブロックしないように
  },
});

export default SelectableText;

解説

  • 注意
    これはあくまで onMoveShouldSetResponder の概念的な使い方を示すための簡略化された例です。実際のテキスト選択は、文字のインデックスに基づいたより複雑なロジックと、ネイティブのAPI(もしあれば)を組み合わせる必要があります。
  • selectionOverlay は、現在の選択範囲を視覚的に示すために絶対配置された View です。
  • この例では、onMoveShouldSetRespondertrue を返すことでテキストがレスポンダーになり、onResponderGrant で選択が開始されたとみなし、onResponderMove で指の現在位置を取得して selectionEnd を更新しています。

onMoveShouldSetResponder は個々のコンポーネントに直接設定できますが、より複雑なジェスチャー(パン、ピンチなど)を扱う場合は、React Native の PanResponder API を使用するのが一般的です。PanResponder は、ジェスチャーの状態(開始、移動、終了など)をより詳細に追跡し、カスタムアニメーションなどと連携しやすいです。

この例では、PanResponder を使って Text コンポーネントをドラッグで移動させます。

import React, { useRef, useState } from 'react';
import { View, Text, StyleSheet, PanResponder, Animated } from 'react-native';

const DraggableTextWithPanResponder = () => {
  const pan = useRef(new Animated.ValueXY()).current; // 位置をアニメーションするためのAnimated.ValueXY

  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true, // 指が動いたらPanResponderがタッチイベントを処理する
      onPanResponderGrant: () => {
        // ドラッグ開始時の初期位置を記憶
        pan.setOffset({
          x: pan.x._value,
          y: pan.y._value,
        });
        pan.setValue({ x: 0, y: 0 }); // 現在の位置をリセットして、オフセットからの相対移動を開始
      },
      onPanResponderMove: Animated.event(
        [
          null, // eventオブジェクトは使用しない
          { dx: pan.x, dy: pan.y }, // gestureStateのdx, dyをpan.x, pan.yにマッピング
        ],
        { useNativeDriver: false } // ネイティブドライバーを使用しない (Animated.ValueXYがUIスレッドで更新されるため)
      ),
      onPanResponderRelease: () => {
        // 指が離されたら、オフセットを現在の位置に結合
        pan.flattenOffset();
      },
    })
  ).current;

  return (
    <View style={styles.container}>
      <Animated.View
        style={{
          transform: [{ translateX: pan.x }, { translateY: pan.y }],
          ...styles.draggableBox,
        }}
        {...panResponder.panHandlers} // PanResponderのハンドラをViewに適用
      >
        <Text style={styles.draggableText}>ドラッグして移動</Text>
      </Animated.View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f0f0f0',
  },
  draggableBox: {
    backgroundColor: '#ffccaa',
    padding: 30,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#ff9966',
  },
  draggableText: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#333',
  },
});

export default DraggableTextWithPanResponder;
  • {...panResponder.panHandlers}: 作成した PanResponder のハンドラを Animated.View に展開することで、その View がタッチイベントに応答するようになります。Text コンポーネント自体に直接適用するのではなく、Text を囲む View に適用している点に注意してください。
  • onPanResponderRelease: 指が離されたときに呼び出されます。flattenOffset() を使うことで、オフセットと現在の値を結合し、次のドラッグ開始時にスムーズに継続できるようにします。
  • onPanResponderMove: 指が移動するたびに呼び出されます。Animated.event を使うことで、ジェスチャーの状態(dx, dy)を直接 Animated.ValueXY にマッピングし、ネイティブアニメーションのパフォーマンスを活かしながらスムーズな移動を実現します。
  • onPanResponderGrant: ジェスチャーが開始され、PanResponder がレスポンダーになったときに呼び出されます。ここでは、現在の位置をオフセットとして設定し、移動をリセットしています。
  • onMoveShouldSetPanResponder: PanResponder がタッチイベントのレスポンダーになるべきかを決定します。ここでは常に true を返しているので、指が動けばこの PanResponder がイベントを処理します。
  • PanResponder.create(): ジェスチャーハンドラの集合を作成します。


主に以下の3つの代替方法が考えられます。

    • これは、React Native アプリケーションでジェスチャーを処理するための、より宣言的で高性能なソリューションです。ネイティブモジュールで実装されているため、UIスレッドでジェスチャーを処理し、メインJSスレッドの負荷を軽減できます。
    • 特徴
      • パン、タップ、ロングプレス、ピンチ、回転など、さまざまな種類のジェスチャーコンポーネントを提供します。
      • 複雑なジェスチャーの状態管理(例: 複数のジェスチャーが同時に発生した場合の競合解決)を簡単に行えます。
      • 高いパフォーマンスを提供し、スムーズなUIレスポンスを実現します。
      • 宣言的なAPIにより、コードの可読性が向上します。
    • 用途
      • スワイプ可能なリストアイテム
      • 写真のピンチズーム
      • カスタムボタンのロングプレスアクション
      • より複雑なドラッグ&ドロップ機能
      • スクロールビュー内のカスタムジェスチャー
    • コード例(PanGesture の例)
      import React, { useRef } from 'react';
      import { View, Text, StyleSheet } from 'react-native';
      import { GestureHandlerRootView, PanGestureHandler } from 'react-native-gesture-handler';
      import Animated, {
        useAnimatedGestureHandler,
        useSharedValue,
        useAnimatedStyle,
      } from 'react-native-reanimated';
      
      const DraggableTextWithGestureHandler = () => {
        const translateX = useSharedValue(0);
        const translateY = useSharedValue(0);
        const startX = useSharedValue(0);
        const startY = useSharedValue(0);
      
        const gestureHandler = useAnimatedGestureHandler({
          onStart: (event, ctx) => {
            startX.value = translateX.value;
            startY.value = translateY.value;
          },
          onActive: (event) => {
            translateX.value = startX.value + event.translationX;
            translateY.value = startY.value + event.translationY;
          },
          onEnd: () => {
            // 必要であれば、ここで最終位置の処理など
          },
        });
      
        const animatedStyle = useAnimatedStyle(() => {
          return {
            transform: [{ translateX: translateX.value }, { translateY: translateY.value }],
          };
        });
      
        return (
          <GestureHandlerRootView style={styles.rootContainer}>
            <View style={styles.container}>
              <PanGestureHandler onGestureEvent={gestureHandler}>
                <Animated.View style={[styles.draggableBox, animatedStyle]}>
                  <Text style={styles.text}>Gesture Handlerでドラッグ</Text>
                </Animated.View>
              </PanGestureHandler>
            </View>
          </GestureHandlerRootView>
        );
      };
      
      const styles = StyleSheet.create({
        rootContainer: {
          flex: 1,
        },
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f0f0f0',
        },
        draggableBox: {
          backgroundColor: '#cceeaa',
          padding: 30,
          borderRadius: 10,
          borderWidth: 1,
          borderColor: '#99cc66',
        },
        text: {
          fontSize: 20,
          fontWeight: 'bold',
          color: '#333',
        },
      });
      
      export default DraggableTextWithGestureHandler;
      
    • onMoveShouldSetResponder との違い
      react-native-gesture-handler は、onMoveShouldSetResponderPanResponder よりもさらに高レベルの抽象化を提供し、ネイティブのジェスチャー処理をJSスレッドから分離することで、よりスムーズな体験を実現します。ほとんどの新しいプロジェクトでは、ジェスチャーを扱う際に最初に検討すべき選択肢です。
  1. 組み込みのコンポーネントプロパティ(onPress など)

    • テキストに対する単純なインタラクションであれば、onMoveShouldSetResponder のような複雑なジェスチャーハンドラは不要です。
    • 特徴
      • Text コンポーネントは、onPressonLongPress といった基本的なタッチイベントプロパティをサポートしています。
      • 最もシンプルで直感的な方法です。
    • 用途
      • テキストのタップ(リンク、詳細表示など)
      • テキストの長押し(コンテキストメニュー表示など)
    • コード例
      import React from 'react';
      import { View, Text, StyleSheet, Alert } from 'react-native';
      
      const SimpleTextInteraction = () => {
        const handlePress = () => {
          Alert.alert('タップされました', 'テキストがタップされました!');
        };
      
        const handleLongPress = () => {
          Alert.alert('長押しされました', 'テキストが長押しされました!');
        };
      
        return (
          <View style={styles.container}>
            <Text
              style={styles.simpleText}
              onPress={handlePress}
              onLongPress={handleLongPress}
            >
              このテキストをタップまたは長押ししてください
            </Text>
          </View>
        );
      };
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f0f0f0',
        },
        simpleText: {
          fontSize: 20,
          padding: 20,
          backgroundColor: '#ffddee',
          borderRadius: 10,
        },
      });
      
      export default SimpleTextInteraction;
      
    • onMoveShouldSetResponder との違い
      移動を伴わない、単一のタップや長押しといった静的なジェスチャーに特化しています。
  • 組み込みの onPress, onLongPress
    単純なタップや長押しであれば、これらが最も簡単で適切な選択肢。
  • react-native-gesture-handler
    ほとんどの新しいプロジェクトで推奨される、最も強力で高性能なジェスチャー処理ライブラリ。ネイティブスレッドで動作するため、パフォーマンスが高く、複雑なジェスチャーの競合解決も容易。
  • PanResponder
    onMoveShouldSetResponder と同じレスポンダーシステムに基づいているが、より体系的にジェスチャーを管理できるため、複雑なドラッグ&ドロップやジェスチャーアニメーションに適している。
  • Text#onMoveShouldSetResponder
    低レベルで特定の状況(特に Text コンポーネント自体の移動ジェスチャーを細かく制御したい場合)で役立つが、直接使うことは稀。