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

2025-06-06

React NativeにおけるTextコンポーネントのonResponderReleaseプロパティは、ユーザーが要素へのタッチを終了(指を離した)したときに発生するイベントを処理するためのものです。

これはReact Nativeの「ジェスチャーレスポンダーシステム (Gesture Responder System)」の一部であり、ユーザーのインタラクション(タッチやスワイプなど)のライフサイクルを管理します。

もう少し詳しく説明すると、以下のようになります。

  1. ジェスチャーレスポンダーシステムとは? React Nativeは、ユーザーのタッチ操作を効率的に処理するために、ジェスチャーレスポンダーシステムという独自の仕組みを持っています。これにより、どのビュー(コンポーネント)が現在のタッチイベントに応答するかを決定し、そのライフサイクル全体を管理します。

  2. onResponderReleaseの役割 onResponderReleaseは、特定のビューがレスポンダー(タッチイベントに応答する責任を持つコンポーネント)になった後、ユーザーがそのビューから指を離したときに呼び出されます。「タッチアップ」の瞬間と考えてください。

  3. 使用例 例えば、Textコンポーネントをタップ可能にして、指を離したときに何らかのアクションを実行したい場合などに使用します。

    import React from 'react';
    import { Text, View, StyleSheet } from 'react-native';
    
    const App = () => {
      const handleRelease = () => {
        console.log('テキストがリリースされました!');
        // ここに、指を離したときに実行したい処理を書きます
      };
    
      return (
        <View style={styles.container}>
          <Text
            style={styles.clickableText}
            onStartShouldSetResponder={() => true} // このビューがレスポンダーになるべきかを尋ねる
            onResponderRelease={handleRelease}    // 指を離したときのイベントハンドラ
          >
            このテキストをタップして指を離してください
          </Text>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      },
      clickableText: {
        fontSize: 20,
        color: 'blue',
        textDecorationLine: 'underline',
      },
    });
    
    export default App;
    

    上記の例では、onStartShouldSetResponder={() => true}によって、このTextコンポーネントがタッチイベントのレスポンダーになることを許可しています。そして、指を離したときにhandleRelease関数が実行されます。

  4. 注意点

    • onResponderReleaseは、そのビューが実際にタッチイベントのレスポンダーである場合にのみ呼び出されます。レスポンダーになるには、onStartShouldSetResponderonMoveShouldSetResponderのようなプロパティでtrueを返す必要があります。
    • 通常、シンプルなタップ処理にはonPressプロパティを使用する方が簡単で推奨されます。onResponderReleaseは、より複雑なジェスチャー(ドラッグ&ドロップなど)を細かく制御したい場合に、onResponderGrantonResponderMoveといった他のレスポンダーシステムのプロパティと組み合わせて使用されることが多いです。


React Native の Text#onResponderRelease における一般的なエラーとトラブルシューティング

Text コンポーネントで onResponderRelease を使用する際に遭遇しやすい問題と、それらの解決策を以下に示します。

onResponderRelease がまったく発火しない

これが最も一般的な問題です。onResponderRelease が機能するためには、その Text コンポーネントがジェスチャーレスポンダーとして認識される必要があります。

考えられる原因と解決策

  • 解決策
    onStartShouldSetResponder または onMoveShouldSetRespondertrue に設定する。

    • onStartShouldSetResponder: ユーザーがこのビューをタッチしたときに、このビューがレスポンダーになるべきかを尋ねる関数です。タップ開始時にレスポンダーになりたい場合に true を返します。
    • onMoveShouldSetResponder: ユーザーがこのビュー上で指を動かしたときに、このビューがレスポンダーになるべきかを尋ねる関数です。スワイプ操作などでレスポンダーになりたい場合に true を返します。


    import React from 'react';
    import { Text, View, StyleSheet, Alert } from 'react-native';
    
    const App = () => {
      const handleResponderRelease = () => {
        Alert.alert('リリースされました', 'onResponderReleaseが発火しました!');
        console.log('onResponderRelease fired!');
      };
    
      return (
        <View style={styles.container}>
          <Text
            style={styles.responsiveText}
            onStartShouldSetResponder={() => true} // これが重要!
            onResponderRelease={handleResponderRelease}
          >
            このテキストをタッチして指を離してください
          </Text>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      },
      responsiveText: {
        fontSize: 20,
        padding: 10,
        backgroundColor: '#ADD8E6', // 背景色を付けてタップ範囲をわかりやすく
        borderRadius: 5,
      },
    });
    
    export default App;
    

    多くの場合、onStartShouldSetResponder={() => true} を追加することで解決します。

  • 原因
    Text コンポーネントがレスポンダーになっていない。

    • Text コンポーネントは、デフォルトではタッチイベントのレスポンダーになりにくい性質があります。特に、子要素を持たないシンプルな Text の場合、onPress とは異なり、レスポンダーになるための明示的な設定が必要です。

親要素または子要素とのジェスチャー競合

複数のビューがジェスチャーレスポンダーになる可能性がある場合、意図しないビューがレスポンダーになってしまい、期待する onResponderRelease が発火しないことがあります。

考えられる原因と解決策

  • 解決策
    • レスポンダーの優先順位を理解する
      React Native のジェスチャーレスポンダーシステムは、タッチイベントが発生した際、一番深い(最も内側の)ビューから順にレスポンダーになる権利を問い合わせていきます。しかし、親が true を返すと、それより深い子要素はレスポンダーになれません。
    • onResponderCapture の利用
      親ビューで onResponderCapture を使用して、子要素にタッチイベントを渡す前に、親がイベントを「キャプチャ」するかどうかを制御できます。
    • onPress の使用を検討する
      単純なタップ操作であれば、Text コンポーネントの onPress プロパティを使用する方が、レスポンダーシステムを直接扱うよりもはるかに簡単で、競合の問題も発生しにくいです。onPress は、内部的にジェスチャーレスポンダーシステムを適切に処理してくれます。
    • ビュー階層の確認
      どのビューが実際にタッチ領域を占めているか、またどのビューがレスポンダーになり得るかを確認するために、ビューの構造を見直してください。
  • 原因
    親ビューが子ビューよりも先にレスポンダーを取得してしまっている。または、子ビューがレスポンダーを取得するのを妨げている。

onResponderRelease の動作が onPress と違う、期待通りではない

onResponderReleaseonPress とは異なるイベントです。onPress は「プレスアンドリリース」の完全なサイクルが成功した場合にのみ発火しますが、onResponderRelease は指が離れた時点で発火します(たとえ指がタップ開始位置から少し動いたとしても)。

考えられる原因と解決策

  • 解決策
    • 目的を明確にする
      • 純粋なタップ
        ユーザーが要素をタッチして、ほとんど動かさずに指を離した場合に何かを実行したいなら、onPress を使うべきです。これは、ボタンのような標準的なインタラクションに最適です。
      • 指が離れた瞬間
        ユーザーが要素をタッチしてから指を離した瞬間に何かを実行したいが、その間に指が動いた可能性も考慮したい場合(例えば、ドラッグ操作の終了を検出したい場合など)は、onResponderRelease が適しています。
    • onResponderGrant との組み合わせ
      onResponderRelease は、通常 onResponderGrant(レスポンダーになった瞬間に発火)や onResponderMove(指が動いたときに発火)と組み合わせて、より複雑なジェスチャーロジックを実装するために使用されます。
  • 原因
    onPressonResponderRelease の違いを理解していない。

イベントオブジェクトの構造がわからない

onResponderRelease に渡されるイベントオブジェクトの構造を理解していない場合があります。

考えられる原因と解決策

  • 解決策
    イベントオブジェクトを console.log() で出力して確認する。

    • nativeEvent: ネイティブ側のイベント情報(タッチ座標など)が含まれます。
    • changedTouches: 最後に変化したタッチイベントの配列。
    • identifier: タッチの識別子。
    • locationX, locationY: コンポーネント内の相対位置。
    • pageX, pageY: スクリーン上の絶対位置。
    • target: イベントが発生したビューのID。
    • timestamp: イベント発生時刻。
    • touches: 現在アクティブなすべてのタッチイベントの配列。


    const handleResponderRelease = (event) => {
      console.log('Event Object:', event.nativeEvent);
      // 例えば、指が離れた座標を取得したい場合
      console.log('Released at X:', event.nativeEvent.locationX);
      console.log('Released at Y:', event.nativeEvent.locationY);
    };
    
  • 原因
    イベントオブジェクトに何が含まれているか不明。

スタイルやレイアウトの問題でタッチ領域が期待と異なる

Text コンポーネントに適切な paddingmargin がない場合、タッチ領域が小さすぎて、ユーザーがタップしにくいことがあります。

考えられる原因と解決策

  • 解決策

    • Text コンポーネント自体に padding を追加する。
    • TextView で囲み、その Viewpaddingheight, width を設定して、タッチ領域を広げる。View はデフォルトでレスポンダーになりやすいため、こちらの方が扱いやすい場合もあります。


    <View
      style={styles.touchableArea}
      onStartShouldSetResponder={() => true}
      onResponderRelease={handleResponderRelease}
    >
      <Text style={styles.responsiveText}>
        このテキストをタッチして指を離してください
      </Text>
    </View>
    
    // ...
    
    const styles = StyleSheet.create({
      touchableArea: {
        padding: 20, // タッチ領域を広げる
        backgroundColor: 'lightgrey',
        borderRadius: 10,
      },
      responsiveText: {
        fontSize: 20,
        // Text 自体には padding をあまり設定せず、親 View で制御
      },
    });
    
  • 原因
    Text コンポーネントの表示上の領域と、実際にタッチ可能な領域が一致していない。

Text#onResponderRelease は、React Native のより低レベルなジェスチャーレスポンダーシステムの一部であり、onPress よりも詳細なジェスチャー制御が必要な場合に利用されます。一般的なエラーは、主にレスポンダーになるための設定不足と、onPress との挙動の違いに対する誤解に起因します。

原因

  • 親要素とのジェスチャー競合
    親の ViewScrollView などがタッチイベントを捕捉しており、Text コンポーネントまでイベントが伝播しないことがあります。特に ScrollView はスクロールジェスチャーを優先するため、その中の要素のジェスチャーが阻害されやすいです。
  • レスポンダーになっていない
    onResponderRelease は、そのコンポーネントがタッチイベントの「レスポンダー」として登録されている場合にのみ発火します。Text コンポーネント単体では、デフォルトでジェスチャーレスポンダーになる機能を持っていません。

トラブルシューティング

  • ジェスチャー競合の解決
    • ScrollView 内で onResponderRelease を使用する場合は、ScrollViewscrollEnabled を一時的に false にするなど、スクロールとジェスチャーのどちらを優先するかを考慮する必要があります。
    • 複雑なジェスチャーの場合、React Native の PanResponder を利用して、より詳細なジェスチャーのライフサイクルを制御することを検討します。
  • TouchableWithoutFeedback で囲む
    Text を直接レスポンダーにする代わりに、TouchableWithoutFeedback などの Touchable コンポーネントで Text を囲むのが一般的で推奨される方法です。TouchableWithoutFeedback はジェスチャーレスポンダーのロジックを内部で持っており、onPressonPressOut (これが onResponderRelease に相当するケースが多い) などのより高レベルなイベントを提供します。
    import { TouchableWithoutFeedback, Text } from 'react-native';
    
    <TouchableWithoutFeedback
      onPressOut={() => console.log('タッチ終了')} // onResponderRelease と似た挙動
    >
      <Text>
        タップできるテキスト
      </Text>
    </TouchableWithoutFeedback>
    
  • onStartShouldSetResponder の設定
    Text コンポーネントがレスポンダーになるように、onStartShouldSetResponder プロパティを true に設定します。
    <Text
      onStartShouldSetResponder={() => true} // これが重要
      onResponderRelease={() => console.log('リリースされました')}
    >
      タップできるテキスト
    </Text>
    

onResponderRelease が意図しないタイミングで発火する、または複数回発火する

原因

  • 親と子のジェスチャー重複
    親子関係にあるコンポーネントの両方がレスポンダーになる設定になっていると、イベントが両方で発火したり、予期せぬ競合が発生したりすることがあります。
  • タッチの開始・終了のずれ
    ユーザーが指をスライドさせたり、素早くタップとリリースを繰り返したりすると、イベントの発火タイミングが期待と異なる場合があります。

トラブルシューティング

  • 状態管理
    onResponderRelease が複数回発火してしまう問題に対しては、useState などを用いて「現在処理中である」という状態を管理し、重複して処理が実行されないようにガードするロジックを追加します。
  • レスポンダーの取得と解放の管理
    • onStartShouldSetResponderCaptureonMoveShouldSetResponderCapture を使用して、親コンポーネントが子コンポーネントよりも先にイベントを捕捉するかどうかを制御できます。
    • onResponderTerminate を使って、レスポンダーが奪われた場合の処理を記述します。
  • ジェスチャーレスポンダーのライフサイクル理解
    onResponderGrant (タッチ開始)、onResponderMove (タッチ移動)、onResponderRelease (タッチ終了)、onResponderTerminate (レスポンダーが他のコンポーネントに奪われた場合) など、ジェスチャーレスポンダーの各イベントの役割を理解し、適切に利用します。

Android で onResponderRelease がうまく動作しない

原因

  • React Native の古いバージョンや特定のAndroidデバイス/OSバージョンにおいて、ジェスチャーレスポンダーシステムの一部の挙動がiOSと異なる、またはバグが存在する場合があります。(過去にはGitHubでそのようなIssueが報告されていますが、最新バージョンでは改善されていることが多いです。)

トラブルシューティング

  • PanResponder の利用
    より複雑で堅牢なジェスチャー処理が必要な場合は、PanResponder を利用することが推奨されます。PanResponder はより低レベルなタッチイベントを統合的に扱えるため、Android特有の挙動の差異にも対応しやすいです。
  • onPressOut の利用検討
    シンプルなタップ終了のイベントであれば、TextTouchableWithoutFeedback で囲み、その onPressOut を使用することを検討します。onPressOutonResponderRelease よりも高レベルな抽象化がされており、プラットフォーム間の挙動の差異が少ない傾向にあります。
  • React Native のバージョンアップ
    最新の安定版にアップデートすることで、既知のバグが修正されている可能性があります。

Text#onResponderRelease は、React Native のジェスチャーレスポンダーシステムの一部であり、特定の状況(特に複雑なジェスチャー検出やカスタムインタラクション)で強力なツールとなります。しかし、一般的なタップイベントには onPress を持つ Touchable コンポーネント(TouchableWithoutFeedback, TouchableOpacity, TouchableHighlight など)を使用する方が、シンプルでエラーが少なく、より推奨されます。

onResponderRelease を使用する場合は、以下の点を意識することが重要です。

  • より高レベルな Touchable コンポーネントや PanResponder の利用も検討すること。
  • 親要素や他のコンポーネントとのジェスチャー競合に注意すること。
  • ジェスチャーレスポンダーシステムのライフサイクルを理解すること。
  • onStartShouldSetResponder / onMoveShouldSetResponder によるレスポンダーの取得が必須であること。


ここでは、Text#onResponderRelease を使用する際の具体的なコード例をいくつか紹介し、その挙動を説明します。

例1: 基本的な onResponderRelease の使用

この例では、Text コンポーネントがタッチされた後に指が離されたことをコンソールに表示します。

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

const BasicResponderRelease = () => {
  const handleResponderRelease = (event) => {
    // event オブジェクトには、タッチに関する詳細情報が含まれます
    // 例: event.nativeEvent.pageX, event.nativeEvent.pageY (画面上の座標)
    // event.nativeEvent.locationX, event.nativeEvent.locationY (要素内の座標)
    console.log('Text がリリースされました!');
    console.log('イベント情報:', event.nativeEvent);
    Alert.alert('イベント', 'テキストがリリースされました!');
  };

  return (
    <View style={styles.container}>
      <Text
        style={styles.clickableText}
        // onStartShouldSetResponder は、このビューがレスポンダーになるべきかを尋ねます。
        // true を返すことで、この Text がタッチイベントを処理する権利を得ます。
        onStartShouldSetResponder={() => true}
        onResponderRelease={handleResponderRelease}
      >
        このテキストをタップして指を離してください (onResponderRelease)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  clickableText: {
    fontSize: 18,
    color: 'blue',
    padding: 20,
    borderWidth: 1,
    borderColor: 'gray',
    borderRadius: 5,
  },
});

export default BasicResponderRelease;

説明

  • event.nativeEvent: イベントオブジェクトには、タッチの座標など、低レベルなタッチに関する情報が含まれています。
  • onResponderRelease={handleResponderRelease}: レスポンダーになった Text コンポーネント上でユーザーが指を離したときに handleResponderRelease 関数が呼び出されます。
  • onStartShouldSetResponder={() => true}: これが最も重要です。このプロパティがないと、Text コンポーネントはジェスチャーレスポンダーシステムの一部として認識されず、onResponderRelease が発火しません。true を返すことで、この Text がタッチイベントのレスポンダーになることを要求します。

例2: onResponderGrant との組み合わせ(押された時と離された時)

onResponderRelease は、onResponderGrant (タッチが開始され、このコンポーネントがレスポンダーになった時) と組み合わせて使用することで、より詳細なタッチのフィードバックを提供できます。

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

const GrantAndReleaseExample = () => {
  const [isPressed, setIsPressed] = useState(false);

  const handleResponderGrant = () => {
    console.log('Text にタッチしました (onResponderGrant)');
    setIsPressed(true); // 押された状態に設定
  };

  const handleResponderRelease = () => {
    console.log('Text から指を離しました (onResponderRelease)');
    setIsPressed(false); // 押されていない状態に設定
    Alert.alert('アクション', 'テキストが押されてから離されました!');
  };

  return (
    <View style={styles.container}>
      <Text
        style={[
          styles.interactiveText,
          isPressed ? styles.textPressed : styles.textNormal,
        ]}
        onStartShouldSetResponder={() => true}
        onResponderGrant={handleResponderGrant}
        onResponderRelease={handleResponderRelease}
      >
        {isPressed ? '指を離してください' : '私をタップしてください'}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  interactiveText: {
    fontSize: 22,
    padding: 25,
    borderWidth: 2,
    borderRadius: 10,
    textAlign: 'center',
  },
  textNormal: {
    backgroundColor: '#e0e0e0',
    borderColor: 'gray',
    color: 'black',
  },
  textPressed: {
    backgroundColor: '#a0a0a0',
    borderColor: 'darkgray',
    color: 'white',
  },
});

export default GrantAndReleaseExample;

説明

  • onResponderRelease: ユーザーが指を離したときに呼び出されます。isPressedfalse に戻し、元のスタイルに戻します。
  • onResponderGrant: ユーザーが Text をタッチし、この Text がレスポンダーになった瞬間に呼び出されます。ここでは isPressedtrue に設定し、視覚的なフィードバック(背景色の変更)を提供しています。
  • useState を使用して isPressed という状態を管理し、Text のスタイルを動的に変更しています。

onResponderRelease が発火する前に、他のコンポーネントやOSによってレスポンダーが奪われる場合があります。そのようなシナリオを処理するために onResponderTerminate があります。

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

const ResponderTerminationExample = () => {
  const [status, setStatus] = useState('待機中...');

  // PanResponder を使用して、親Viewがジェスチャーを奪う例
  const panResponder = React.useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true, // 親Viewがレスポンダーになることを許可
      onPanResponderGrant: () => {
        setStatus('親がレスポンダーを取得しました!');
      },
      onPanResponderRelease: () => {
        setStatus('親がリリースされました。');
      },
      onPanResponderTerminate: () => {
        setStatus('親のレスポンダーが終了しました。');
      },
    })
  ).current;

  const handleTextGrant = () => {
    setStatus('テキストがレスポンダーを取得しました!');
  };

  const handleTextRelease = () => {
    setStatus('テキストがリリースされました!');
    Alert.alert('Text Event', 'テキストがリリースされました!');
  };

  const handleTextTerminate = () => {
    setStatus('テキストのレスポンダーが奪われました!');
    Alert.alert('Text Event', 'テキストのレスポンダーが奪われました。');
  };

  return (
    <View style={styles.container} {...panResponder.panHandlers}>
      <Text style={styles.statusText}>状態: {status}</Text>

      <Text
        style={styles.innerClickableText}
        onStartShouldSetResponder={() => true} // この Text もレスポンダーになりたい
        onResponderGrant={handleTextGrant}
        onResponderRelease={handleTextRelease}
        onResponderTerminate={handleTextTerminate} // レスポンダーが奪われた時に発火
      >
        このテキストをタップしてください
      </Text>

      <Text style={styles.infoText}>
        (テキストをタップして指を動かしたり、テキストの外側をタップして指を離すと、
        レスポンダーが奪われる挙動を確認できます)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f0f0f0',
  },
  statusText: {
    fontSize: 16,
    marginBottom: 20,
  },
  innerClickableText: {
    fontSize: 18,
    color: 'purple',
    padding: 30,
    borderWidth: 2,
    borderColor: 'orange',
    borderRadius: 15,
    backgroundColor: '#fff',
    marginBottom: 20,
  },
  infoText: {
    fontSize: 12,
    color: 'gray',
    textAlign: 'center',
    marginHorizontal: 20,
  },
});

export default ResponderTerminationExample;
  • onResponderTerminate: Text がレスポンダーになった後に、他のコンポーネント(この場合は親の View)がタッチイベントのレスポンダーを要求し、Text がそのレスポンダーを「手放す」場合にこのイベントが発火します。例えば、Text をタッチしたまま指を動かし、親の View がその動きを「パンジェスチャー」と認識してレスポンダーを奪うようなシナリオが考えられます。
  • ジェスチャー競合のシミュレーション
    親の ViewPanResponder を設定し、親もタッチイベントのレスポンダーになる可能性があるようにしています。


Touchable コンポーネントを使用する (最も一般的で推奨される方法)

ほとんどの「テキストをタップして何かを実行する」というユースケースでは、onResponderRelease を直接使うよりも、React Native が提供する Touchable コンポーネントを使用する方がはるかに簡単で堅牢です。これらのコンポーネントは、内部的にジェスチャーレスポンダーシステムを管理しており、onPressonPressOut といった直感的なプロパティを提供します。

主な Touchable コンポーネントは以下の通りです。

  • TouchableHighlight: タップ時に背景がハイライト表示されるボタンなど。

  • TouchableWithoutFeedback: 視覚的なフィードバックを必要としない場合(例えば、カスタムなフィードバックを自分で実装したい場合)に使用します。

    • 代替イベント: onPress, onPressOut, onPressIn (上記と同様)
    • コード例:
      import React from 'react';
      import { View, Text, TouchableWithoutFeedback, StyleSheet, Alert } from 'react-native';
      
      const TouchableWithoutFeedbackExample = () => {
        const handlePressOut = () => {
          console.log('TouchableWithoutFeedback: 指を離しました (onPressOut)');
          Alert.alert('イベント', 'onPressOut が発火しました!');
        };
      
        return (
          <View style={styles.container}>
            <TouchableWithoutFeedback
              onPressOut={handlePressOut} // onResponderRelease の代替
            >
              <View style={styles.feedbackArea}>
                <Text style={styles.text}>
                  TouchableWithoutFeedback をタップして指を離す
                </Text>
              </View>
            </TouchableWithoutFeedback>
          </View>
        );
      };
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
        },
        feedbackArea: {
          padding: 20,
          backgroundColor: '#FFD700', // Gold
          borderRadius: 10,
          borderWidth: 1,
          borderColor: '#DAA520', // Goldenrod
        },
        text: {
          fontSize: 18,
          color: '#333',
        },
      });
      
      export default TouchableWithoutFeedbackExample;
      
  • TouchableOpacity: タップすると透明度が変化する(視覚的なフィードバックがある)ボタンなどによく使われます。

    • 代替イベント:
      • onPress: タップが完了したときに発火します。(指を押し下げてから離すまでの一連の動作が完了したときに、一度だけ発火)
      • onPressOut: 指を離したときに発火します。これは onResponderRelease に最も近い挙動をします。
      • onPressIn: 指を押し下げたときに発火します。
    • コード例:
      import React from 'react';
      import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
      
      const TouchableOpacityExample = () => {
        const handlePressOut = () => {
          console.log('TouchableOpacity: 指を離しました (onPressOut)');
          Alert.alert('イベント', 'onPressOut が発火しました!');
        };
      
        return (
          <View style={styles.container}>
            <TouchableOpacity
              style={styles.touchableArea}
              onPressOut={handlePressOut} // onResponderRelease の代替
              // onPress={() => Alert.alert('イベント', 'onPress が発火しました!')}
            >
              <Text style={styles.text}>
                TouchableOpacity をタップして指を離す
              </Text>
            </TouchableOpacity>
          </View>
        );
      };
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
        },
        touchableArea: {
          padding: 20,
          backgroundColor: '#ADD8E6', // Light Blue
          borderRadius: 10,
          borderWidth: 1,
          borderColor: '#6A5ACD', // Slate Blue
        },
        text: {
          fontSize: 18,
          color: '#333',
        },
      });
      
      export default TouchableOpacityExample;
      

メリット

  • 視覚的なフィードバック(ハイライト、透明度変化など)を簡単に実装できる。
  • onPress, onPressIn, onPressOut など、より直感的なイベント名が提供されている。
  • プラットフォームごとの差異が吸収されており、より一貫した挙動が期待できる。
  • onResponderRelease のように onStartShouldSetResponder を設定する必要がない。

Pressable コンポーネントを使用する (React Native 0.63+ で推奨される新しいAPI)

Pressable は、Touchable コンポーネントを置き換えることを目的とした、より柔軟で高機能なAPIです。きめ細やかなプレスイベントを制御でき、状態に応じたスタイリングも容易です。

  • コード例:
    import React from 'react';
    import { View, Text, Pressable, StyleSheet, Alert } from 'react-native';
    
    const PressableExample = () => {
      const handlePressOut = () => {
        console.log('Pressable: 指を離しました (onPressOut)');
        Alert.alert('イベント', 'onPressOut が発火しました!');
      };
    
      return (
        <View style={styles.container}>
          <Pressable
            onPressOut={handlePressOut} // onResponderRelease の代替
            style={({ pressed }) => [ // プレス状態に応じたスタイリングも可能
              styles.pressableArea,
              pressed && styles.pressablePressed,
            ]}
          >
            {({ pressed }) => (
              <Text style={styles.text}>
                {pressed ? '指を離してください' : 'Pressable をタップ'}
              </Text>
            )}
          </Pressable>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
      },
      pressableArea: {
        padding: 20,
        backgroundColor: '#98FB98', // Pale Green
        borderRadius: 10,
        borderWidth: 1,
        borderColor: '#3CB371', // Medium Sea Green
      },
      pressablePressed: {
        backgroundColor: '#6B8E23', // Olive Drab
      },
      text: {
        fontSize: 18,
        color: '#333',
      },
    });
    
    export default PressableExample;
    
  • 代替イベント:
    • onPress: タップが完了したときに発火。
    • onPressOut: 指を離したときに発火。これも onResponderRelease に最も近い挙動です。
    • onPressIn: 指を押し下げたときに発火。
    • onLongPress: 長押しが検出されたときに発火。
    • onPressonLongPress の間での競合解決ロジックも提供されています。

メリット

  • 将来的な拡張性も考慮されている。
  • pressed 状態にアクセスできるため、カスタムなプレスフィードバックを簡単に実装できる。
  • Touchable コンポーネントの機能を全て含み、さらに柔軟。

もし単に「指を離した」というイベントだけでなく、ドラッグ、スワイプ、ピンチなどのより複雑なジェスチャーを検出・制御したい場合は、PanResponder が最適な選択肢となります。PanResponder はジェスチャーレスポンダーシステムを完全にラップしており、タッチイベントのライフサイクル全体を細かく制御できます。

onResponderRelease の代わりに、PanResponder の以下のプロパティを使用します。

  • onPanResponderTerminate: レスポンダーが他のコンポーネントに奪われたときに発火します。
  • onPanResponderRelease: ジェスチャーが終了し、指が離されたときに発火します。

メリット

  • タッチイベントの座標や速度など、詳細な情報を取得できる。
  • ドラッグ&ドロップ、リサイズ、スワイプなど、複雑なカスタムジェスチャーを実装できる。

デメリット

  • 実装が複雑になる。単純なタップイベントのためにはオーバーキルとなる。
方法用途onResponderRelease の代替イベントメリットデメリット
TouchableOpacity最も一般的。タップ時に視覚的なフィードバックが欲しい場合。onPressOut / onPress簡単、プラットフォーム間の互換性、視覚的フィードバック-
TouchableWithoutFeedback視覚的なフィードバックが不要、または自分で実装したい場合。onPressOut / onPress簡単、カスタマイズ性(フィードバック部分)視覚的フィードバックがデフォルトでない
Pressable最新の推奨API。より柔軟なプレスイベントとスタイリング。onPressOut / onPress高機能、柔軟なスタイリング、将来性React Native 0.63以降で利用可能
PanResponderドラッグ、スワイプなど、複雑なカスタムジェスチャー。onPanResponderReleaseあらゆる複雑なジェスチャーに対応実装が複雑、オーバーキルになることが多い