React Native: Text#onMoveShouldSetResponder徹底解説 - ジェスチャー制御の基本
もう少し詳しく説明します。
React Native では、タッチイベントは「レスポンダーシステム」という仕組みで管理されています。ユーザーが画面に触れると、どのコンポーネントがそのタッチイベントに応答すべきかをシステムが判断します。
onMoveShouldSetResponder
は、以下のような挙動をします。
- タッチが開始され、指が移動したとき
ユーザーが画面に触れ、その指が移動(ドラッグなど)し始めたときに、Text
コンポーネントに対してこの関数が呼び出されます。 - 真偽値の返却
この関数はtrue
またはfalse
を返します。- true を返した場合
そのText
コンポーネントが「レスポンダー」になるべきだとシステムに伝えます。レスポンダーになると、その後の指の移動(onResponderMove
)、指が離れた(onResponderRelease
)、タッチがキャンセルされた(onResponderTerminate
)などのイベントをそのコンポーネントが受け取れるようになります。これにより、テキストのドラッグ選択や、テキストを使ったジェスチャーなどを実装できます。 - false を返した場合
そのText
コンポーネントはレスポンダーになるべきではないとシステムに伝えます。この場合、そのタッチイベントは親コンポーネントや他の兄弟コンポーネントに伝播され、他のコンポーネントがレスポンダーになる機会を得ます。
- true を返した場合
- イベントオブジェクトの引数
この関数には、タッチイベントに関する情報を含むイベントオブジェクトが引数として渡されます。これを使って、指の移動量や現在の位置などを確認し、レスポンダーになるべきかをより詳細に判断できます。
なぜこれが必要なのか?
すべてのタッチイベントをすべてのコンポーネントが処理する必要はありません。例えば、テキストをタップしてリンクに飛ぶ場合は 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
コンポーネントが onMoveShouldSetResponder
で true
を返しているため、指が動くとこの Text
コンポーネントがレスポンダーになり、その後の onResponderMove
などのイベントを受け取ることができます。
イベントが発火しない、または期待通りに動作しない
エラー/症状
- テキストのドラッグや選択ができない。
onResponderMove
やonResponderRelease
などの後続のレスポンダーイベントが発火しない。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 のレスポンダーシステムは、原則として一つのコンポーネントのみがアクティブなレスポンダーになれます。もし親コンポーネントや兄弟コンポーネントにonStartShouldSetResponder
やonMoveShouldSetResponder
が設定されており、それが先にtrue
を返した場合、あなたのText
コンポーネントはレスポンダーになれません。 - 解決策
- 階層を遡って、親や兄弟コンポーネントに設定されているレスポンダー関連のプロパティを確認します。
- 特に
ScrollView
やFlatList
のようなスクロール可能なコンポーネントは、内部でタッチイベントを処理するため、干渉しやすいです。これらのコンポーネント内で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>
- 説明
デバッグのヒント
-
シンプルな例で切り分ける
- 複雑なコンポーネントの中で問題が発生している場合、
Text
とonMoveShouldSetResponder
だけを持つ最もシンプルなコンポーネントを作成し、それが期待通りに動作するかどうかをテストします。これにより、問題が特定のコンポーネントのロジックにあるのか、それとも全体的な環境や他のコンポーネントとの競合にあるのかを切り分けることができます。
- 複雑なコンポーネントの中で問題が発生している場合、
-
React DevTools を使用する
- React DevTools を使って、コンポーネントのプロパティや状態が正しく更新されているかを確認します。
-
console.log を活用する
onMoveShouldSetResponder
、onResponderGrant
、onResponderMove
、onResponderRelease
、onResponderTerminationRequest
、onResponderTerminate
の各関数内で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.pageX
とevent.nativeEvent.pageY
は、画面全体の座標における現在の指の位置を示します。onResponderGrant
:Text
コンポーネントがタッチイベントのレスポンダーになった瞬間に呼び出されます。ここでは、レスポンダーになったことをログに記録しています。onMoveShouldSetResponder
: ユーザーが指を動かしたときに呼び出されます。ここでは、event.nativeEvent.dx
とevent.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
です。- この例では、
onMoveShouldSetResponder
がtrue
を返すことでテキストがレスポンダーになり、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
は、onMoveShouldSetResponder
やPanResponder
よりもさらに高レベルの抽象化を提供し、ネイティブのジェスチャー処理をJSスレッドから分離することで、よりスムーズな体験を実現します。ほとんどの新しいプロジェクトでは、ジェスチャーを扱う際に最初に検討すべき選択肢です。
-
組み込みのコンポーネントプロパティ(onPress など)
- テキストに対する単純なインタラクションであれば、
onMoveShouldSetResponder
のような複雑なジェスチャーハンドラは不要です。 - 特徴
Text
コンポーネントは、onPress
やonLongPress
といった基本的なタッチイベントプロパティをサポートしています。- 最もシンプルで直感的な方法です。
- 用途
- テキストのタップ(リンク、詳細表示など)
- テキストの長押し(コンテキストメニュー表示など)
- コード例
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
コンポーネント自体の移動ジェスチャーを細かく制御したい場合)で役立つが、直接使うことは稀。