React Native Shared Elements Using Reanimated 3

Ajmal Hasan - Nov 30 '23 - - Dev Community

Shared element transitions in React Native involve smoothly transitioning the position, size, and appearance of elements between two screens. This is often used for a seamless user experience when navigating between screens.

Prerequisite:

Install and configure by following below links:

  1. https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation
  2. https://reactnavigation.org/docs/getting-started/#wrapping-your-app-in-navigationcontainer
  3. https://reactnavigation.org/docs/hello-react-navigation#installing-the-native-stack-navigator-library
  4. https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#installation

After doing above step lets move to coding. I have added inline comments for understating.

RenderItem.js (flatlist render item)

import {Pressable, StyleSheet, Text, View} from 'react-native';
import React from 'react';
import {useNavigation} from '@react-navigation/native';
import Animated, {FadeInDown} from 'react-native-reanimated';

const RenderItem = ({item, index}) => {
  const navigation = useNavigation();
  return (
    // Animated.View for fading in the item with delay based on index
    <Animated.View entering={FadeInDown.delay(200 * index)}>
      <Pressable
        style={styles.container}
        onPress={() => {
          navigation.navigate('Details', {item: item});
        }}>
        {/* Animated.Image with sharedTransitionTag is used to create a visually appealing shared element transition between screens when navigating, ensuring a smooth animation of the image as it moves from one screen to another. */}
        <Animated.Image
          source={item.image}
          style={styles.image}
          sharedTransitionTag={item.name}
        />
        <View style={styles.textContainer}>
          <Text style={styles.textName}>{item.name}</Text>
          <Text style={styles.textLocation}>{item.location}</Text>
        </View>
      </Pressable>
    </Animated.View>
  );
};

export default RenderItem;

Enter fullscreen mode Exit fullscreen mode

Detail Screen

import {StyleSheet, Text, View, useWindowDimensions} from 'react-native';
import React from 'react';
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated';
import Header from '../components/Header';
import Button from '../components/Button';
import LinearGradient from 'react-native-linear-gradient';

const Detail = ({route}) => {
  const {item} = route.params;
  const {width} = useWindowDimensions();

  return (
    <LinearGradient colors={['#a8ff78', '#78ffd6']} style={styles.container}>
      <Header />
      <View>
        <View>
          <Animated.Image
            sharedTransitionTag={item.name}
            source={item.image}
            style={{width: width, height: width}}
          />
          <Animated.View
            style={styles.textContainer}
            entering={FadeIn.delay(600)}>
            <Text style={styles.textName}>{item.name}</Text>
            <Text style={styles.textLocation}>{item.location}</Text>
          </Animated.View>
        </View>
        <Animated.View entering={FadeInDown.delay(800)}>
          <Text style={styles.textAbout}>About</Text>
          <Text style={styles.text}>{item.about}</Text>
        </Animated.View>
      </View>
      <Button />
    </LinearGradient>
  );
};

export default Detail;
Enter fullscreen mode Exit fullscreen mode

Header of Detail Screen

/* eslint-disable react-native/no-inline-styles */
import {Image, Platform, Pressable, StyleSheet} from 'react-native';
import React from 'react';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import Animated, {FadeIn} from 'react-native-reanimated';
import {useNavigation} from '@react-navigation/native';

const Header = () => {
  const inset = useSafeAreaInsets();
  const navigation = useNavigation();
  return (
    <Animated.View
      style={[styles.container, {top: Platform.OS === 'ios' ? inset.top : 20}]}
      entering={FadeIn.delay(400)}>
      <Pressable
        onPress={() => {
          navigation.goBack();
        }}>
        <Image
          source={require('../assets/chevron.png')}
          style={styles.chevron}
        />
      </Pressable>
      <Pressable
        onPress={() => {
          console.log('LIKE');
        }}>
        <Image source={require('../assets/like.png')} style={styles.chevron} />
      </Pressable>
    </Animated.View>
  );
};

export default Header;

Enter fullscreen mode Exit fullscreen mode

Bottom Button on detail screen

import {Pressable, StyleSheet, Text, useWindowDimensions} from 'react-native';
import React from 'react';
import Animated, {FadeInDown} from 'react-native-reanimated';

const Button = () => {
  const {width} = useWindowDimensions();

  // Creating an Animated Pressable component using createAnimatedComponent //ALTERNATE:==> <Animated.View/>
  const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

  return (
    <AnimatedPressable
      style={[styles.container, {width: width * 0.9}]} // Styles for the Pressable
      entering={FadeInDown.delay(1000)} // FadeInDown animation with a delay of 1000 milliseconds
      onPress={() => {
        console.log('BOOKING NOW');
      }}>
      <Text style={styles.text}>Booking Now</Text>
    </AnimatedPressable>
  );
};
export default Button;

Enter fullscreen mode Exit fullscreen mode

GITHUB REPO:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .