Text#onResponderTerminationRequest

2025-06-06

詳しく説明します。

ジェスチャーレスポンダーシステムとは?

React Nativeでは、ユーザーのタップ、スワイプ、ドラッグといった様々なタッチイベントをどのように処理するかを管理するために「ジェスチャーレスポンダーシステム」が導入されています。これは、どのコンポーネントが現在タッチイベントの「レスポンダー」(応答者)であるかを決定し、そのライフサイクルを管理する仕組みです。

onResponderTerminationRequest の役割

ある View(または Text などのTouchableなコンポーネント)が現在タッチイベントのレスポンダーであるとします。つまり、ユーザーがそのコンポーネントを触っていて、そのコンポーネントがそのタッチイベントを処理している状態です。

このとき、他のコンポーネント(例えば、親の ScrollView や別のTouchableな要素など)が「私にレスポンダーの役割を譲ってほしい」と要求してくることがあります。

onResponderTerminationRequest は、現在のレスポンダーであるコンポーネントに対して、「レスポンダーの役割を終了して、他のコンポーネントに明け渡しても良いか?」と尋ねるコールバック関数です。

  • この関数が false を返すと、現在のコンポーネントはレスポンダーの役割を保持し続け、他のコンポーネントに明け渡しません。
  • この関数が true を返すと、現在のコンポーネントはレスポンダーの役割を解放し、他のコンポーネントがレスポンダーになることを許可します。

具体的な使用例とシナリオ

例えば、ScrollView の中に Text コンポーネントがあり、その Text コンポーネントに onPress などのタッチイベントハンドラーが設定されている場合を考えます。

  1. ユーザーが Text をタップし始め、Text がレスポンダーになります。
  2. ユーザーが指を動かし、ScrollView がスクロールしたいと判断します。
  3. ScrollViewText に対して onResponderTerminationRequest を呼び出します。
    • もし TextonResponderTerminationRequesttrue を返すと、Text はレスポンダーを終了し、ScrollView がスクロールを開始します。
    • もし TextonResponderTerminationRequestfalse を返すと、Text はレスポンダーを保持し、ScrollView はスクロールを開始できません(これは一般的にはユーザー体験として望ましくない場合が多いです)。

Text コンポーネントはデフォルトで onPress などのタッチハンドラーを持つことができます。onResponderTerminationRequest は、Text がタッチイベントを処理している最中に、他の要素がタッチイベントの制御を要求してきた場合に、その要求を受け入れるかどうかを制御するのに役立ちます。



よくあるエラーと問題点

    • 問題
      ScrollView 内の Text コンポーネントがタッチイベントを処理している場合(例えば onPress が設定されている場合)、ユーザーがText の上で指を動かしても ScrollView がスクロールを開始しないことがあります。これは、Text がレスポンダーの役割を手放さないために起こります。
    • 原因
      TextonResponderTerminationRequestfalse を返しているか、デフォルトの挙動が期待通りでないため、親の ScrollView がレスポンダーになることを許可していない。
  1. ボタンやインタラクティブ要素のタップが効かない

    • 問題
      複数のタッチ可能なコンポーネントが重なっている場合(または隣接しているがタッチ領域が重複している場合)に、一部のコンポーネントのタップイベントが発火しないことがあります。
    • 原因
      あるコンポーネントがレスポンダーの役割を保持し続けてしまい、他のコンポーネントがレスポンダーになる機会を得られないため。特に、onResponderTerminationRequestfalse を返しているコンポーネントがある場合に顕著です。
  2. 複雑なジェスチャーが意図通りに動作しない

    • 問題
      ピンチズームやスワイプなどの複雑なジェスチャーを実装しようとしたときに、一部のジェスチャーが他のコンポーネントによって「奪われて」しまい、予期しない動作になることがあります。
    • 原因
      ジェスチャーハンドリングライブラリ(例: react-native-gesture-handler)を使用している場合でも、基盤となるジェスチャーレスポンダーシステムとの相互作用が正しく設定されていないため。onResponderTerminationRequest の挙動が、ジェスチャーハンドラーの意図と異なる場合に発生します。
  3. デバッグが難しい

    • 問題
      どのコンポーネントが現在レスポンダーになっているのか、なぜレスポンダーが切り替わらないのかを特定するのが難しい。
    • 原因
      ジェスチャーレスポンダーシステムは内部的な仕組みであり、その状態を直接視覚的に確認するツールが限られているため。
  1. onResponderTerminationRequest の返り値を確認する

    • ほとんどの場合、onResponderTerminationRequesttrue を返すようにしておくと、他のコンポーネントがレスポンダーになることを許可するため、柔軟なインタラクションが可能です。
    • 明示的に false を返している箇所がないか、コードを確認してください。特定の理由がない限り、false を返すのは避けるべきです。

    • <Text
        onPress={() => console.log('Text pressed')}
        onResponderTerminationRequest={() => true} // これがデフォルトで、ほとんどの場合問題ない
      >
        タップ可能なテキスト
      </Text>
      
  2. タッチイベントのライフサイクルを理解する

    • onStartShouldSetResponderonMoveShouldSetResponder など、レスポンダーになるための他のプロパティとの関連性を理解することが重要です。
    • onResponderTerminationRequest は、既にレスポンダーになっているコンポーネントが、その役割を終了して良いかを問われた際に呼び出されるという点を再確認してください。
  3. console.log を活用してデバッグする

    • 各タッチイベントハンドラー(onPressIn, onPressOut, onResponderGrant, onResponderRelease, onResponderTerminationRequest など)の中に console.log を仕込み、イベントの発生順序や onResponderTerminationRequest の返り値を確認します。
    • これにより、どのコンポーネントがいつレスポンダーになり、いつその役割を放棄しようとしているのかを把握できます。
  4. コンポーネントの階層とz-indexを確認する

    • 重なっているコンポーネントがある場合、z-indexや描画順がタッチイベントの処理に影響を与えることがあります。onResponderTerminationRequest は直接的な原因ではないかもしれませんが、コンポーネントの配置が原因で予期せぬタッチの奪い合いが起きている可能性も考慮します。
  5. react-native-gesture-handler の利用を検討する

    • 複雑なジェスチャー(ピンチ、スワイプ、パンなど)を扱う場合や、コンポーネント間のタッチイベントの競合をより細かく制御したい場合は、react-native-gesture-handler ライブラリの導入を強く推奨します。
    • このライブラリは、React Nativeのネイティブなジェスチャーシステムをより高度に抽象化し、競合解決の仕組みを提供します。onResponderTerminationRequest のような低レベルなプロパティを直接操作するよりも、このライブラリが提供するコンポーネント(PanGestureHandler など)を使う方が、多くのケースで問題が解決しやすくなります。
  6. 簡素化されたテストケースを作成する

    • 問題が発生している画面全体ではなく、問題の原因となっていると思われるコンポーネントとその周辺の最小限のコードで新しいコンポーネントを作成し、挙動を再現してみてください。これにより、問題を切り分けやすくなります。


具体的なプログラミング例を通じて、その挙動を理解しましょう。

基本的な例:Text がレスポンダーの役割を譲る場合(デフォルトの挙動)

ほとんどの場合、onResponderTerminationRequesttrueを返すように設定されており、これは他のコンポーネント(特に親のScrollViewなど)がタッチイベントの制御を求めたときに、現在のTextコンポーネントがそれを快く譲ることを意味します。

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

const TerminationRequestExample = () => {
  const [log, setLog] = useState([]);

  const addLog = (message) => {
    setLog(prevLog => [...prevLog, message]);
  };

  return (
    <View style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.header}>スクロール可能な領域</Text>
        <Text
          style={styles.touchableText}
          onPressIn={() => addLog('Text: onPressIn (レスポンダーになることを要求)')}
          onResponderGrant={() => addLog('Text: onResponderGrant (レスポンダーになりました)')}
          onResponderRelease={() => addLog('Text: onResponderRelease (タップ終了)')}
          // デフォルトの挙動: trueを返す
          onResponderTerminationRequest={() => {
            addLog('Text: onResponderTerminationRequest (他の要素がレスポンダーを要求)');
            return true; // レスポンダーを譲ることを許可
          }}
          onResponderTerminate={() => addLog('Text: onResponderTerminate (レスポンダーが奪われました)')}
        >
          このテキストをタップして指を上下に動かしてください。
          通常、ScrollViewがスクロールを開始します。
          {'\n\n'}
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
        </Text>
        {Array.from({ length: 20 }).map((_, i) => (
          <Text key={i} style={styles.fillerText}>
            追加のスクロール可能なコンテンツ {i + 1}
          </Text>
        ))}
      </ScrollView>

      <View style={styles.logContainer}>
        <Text style={styles.logHeader}>イベントログ:</Text>
        <ScrollView style={styles.logScrollView}>
          {log.map((entry, index) => (
            <Text key={index} style={styles.logText}>{entry}</Text>
          ))}
        </ScrollView>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 50,
  },
  scrollView: {
    flex: 1,
    backgroundColor: '#f0f0f0',
    padding: 10,
  },
  header: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  touchableText: {
    fontSize: 16,
    padding: 15,
    backgroundColor: '#e0ffe0',
    borderWidth: 1,
    borderColor: 'green',
    marginBottom: 20,
  },
  fillerText: {
    fontSize: 14,
    padding: 5,
    marginVertical: 2,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#cccccc',
  },
  logContainer: {
    height: 200,
    backgroundColor: '#333',
    padding: 10,
  },
  logHeader: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#fff',
    marginBottom: 5,
  },
  logScrollView: {
    flex: 1,
  },
  logText: {
    fontSize: 12,
    color: '#eee',
  },
});

export default TerminationRequestExample;

解説

  1. touchableText をタップ
    • onPressIn が発火し、Text がレスポンダーになることを要求します。
    • onResponderGrant が発火し、Text がレスポンダーになります。
  2. 指を上下にドラッグ
    • ScrollView がスクロールの意思を検知し、Text コンポーネントに「レスポンダーを譲ってほしい」と要求します。
    • TextonResponderTerminationRequest が呼び出されます。この例では true を返すため、Text はレスポンダーの役割を解放します。
    • onResponderTerminate が発火し、Text がレスポンダーではなくなったことを示します。
    • ScrollView がレスポンダーになり、スクロールが開始されます。

ここでは、onResponderTerminationRequestfalse を返すように設定し、Text がレスポンダーの役割を他のコンポーネント(ScrollView)に譲ることを拒否するケースを示します。

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

const RejectTerminationExample = () => {
  const [log, setLog] = useState([]);

  const addLog = (message) => {
    setLog(prevLog => [...prevLog, message]);
  };

  return (
    <View style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.header}>レスポンダー拒否の例</Text>
        <Text
          style={[styles.touchableText, { backgroundColor: '#ffffe0', borderColor: 'orange' }]}
          onPressIn={() => addLog('Text: onPressIn')}
          onResponderGrant={() => addLog('Text: onResponderGrant (レスポンダーになりました)')}
          onResponderRelease={() => addLog('Text: onResponderRelease (タップ終了)')}
          // !!! レスポンダーを譲ることを拒否する !!!
          onResponderTerminationRequest={() => {
            addLog('Text: onResponderTerminationRequest (他の要素がレスポンダーを要求) -> falseを返します');
            Alert.alert('拒否', 'このテキストがレスポンダーを保持しています。');
            return false; // レスポンダーを譲ることを拒否
          }}
          onResponderTerminate={() => addLog('Text: onResponderTerminate (レスポンダーが奪われました - これは呼ばれません)')}
        >
          このテキストをタップして指を上下に動かしてください。
          ScrollViewはスクロールを開始しないはずです。
          {'\n\n'}
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
          長いテキストを置いて、スクロールとタップの競合を試します。
        </Text>
        {Array.from({ length: 20 }).map((_, i) => (
          <Text key={i} style={styles.fillerText}>
            追加のスクロール可能なコンテンツ {i + 1}
          </Text>
        ))}
      </ScrollView>

      <View style={styles.logContainer}>
        <Text style={styles.logHeader}>イベントログ:</Text>
        <ScrollView style={styles.logScrollView}>
          {log.map((entry, index) => (
            <Text key={index} style={styles.logText}>{entry}</Text>
          ))}
        </ScrollView>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 50,
  },
  scrollView: {
    flex: 1,
    backgroundColor: '#f0f0f0',
    padding: 10,
  },
  header: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  touchableText: {
    fontSize: 16,
    padding: 15,
    backgroundColor: '#e0ffe0',
    borderWidth: 1,
    borderColor: 'green',
    marginBottom: 20,
  },
  fillerText: {
    fontSize: 14,
    padding: 5,
    marginVertical: 2,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#cccccc',
  },
  logContainer: {
    height: 200,
    backgroundColor: '#333',
    padding: 10,
  },
  logHeader: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#fff',
    marginBottom: 5,
  },
  logScrollView: {
    flex: 1,
  },
  logText: {
    fontSize: 12,
    color: '#eee',
  },
});

export default RejectTerminationExample;
  1. touchableText をタップ
    • onPressIn が発火し、Text がレスポンダーになることを要求します。
    • onResponderGrant が発火し、Text がレスポンダーになります。
  2. 指を上下にドラッグ
    • ScrollView がスクロールの意思を検知し、Text コンポーネントに「レスポンダーを譲ってほしい」と要求します。
    • TextonResponderTerminationRequest が呼び出されますが、この例では false を返すため、Text はレスポンダーの役割を保持し続けます。
    • 結果として、ScrollView はスクロールを開始できず、onResponderTerminate は呼び出されません(レスポンダーが奪われないため)。
  • PanResponder の利用
    複雑なジェスチャーや複数の要素間でのタッチイベントの競合をより細かく制御したい場合は、PanResponder APIを検討してください。PanResponderonResponderTerminationRequestを含む、より包括的なジェスチャーレスポンダーのライフサイクルを提供します。
  • false を返すのは慎重に
    false を返すことは、そのコンポーネントが「絶対にタッチイベントの制御を譲らない」ことを意味します。これは、特定のドラッグ操作など、コンポーネントがタッチイベントを完全に制御し続ける必要がある場合にのみ使用すべきです。そうしないと、ユーザー体験を損なう可能性があります。
  • デフォルトは true
    ほとんどのTouchableコンポーネントやTextコンポーネントは、onResponderTerminationRequestが内部的にtrueを返すように設定されています。これにより、ScrollViewなどの親コンポーネントがスムーズにタッチイベントを奪うことができます。


onResponderTerminationRequest の代替方法というよりは、ジェスチャーレスポンダーシステムやタッチイベントの制御をより効果的に行うための、より高レベルな方法や推奨されるアプローチとして以下のものがあります。

Pressable コンポーネント (推奨)

React Native 0.63 から導入された Pressable は、Touchable 系のコンポーネント(TouchableOpacity, TouchableHighlight など)に代わる、より柔軟で高機能なコンポーネントです。onResponderTerminationRequest のような低レベルなイベントを直接扱う必要なく、より豊かなインタラクションを実現できます。

特徴

  • より安定した挙動
    ジェスチャーレスポンダーシステムとの連携が最適化されています。
  • hitSlop
    タップ可能な領域を視覚的なサイズとは別に拡張できます。
  • カスタムフィードバック
    Touchable 系のコンポーネントのような組み込みのフィードバック(透明度の変化、背景色の変化など)だけでなく、より複雑なアニメーションや視覚効果を自由に実装できます。
  • より詳細な状態
    pressed 状態をコールバックで受け取れるため、押されている間とそうでない間のスタイルを簡単に切り替えられます。

Text コンポーネントにタッチイベントを付与する代替として
Text コンポーネント自体には onPress などの基本的なタッチハンドラーがありますが、より高度なインタラクションが必要な場合は、TextPressable で囲むのが一般的です。


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

const PressableExample = () => {
  const [timesPressed, setTimesPressed] = useState(0);

  return (
    <View style={styles.container}>
      <Pressable
        onPress={() => {
          setTimesPressed((current) => current + 1);
        }}
        // 押されている間のスタイルを動的に変更
        style={({ pressed }) => [
          styles.pressableText,
          {
            backgroundColor: pressed ? '#d0f0d0' : '#e0ffe0',
            borderColor: pressed ? 'darkgreen' : 'green',
          },
        ]}
      >
        <Text style={styles.textInsidePressable}>
          Pressable なテキストをタップしてください!
        </Text>
        <Text style={styles.pressCount}>
          タップ回数: {timesPressed}
        </Text>
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  pressableText: {
    padding: 15,
    borderRadius: 8,
    borderWidth: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  textInsidePressable: {
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  pressCount: {
    marginTop: 10,
    fontSize: 14,
    color: '#666',
  },
});

export default PressableExample;

react-native-gesture-handler (複雑なジェスチャーや競合解決に最適)

ピンチ、パン、スワイプ、長押しなど、より複雑なジェスチャーを実装したり、複数のジェスチャーが競合する状況を管理したりする場合、React Nativeに組み込まれているジェスチャーレスポンダーシステムよりも、 ライブラリの使用が強く推奨されます。

特徴

  • 豊富なジェスチャータイプ
    PanGestureHandler (ドラッグ), TapGestureHandler (タップ), LongPressGestureHandler (長押し), PinchGestureHandler (ピンチ), RotationGestureHandler (回転) など、多くのプリセットジェスチャーが用意されています。
  • ジェスチャー間の関係性
    複数のジェスチャーハンドラー間で優先順位や相互排他を簡単に設定できます(例: 「スクロール中にタップは無効にする」など)。
  • 宣言的なAPI
    ジェスチャーの定義と競合解決をより宣言的に記述できます。
  • ネイティブスレッドでの処理
    ジェスチャー処理をJavaScriptスレッドではなくネイティブUIスレッドで行うため、パフォーマンスが向上し、UIがより滑らかに感じられます。

Text をドラッグ可能にする例(PanGestureHandler を使用)
この場合、Text コンポーネント自体に直接 onResponderTerminationRequest を設定する代わりに、TextPanGestureHandler でラップし、そのハンドラーのイベントを処理します。

import React, { useRef } from 'react';
import { View, Text, StyleSheet, Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';

const GestureHandlerExample = () => {
  const translateX = useRef(new Animated.Value(0)).current;
  const translateY = useRef(new Animated.Value(0)).current;

  const onGestureEvent = Animated.event(
    [{
      nativeEvent: {
        translationX: translateX,
        translationY: translateY,
      },
    }],
    { useNativeDriver: true }
  );

  const onHandlerStateChange = ({ nativeEvent }) => {
    if (nativeEvent.state === State.END) {
      // ジェスチャーが終了したら、現在の位置をオフセットとして保持
      translateX.extractOffset();
      translateY.extractOffset();
    }
  };

  return (
    <View style={styles.container}>
      <PanGestureHandler
        onGestureEvent={onGestureEvent}
        onHandlerStateChange={onHandlerStateChange}
      >
        <Animated.View
          style={[
            styles.draggableTextContainer,
            {
              transform: [
                { translateX: translateX },
                { translateY: translateY },
              ],
            },
          ]}
        >
          <Text style={styles.draggableText}>
            このテキストをドラッグしてください
          </Text>
        </Animated.View>
      </PanGestureHandler>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  draggableTextContainer: {
    padding: 20,
    backgroundColor: '#add8e6', // 薄い水色
    borderRadius: 10,
    borderWidth: 1,
    borderColor: 'blue',
  },
  draggableText: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#333',
  },
});

export default GestureHandlerExample;

解説
この例では、PanGestureHandler がタッチイベントを直接処理し、Animated.Value を使用してTextコンポーネントの表示位置をネイティブスレッドで更新しています。onResponderTerminationRequestのような低レベルなAPIは意識せずに、複雑なドラッグジェスチャーを実装できます。

PanResponder は、React Nativeに組み込まれているジェスチャーレスポンダーシステムを直接操作するためのAPIです。onResponderTerminationRequest も含め、タッチイベントのライフサイクル全体を自分で管理したい場合に利用します。これは非常に強力ですが、その分複雑さも増します。

特徴

  • 柔軟性
    あらゆるカスタムジェスチャーをゼロから作成できます。
  • シングルタッチとマルチタッチ
    基本的なマルチタッチジェスチャーも自力で実装できます。
  • 低レベルな制御
    onStartShouldSetResponder, onMoveShouldSetResponder, onResponderGrant, onResponderMove, onResponderRelease, onResponderTerminationRequest, onResponderTerminate など、レスポンダーの全ライフサイクルイベントをハンドリングできます。

使用場面

  • 既存のTouchableコンポーネントやScrollViewとの競合を厳密に制御したい場合。
  • react-native-gesture-handler では対応できない、非常に特殊なジェスチャーロジックが必要な場合。
import React, { useRef } from 'react';
import { View, Text, PanResponder, StyleSheet, Animated } from 'react-native';

const PanResponderExample = () => {
  const pan = useRef(new Animated.ValueXY()).current; // ドラッグ位置を保持

  const panResponder = useRef(
    PanResponder.create({
      // ユーザーがタッチを開始したときに、このコンポーネントがレスポンダーになるべきか?
      onStartShouldSetPanResponder: () => true,
      // ユーザーが指を動かしたときに、このコンポーネントがレスポンダーになるべきか?
      onMoveShouldSetPanResponder: () => true,

      // レスポンダーになったときに呼び出される
      onPanResponderGrant: (evt, gestureState) => {
        console.log('PanResponder: レスポンダーになりました');
        // 現在のレイアウトオフセットを保持し、アニメーションを開始
        pan.setOffset({ x: pan.x._value, y: pan.y._value });
        pan.setValue({ x: 0, y: 0 }); // オフセットを0にリセットして、相対的な移動を計算
      },

      // 指が動いている間呼び出される
      onPanResponderMove: Animated.event(
        [
          null,
          { dx: pan.x, dy: pan.y } // x, y の値を更新
        ],
        { useNativeDriver: false } // ここでは例としてfalseにしています
      ),

      // 他のコンポーネントがレスポンダーを要求したときに呼び出される
      onPanResponderTerminationRequest: (evt, gestureState) => {
        console.log('PanResponder: レスポンダー終了要求を受け取りました');
        // 必要に応じてtrue/falseを返す
        return true; // デフォルトでは譲ることを許可
      },

      // レスポンダーが他のコンポーネントに奪われたときに呼び出される
      onPanResponderTerminate: (evt, gestureState) => {
        console.log('PanResponder: レスポンダーが奪われました');
        // オフセットをクリア
        pan.extractOffset();
      },

      // タッチが終了したときに呼び出される
      onPanResponderRelease: (evt, gestureState) => {
        console.log('PanResponder: タッチが終了しました');
        // オフセットをクリア
        pan.extractOffset();
      },
    })
  ).current;

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  draggableBox: {
    width: 150,
    height: 100,
    backgroundColor: 'lightblue',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
    borderWidth: 1,
    borderColor: 'blue',
  },
  draggableText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#333',
  },
});

export default PanResponderExample;