ReactNative Expoでアプリ開発入門してみる Part4

## 参考

ジェスチャーの実装に入っていきます。

https://docs.expo.dev/tutorial/gestures/


## ライブラリのインストール

ReactNativeGestureHandlerをインストールします。
また、ジェスチャー中のアニメーションを実装するために、react-native-reanimatedもインストールします。

https://docs.swmansion.com/react-native-gesture-handler/docs/


```sh
$ npx expo install react-native-gesture-handler react-native-reanimated
$ npm install -D @babel/plugin-proposal-export-namespace-from 
```

ReanimatedのBabelプラグインをbabel.config.jsに追加します。

```babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      "@babel/plugin-proposal-export-namespace-from",
      "react-native-reanimated/plugin",
    ],
  };
};

```

開発サーバーを再起動します。
babel.config.jsの変更を反映させるために、-cをつけて起動しています。

```sh
$ npx expo start -c
```


アプリ内でジェスチャを動かすには、アプリの最上位にラップする必要があります。
App.jsに追加します。

```App.js
import { GestureHandlerRootView } from "react-native-gesture-handler"; 

export default function App() {
  return (
    <GestureHandlerRootView style={styles.container}> 
    /* ...rest of the code remains */
    </GestureHandlerRootView>
  )
}

```

### コンポーネントをアニメーション化する

EmojiStickerをアニメーション化します。
ImageComponentをAnimated.Imageに修正します。

```EmojiSticker.js
import Animated from 'react-native-reanimated';

  <Animated.Image
    source={stickerSource}
    resizeMode="contain"
    style={{ width: imageSize, height: imageSize }}
  />
```

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

useSharedValueで、アニメーションに使う値を設定します。
今回は画像サイズをアニメーションに使います。

```js
const scaleImage = useSharedValue(imageSize);
```

doubleTap時の処理を追加します。
下記のコードでは、２回タップされたら、画像のサイズを２倍にする処理になっています。
```js
const doubleTap = Gesture.Tap()
  .numberOfTaps(2)
  .onStart(() => {
    if (scaleImage.value !== imageSize * 2) {
      scaleImage.value = scaleImage.value * 2;
    }
  });
```

useAnimatedStyle()で、アニメーションに使うStyleを設定します。
```js
const imageStyle = useAnimatedStyle(() => {
  return {
    width: withSpring(scaleImage.value),
    height: withSpring(scaleImage.value),
  };
});
```

下記２点を行います。
- GestureDetectorのgesturePropsへ、doubleTap時の処理を渡す。
- Animated.ImageのStyleへ、useAnimatedStyle()で作成したimageStyleを追加する。

```jsx
<GestureDetector gesture={doubleTap}>
    <Animated.Image
      source={stickerSource}
      resizeMode="contain"
      style={[imageStyle, { width: imageSize, height: imageSize }]}
    />
</GestureDetector>
```

すべてのコードは下記になります。

```jsx
import { View } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from "react-native-reanimated";

export default function EmojiSticker({ imageSize, stickerSource }) {
  const scaleImage = useSharedValue(imageSize);
  const doubleTap = Gesture.Tap()
    .numberOfTaps(2)
    .onStart(() => {
      if (scaleImage.value !== imageSize * 2) {
        scaleImage.value = scaleImage.value * 2;
      }
    });
  const imageStyle = useAnimatedStyle(() => {
    return {
      width: withSpring(scaleImage.value),
      height: withSpring(scaleImage.value),
    };
  });

  return (
    <View style={{ top: -350 }}>
      <GestureDetector gesture={doubleTap}>
        <Animated.Image
          source={stickerSource}
          resizeMode="contain"
          style={{ width: imageSize, height: imageSize }}
        />
      </GestureDetector>
    </View>
  );
}
```

この変更後、アイコンをダブルタップ(Webではダブルクリック)することで、画像がアニメーションしながら２倍サイズになります。


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

EmojiSticker.jsのViewComponentをAnimatedにします。

```diff_jsx
+ <Animated.View style={{ top: -350 }}>
  <GestureDetector gesture={doubleTap}>
    {/* ...rest of the code remains same */}
  </GestureDetector>
+ </Animated.View>
```

X,Y座標を保存する値を、`useSharedValue`で作成します。

```jsx
export default function EmojiSticker({ imageSize, stickerSource }) {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);

  // ...rest of the code remains same
}
```

ダブルタップの処理を追加したときと似たように、Panジェスチャーを実装します。
onChangeイベントのeventから、指の位置を取得し、先程作成した、translate変数へ入れる処理にします。

```
const drag = Gesture.Pan()
.onChange((event) => {
  translateX.value += event.changeX;
  translateY.value += event.changeY;
});
```

useAnimatedStyleで座標のスタイルを作成します。

```jsx
const containerStyle = useAnimatedStyle(() => {
  return {
    transform: [
      {
        translateX: translateX.value,
      },
      {
        translateY: translateY.value,
      },
    ],
  };
});
```

下記２点を行います。DoubleTapと同様です。
- GestureDetectorを再度追加し、drag関数を渡す。
- Animated.ViewのstyleへuseAnimatedStyleで作成したcontainerStyleを渡す。

```diff_jsx
+ <GestureDetector gesture={drag}>
+  <Animated.View style={[containerStyle, { top: -350 }]}>
    <GestureDetector gesture={doubleTap}>
      <Animated.Image
        source={stickerSource}
        resizeMode="contain"
        style={[imageStyle, { width: imageSize, height: imageSize }]}
      />
    </GestureDetector>
+  </Animated.View>
+ </GestureDetector>
```

上記でアイコンを好き場所にドラッグすることができるようになりました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3589058/d2af6091-a3d5-19f0-8b89-bb8972f19648.png)
![uploading...0]()