This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

ジェスチャーを追加する

ページを編集

このチュートリアルでは、React Native アプリで React Native Gesture Handler を使ってダブルタップとドラッグのジェスチャーを追加し、Reanimated でアニメーションさせる方法を学びます。


ジェスチャーは、アプリに直感的なユーザー体験をもたらす優れた手段です。React Native Gesture Handler ライブラリは、ジェスチャーを処理できるネイティブコンポーネントを組み込みで提供します。プラットフォームのネイティブなタッチ処理システムを使って、パン、タップ、回転などのジェスチャーを認識します。この章では、このライブラリを使って 2 種類のジェスチャーを追加します。

  • ダブルタップで絵文字ステッカーのサイズを拡大し、もう一度ダブルタップすると元のサイズに戻します。
  • パンで絵文字ステッカーを移動させ、画像のどこにでもステッカーを配置できるようにします。ステッカーは画像の内側にとどまります。

また、ジェスチャーの状態間をアニメーションさせるために Reanimated ライブラリも使用します。デフォルトのテンプレートには、両方のライブラリがすでに含まれています。

視聴:ユニバーサル Expo アプリにジェスチャーを追加する
視聴:ユニバーサル Expo アプリにジェスチャーを追加する

React Native Gesture Handler と Reanimated を使って、絵文字ステッカーにダブルタップとパンのジェスチャーを追加します。


1

GestureHandlerRootView を追加する

アプリ内でジェスチャー操作を機能させるために、アプリのルートに react-native-gesture-handler の <GestureHandlerRootView> をレンダリングします。ジェスチャーは、このビューの内側でのみ認識されます。src/app/_layout.tsx で、<Stack> を <GestureHandlerRootView> でラップします。

src/app/_layout.tsx
import { Stack } from 'expo-router'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; export default function RootLayout() { return ( <GestureHandlerRootView> <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> </Stack> </GestureHandlerRootView> ); }

デフォルトで <GestureHandlerRootView> には flex: 1 のスタイルが設定されているため、画面全体に広がります。

2

アニメーション対応コンポーネントを使用する

Animated コンポーネントは、コンポーネントの style プロパティを参照してアニメーションさせる値を判断し、更新を適用してアニメーションを作成します。Reanimated は <Animated.View>、<Animated.Text>、<Animated.ScrollView> などのアニメーション対応コンポーネントをエクスポートしています。ここでは、絵文字ステッカーをアニメーションさせるために <Animated.View> を使用します。

また、ステッカーは画像の上にとどまる必要があります。前の章では、負の top 値を使ってステッカーを画像の上に配置しました。代わりに、画像を基準にしてステッカーを配置しましょう。

  1. src/app/(tabs)/index.tsx で、ImageViewer と EmojiSticker コンポーネントを <View> でラップします。この <View> は画像と同じサイズになります。
  2. src/components ディレクトリ内の emoji-sticker.tsx ファイルを開きます。アニメーション対応コンポーネントを使うために、react-native-reanimated ライブラリから Animated をインポートします。
  3. <View> コンポーネントを <Animated.View> に置き換え、position: 'absolute' を使って画像の左上隅に配置します。Image は <Animated.View> 全体に広がります。
src/app/(tabs)/index.tsx
// ...rest of the import statements remain same export default function Index() { // ...rest of the code remains same return ( <SafeAreaView style={styles.container}> <View style={styles.imageContainer}> <View> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {/* ...rest of the code remains same */} </SafeAreaView> ); }
src/components/emoji-sticker.tsx
import { StyleSheet } from 'react-native'; import Animated from 'react-native-reanimated'; import { Image, type ImageSource } from 'expo-image'; type Props = { imageSize: number; stickerSource: ImageSource; }; export default function EmojiSticker({ imageSize, stickerSource }: Props) { return ( <Animated.View style={[styles.sticker, { width: imageSize, height: imageSize }]}> <Image source={stickerSource} style={styles.image} /> </Animated.View> ); } const styles = StyleSheet.create({ sticker: { position: 'absolute', top: 0, left: 0, }, image: { width: '100%', height: '100%', }, });

3

タップジェスチャーを追加する

React Native Gesture Handler を使うと、ダブルタップイベントなどのタッチ入力を検出したときの動作を追加できます。

src/components/emoji-sticker.tsx ファイル内で:

  1. react-native-gesture-handler から GestureDetector と useTapGesture をインポートします。
  2. ステッカーのタップを認識するために、react-native-reanimated から useAnimatedStyle、useSharedValue、withSpring をインポートし、<Animated.View> のスタイルをアニメーションさせます。
  3. EmojiSticker コンポーネント内で、useSharedValue() フックを使って scaleImage という参照を作成します。初期値には imageSize の値を使用します。
src/components/emoji-sticker.tsx
// ...rest of the import statements remain same import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; export default function EmojiSticker({ imageSize, stickerSource }: Props) { const scaleImage = useSharedValue(imageSize); return ( // ...rest of the code remains same ) }

useSharedValue() フックを使って共有値を作成すると、多くの利点があります。データを変更したり、現在の値に基づいてアニメーションを実行したりするのに役立ちます。共有値は get() メソッドで読み取り、set() メソッドで変更します。デフォルトのテンプレートでは React Compiler が有効になっており、React Compiler を使う場合、Reanimated は value プロパティの代わりに get() と set() を使うことを推奨しています。

useTapGesture() フックを使って doubleTap ジェスチャーを作成します。numberOfTaps オプションで必要なタップ回数を指定し、onActivate コールバックはジェスチャーが認識されたときに実行されます。このコールバックは、ステッカーのサイズを 2 倍にするか、初期サイズに戻します。

EmojiSticker コンポーネント内に次のジェスチャーを作成します。

src/components/emoji-sticker.tsx
const doubleTap = useTapGesture({ numberOfTaps: 2, onActivate: () => { scaleImage.set(scaleImage.get() === imageSize ? imageSize * 2 : imageSize); }, });

アニメーションさせるために、スプリングベースのアニメーションを使用します。実世界のスプリングの物理に基づいているため、生き生きとした感覚になります。react-native-reanimated が提供する withSpring() 関数を使用します。

ステッカーでは、useAnimatedStyle() フックを使ってスタイルオブジェクトを作成します。これにより、アニメーション時に共有値を使ってスタイルを更新できます。さらに、width と height プロパティを操作してステッカーのサイズをスケーリングします。これらのプロパティの初期値は imageSize に設定されています。

stickerStyle 変数を作成し、EmojiSticker コンポーネントに追加します。

src/components/emoji-sticker.tsx
const stickerStyle = useAnimatedStyle(() => { return { width: withSpring(scaleImage.get()), height: withSpring(scaleImage.get()), }; });

次に、<Animated.View> コンポーネントを <GestureDetector> でラップし、<Animated.View> の style プロパティにあるサイズを stickerStyle に置き換えます。

src/components/emoji-sticker.tsx
import { StyleSheet } from 'react-native'; import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; import { Image, type ImageSource } from 'expo-image'; type Props = { imageSize: number; stickerSource: ImageSource; }; export default function EmojiSticker({ imageSize, stickerSource }: Props) { const scaleImage = useSharedValue(imageSize); const doubleTap = useTapGesture({ numberOfTaps: 2, onActivate: () => { scaleImage.set(scaleImage.get() === imageSize ? imageSize * 2 : imageSize); }, }); const stickerStyle = useAnimatedStyle(() => { return { width: withSpring(scaleImage.get()), height: withSpring(scaleImage.get()), }; }); return ( <GestureDetector gesture={doubleTap}> <Animated.View style={[styles.sticker, stickerStyle]}> <Image source={stickerSource} style={styles.image} /> </Animated.View> </GestureDetector> ); } const styles = StyleSheet.create({ sticker: { position: 'absolute', top: 0, left: 0, }, image: { width: '100%', height: '100%', }, });

上記のスニペットでは、gesture プロパティが doubleTap の値を受け取り、ユーザーがステッカーをダブルタップしたときにジェスチャーを発動します。

Android、iOS、web でアプリを確認してみましょう。

4

パンジェスチャーを追加する

ステッカー上のドラッグジェスチャーを認識し、その動きを追跡するために、パンジェスチャーを使用します。ステッカーは画像の内側にとどまる必要があるため、位置も制限します。src/components/emoji-sticker.tsx ファイルで:

  1. 新しい共有値 translateX と translateY を 2 つ作成します。これらはステッカーの位置を保持します。初期値の 0 により、ステッカーは画像の左上隅にとどまります。
  2. usePanGesture() フックを使って drag ジェスチャーを作成します。その onUpdate コールバックは、指が動いている間に実行されます。event の changeX と changeY プロパティは、前回のイベントからの位置変化量を保持します。
  3. 新しい位置をそれぞれ Reanimated の clamp() 関数に渡します。この関数は、値を最小値と最大値の間に制限します。image-viewer.tsx で設定したとおり、画像の幅は 320、高さは 440 です。位置の最大値は、画像のサイズからステッカーのサイズを引いた値です。
  4. doubleTap ジェスチャーでは、サイズが変わった後に位置を制限します。これにより、端の近くでサイズが 2 倍になったステッカーも画像の内側にとどまります。
  5. useSimultaneousGestures() フックで両方のジェスチャーを組み合わせ、ステッカーが両方のジェスチャーを同時に認識できるようにします。
  6. stickerStyle に transform プロパティを追加し、translateX と translateY の値でステッカーを移動させます。
  7. Image の pointerEvents を 'none' に設定します。web では、ユーザーが画像を押してポインターを動かすと、ブラウザが独自のドラッグを開始し、パンジェスチャーがキャンセルされます。このスタイルにより、代わりに <Animated.View> がポインターイベントを受け取ります。
src/components/emoji-sticker.tsx
import { StyleSheet } from 'react-native'; import { GestureDetector, usePanGesture, useSimultaneousGestures, useTapGesture, } from 'react-native-gesture-handler'; import Animated, { clamp, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; import { Image, type ImageSource } from 'expo-image'; type Props = { imageSize: number; stickerSource: ImageSource; }; const IMAGE_WIDTH = 320; const IMAGE_HEIGHT = 440; export default function EmojiSticker({ imageSize, stickerSource }: Props) { const scaleImage = useSharedValue(imageSize); const translateX = useSharedValue(0); const translateY = useSharedValue(0); const doubleTap = useTapGesture({ numberOfTaps: 2, onActivate: () => { const size = scaleImage.get() === imageSize ? imageSize * 2 : imageSize; scaleImage.set(size); translateX.set(clamp(translateX.get(), 0, IMAGE_WIDTH - size)); translateY.set(clamp(translateY.get(), 0, IMAGE_HEIGHT - size)); }, }); const drag = usePanGesture({ onUpdate: event => { translateX.set(clamp(translateX.get() + event.changeX, 0, IMAGE_WIDTH - scaleImage.get())); translateY.set(clamp(translateY.get() + event.changeY, 0, IMAGE_HEIGHT - scaleImage.get())); }, }); const gesture = useSimultaneousGestures(drag, doubleTap); const stickerStyle = useAnimatedStyle(() => { return { width: withSpring(scaleImage.get()), height: withSpring(scaleImage.get()), transform: [{ translateX: translateX.get() }, { translateY: translateY.get() }], }; }); return ( <GestureDetector gesture={gesture}> <Animated.View style={[styles.sticker, stickerStyle]}> <Image source={stickerSource} style={styles.image} /> </Animated.View> </GestureDetector> ); } const styles = StyleSheet.create({ sticker: { position: 'absolute', top: 0, left: 0, }, image: { width: '100%', height: '100%', pointerEvents: 'none', }, });

Android、iOS、web でアプリを確認してみましょう。

まとめ

第 6 章:ジェスチャーを追加する

パンとタップのジェスチャーの実装が完了しました。

次の章では、画像とステッカーのスクリーンショットを撮影し、デバイスのライブラリに保存する方法を学びます。

次へ:スクリーンショットを撮影する