🎉

【React Native Reanimated】Shared Element Transitions使ってみた

2023/03/04に公開

はじめに

こんにちは!
犬専用の音楽アプリ オトとりっぷでエンジニアしています、足立です!

https://www.oto-trip.com/

この記事では、React Native Reanimated v3で追加されたShared Element Transitionsを紹介します。
Shared Elementを検討している方の参考になれば幸いです。

目次

  • Shared Element Transitionsとは?
  • 導入方法
  • 活用方法

Shared Element Transitionsとは?

Shared Element TransitionsはReactとは、ある画面から別の画面に遷移するときに画面間で共通のコンポーネントが切れ目なく繋がる遷移のことです。
百聞は一見に如かず、React Native Reanimatedの公式Exampleから一例を紹介します。

画像を押下して画面遷移する時に、画像がニョキっと広がって画面遷移していると思います。
これが、Shared Element Transitionsです。

導入方法

まずはReact Nativeをinitし、React Navigationを導入します。

$ npx react-native@latest init AwesomeProject
$ cd AwesomeProject
$ yarn add @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context
$ npx pod-install

次にApp.tsxを以下の通りに書き換えます。

App.tsx
import {NavigationContainer} from '@react-navigation/native';
import type {NativeStackScreenProps} from '@react-navigation/native-stack';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import * as React from 'react';
import {Button, View} from 'react-native';

type RootStackParamList = {
  Screen1: undefined;
  Screen2: undefined;
};

const Stack = createNativeStackNavigator<RootStackParamList>();
type Screen1Props = NativeStackScreenProps<RootStackParamList, 'Screen1'>;
type Screen2Props = NativeStackScreenProps<RootStackParamList, 'Screen2'>;

function Screen1({navigation}: Screen1Props) {
  return (
    <View style={{flex: 1}}>
      <View style={{width: 150, height: 150, backgroundColor: 'green'}} />
      <Button title="Screen2" onPress={() => navigation.navigate('Screen2')} />
    </View>
  );
}

function Screen2({navigation}: Screen2Props) {
  return (
    <View style={{flex: 1, marginTop: 50}}>
      <View style={{width: 100, height: 100, backgroundColor: 'green'}} />
      <Button title="Screen1" onPress={() => navigation.navigate('Screen1')} />
    </View>
  );
}

export default function SharedElementExample() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: true}}>
        <Stack.Screen name="Screen1" component={Screen1} />
        <Stack.Screen name="Screen2" component={Screen2} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

ではアプリを起動させます。

$ npx react-native start

はい、無事にReact NavigationによるStack遷移が実装できました。

では次に、React Native Reanimatedを導入します。

$ yarn add react-native-reanimated
$ npx pod-install

babel.config.jsをちょこっと書き換えます。

babel.config.js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
+  plugins: ['react-native-reanimated/plugin'],
};

最後にApp.tsxをちょこっと書き換えます。

App.tsx
import {NavigationContainer} from '@react-navigation/native';
import type {NativeStackScreenProps} from '@react-navigation/native-stack';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import * as React from 'react';
import {Button, View} from 'react-native';
+ import Animated from 'react-native-reanimated';

type RootStackParamList = {
  Screen1: undefined;
  Screen2: undefined;
};

const Stack = createNativeStackNavigator<RootStackParamList>();
type Screen1Props = NativeStackScreenProps<RootStackParamList, 'Screen1'>;
type Screen2Props = NativeStackScreenProps<RootStackParamList, 'Screen2'>;

function Screen1({navigation}: Screen1Props) {
  return (
    <View style={{flex: 1}}>
-      <View style={{width: 150, height: 150, backgroundColor: 'green'}} />
+      <Animated.View
+        style={{width: 150, height: 150, backgroundColor: 'green'}}
+        sharedTransitionTag="sharedTag"
+      />
      <Button title="Screen2" onPress={() => navigation.navigate('Screen2')} />
    </View>
  );
}

function Screen2({navigation}: Screen2Props) {
  return (
    <View style={{flex: 1, marginTop: 50}}>
-      <View style={{width: 100, height: 100, backgroundColor: 'green'}} />
+      <Animated.View
+        style={{width: 100, height: 100, backgroundColor: 'green'}}
+        sharedTransitionTag="sharedTag"
+      />
      <Button title="Screen1" onPress={() => navigation.navigate('Screen1')} />
    </View>
  );
}

export default function SharedElementExample() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: true}}>
        <Stack.Screen name="Screen1" component={Screen1} />
        <Stack.Screen name="Screen2" component={Screen2} />
      </Stack.Navigator>  </NavigationContainer>
  );
}

なんと、これだけでShared Element Transitionsが実装できました!

活用方法

Transitionsアニメーションのタイミングや種類を変えることでいろいろなことができそうです。
実装例は、公式Exampleが充実しています。
例えば、このようなUIが簡単に実装可能です。


Card


LayoutAnimation


Modal

これ以外にもまだまだ遷移アニメーションが用意されていますので、ぜひのぞいて見てください。
公式のドキュメントは以下の通りです。

https://docs.swmansion.com/react-native-reanimated/docs/api/sharedElementTransitions

ちなみに、reanimated v3に関するBlogは以下に公開されています。

https://blog.swmansion.com/releasing-reanimated-3-0-17fab4cb2394

最後に

ここまで読んでいただきありがとうございました。
こんなすごい技術が簡単に導入できるのはすごいですね!
推奨版が登場したら、オトとりっぷにもぜひ導入したいです。

もし犬専用の音楽アプリに興味を持っていただけたら、ぜひダウンロードしてみてください!

https://www.oto-trip.com/

Discussion