React Native Flat-List Animation using Animated

React Native Flat-List Animation using Animated

Hey folks, media.giphy.com This article is about creating an animation for a Flat List in React Native. The project uses Animated for creating the animation.

The animation will look like this -

ezgif.com-gif-maker.gif

Prerequisites -

  • Knowledge of React Native ~ Beginner Level
  • Working React Native Environment - Expo or React Native setup

Inital Dependencies in react-native get started project -

Copied from package.json file

"expo": "~44.0.0",
"expo-status-bar": "~1.2.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.3",
"react-native-web": "0.17.1"

So let's get started -

Open the folder in terminal, where you want to create your project -

  1. Make folder mkdir React_Native_Animation
  2. Run the command cd React_Native_Animation
  3. Run the command expo init .
  4. Select the first blank template.
  5. Run the command to open your folder in Virtual Studio Code code .
  6. In Virtual Studio Code open App.js file and clear all the code.

Now let's code some gibberish...

In App.js file

  • import modules
import React from 'react'
import {View,Text,SafeAreaView,FlatList,StyleSheet,TouchableOpacity,Pressable} from 'react-native'
import { Animated } from 'react-native';
  • Create a class component and export it
class SocialMediaPage extends React.Component  {

}
export default SocialMediaPage;
  • Add a render method in class and return a View with a Text Component and text as 'Hello'
 render() { 
     return (
      <View> 
        <Text>
           Hello
        </Text>  
      </View>
      );
    }
  • Wrap the View component inside a SafeAreaView and create styles and assign styles to the components.
import React from 'react'
import {View,Text,SafeAreaView,FlatList,StyleSheet,TouchableOpacity,Pressable} from 'react-native'
import { Animated } from 'react-native';

class SocialMediaPage extends React.Component  {

    render() { 
     return (
     <SafeAreaView  
     style={styles.container}
     >  
        <View  style={styles.container}> 
          <Text>
           Hello
          </Text> 
        </View>
      </SafeAreaView>
      );
    }
}

const styles = StyleSheet.create({
  container:{
     marginTop:20,
  },
})

export default SocialMediaPage;
  • Create a data variable inside the SocialMediaPage class component which will contains the Item objects for the Flatlist
 data = [
   {text:'Twitter'},
   {text:'Facebook'},
   {text:'Github'},
   {text:'Whatsapp'},
   {text:'Instagram'},
   {text:'Snapchat'},
   {text:'LinkedIn'},
   {text:'Leetcode'},
   {text:'Codechef'},
   {text:'Koo'}
  ]
  • Remove the View , TextComponent and add a Flatlist and pass the variable data in data props and define the keyExtractor props of Flatlist.
    <FlatList 
          data={this.data}
           keyExtractor={(item)=>{
           return  item.text}}
         />
    
  • Create styles for ItemSeparatorComponent and ItemComponent of Flatlist.
const styles = StyleSheet.create({

  container:{
    marginTop:20,
  },
  itemSeparator:{
    height:2.0,
    backgroundColor:'black',
    margin:2.0,
    marginHorizontal:.0
  },
  itemContainer:{
    flexDirection:'row',
    height:70,
    width:370,
    borderColor:'blue',
    borderWidth:1.0,
    padding:16,
    marginHorizontal:14.0,
    marginVertical:4.0,
    backgroundColor:'lightgrey',   
  }
})
  • Define the props ItemSeparatorComponent and renderItem for the FlatList.
ItemSeparatorComponent={
           () =>{
            return ( 
              <View
              style={styles.itemSeparator
              }
              > 

              </View>
              )
          }
         }
         showsVerticalScrollIndicator= {false}
         renderItem={(items)=>{
         return  (
         )
        }
  • In the renderItem props of Flatlist create a Text with text taken from data and wrap it inside a View which has the itemContainer styles.
ItemSeparatorComponent={
           () =>{
            return ( 
              <View
              style={styles.itemSeparator
              }
              > 

              </View>
              )
          }
         }
         showsVerticalScrollIndicator= {false}
         renderItem={(items)=>{
         return  (
              <View
          style={styles.itemContainer}
          >  

        <Text>
                {items.item.text}
               </Text>           
           </View>
         )
        }

Animating

  • Create a state variable inside the SocialMediaPage class component
state = {
    selectedKey:'',
    animation:new Animated.Value(0.0)
  }

The animation is an animated value that will vary and will be used for animation. We will change the value of selectedKey on pressing the Flatlist Item and will then animate that particular item.

The Animated library has different constructors for different kinds of animations. The main four are -

Animated.spring - Used for bouncing animations or animation with a spring effect. Eg.

Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ).start()

Animated.stagger - Used for creating complex animations where each animation occurs after the previous animation has ended. Eg.

 Animated.stagger(300,
      [Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ),
      Animated.spring(
        this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:0,
          useNativeDriver:true,
        }
      )
      ]
      ).start();

Animated.timing - Used for creating complex animation and perform a series of animation. Eg.

 Animated.timing(300,
      [Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ),
      Animated.spring(
        this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:0,
          useNativeDriver:true,
        }
      )
      ]
      ).start();

Animated.parallel - Used for creating complex animations where each animation occurs in parallel. Eg.

  Animated.parallel(300,
      [Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ),
      Animated.spring(
        this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:0,
          useNativeDriver:true,
        }
      )
      ]
      ).start();

We will use Animation.stagger as shown in example in our project.

  • Create a handleAnimation function inside the SocialMediaPage class component that will be called when you press on a Flatlist Item.

  • Defining the Animation in handleAnimation function

 handleAnimation=()=>{
    Animated.parallel(300,
      [Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ),
      Animated.spring(
        this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:0,
          useNativeDriver:true,
        }
      )
      ]
      ).start();
  }
  • In the renderItem prop of Flatlist return two different views depending on the value of selectedKey
 {this.state.selectedKey === items.item.text && 
          (
            <Animated.View
          style={[styles.itemContainer,{
            transform:[
              {translateX: this.state !=null ?  this.state.animation : 0 }
            ]
          }]}
          > 

             <Text>
              {items.item.text}
             </Text>
          </Animated.View>
          )   
          }      
       {this.state.selectedKey != items.item.text &&     
        (
          <View
          style={styles.itemContainer}
          >  

        <Text>
                {items.item.text}
               </Text>           
           </View>
           )   
        }

The View component in the first condition is Animated.View as it will animate. The Animated.View component has a new style that is based on the animation value defined in state variable.

  • Wrap the entire returned FlatList item with a Pressable and define its onPress prop. In the onPress function change the value of selectedKey using this.setState({}) and call the handleAnimation function.

Now renderItem prop of Flatlist would look like this -

   renderItem={(items)=>{
         return  (
          <Pressable  
          onPress={() =>  {
           this.setState({
              selectedKey:items.item.text
            })
            console.log(this.state.selectedKey)
            this.handleAnimation()
           }
          }
          >
          {this.state.selectedKey === items.item.text && 
          (
            <Animated.View
          style={[styles.itemContainer,{
            transform:[
              {translateX: this.state !=null ?  this.state.animation : 0 }
            ]
          }]}
          > 
             <Text>
              {items.item.text}
             </Text>
          </Animated.View>
          )   
          }      
       {this.state.selectedKey != items.item.text &&     
        (
          <View
          style={styles.itemContainer}
          >  
        <Text>
                {items.item.text}
               </Text>           
           </View>
           )   
        }
          </Pressable>
         )   
        } 
      }

The flatList component at this stage will be -

  <FlatList 
        data={this.data}
         keyExtractor={(item)=>{
         return  item.text}}
         ItemSeparatorComponent={
           () =>{
            return ( 
              <View
              style={styles.itemSeparator
              }
              > 
              </View>
              )
          }
         }
         showsVerticalScrollIndicator= {false}
         renderItem={(items)=>{
         return  (
          <Pressable  
          onPress={() =>  {
           this.setState({
              selectedKey:items.item.text
            })
            console.log(this.state.selectedKey)
            this.handleAnimation()
           }
          }
          >
          {this.state.selectedKey === items.item.text && 
          (
            <Animated.View
          style={[styles.itemContainer,{
            transform:[
              {translateX: this.state !=null ?  this.state.animation : 0 }
            ]
          }]}
          > 
             <Text>
              {items.item.text}
             </Text>
          </Animated.View>
          )   
          }      
       {this.state.selectedKey != items.item.text &&     
        (
          <View
          style={styles.itemContainer}
          > 
        <Text>
                {items.item.text}
               </Text>           
           </View>
           )   
        }
          </Pressable>
         )
        } 
      }

The full code is -

import React from 'react'
import {View,Text,SafeAreaView,FlatList,StyleSheet,TouchableOpacity,Pressable} from 'react-native'
import { Animated } from 'react-native';


class SocialMediaPage extends React.Component  {
 data = [
   {text:'Twitter'},
   {text:'Facebook'},
   {text:'Github'},
   {text:'Whatsapp'},
   {text:'Instagram'},
   {text:'Snapchat'},
   {text:'LinkedIn'},
   {text:'Leetcode'},
   {text:'Codechef'},
   {text:'Koo'}
  ]

  state = {
    selectedKey:'',
    animation:new Animated.Value(0.0)
  }

  handleAnimation=()=>{
    Animated.stagger(300,
      [Animated.spring(this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:50,
          useNativeDriver:true,
        }
        ),
      Animated.spring(
        this.state.animation,
        {
          tension:100,
          duration:1,
          friction:1,
          toValue:0,
          useNativeDriver:true,
        }
      )
      ]
      ).start();
  }

    render() { 
     return (
        <SafeAreaView
        style={styles.container}
        >  
        <FlatList 
        data={this.data}
         keyExtractor={(item)=>{
         return  item.text}}
         ItemSeparatorComponent={
           () =>{
            return ( 
              <View
              style={styles.itemSeparator
              }
              > 

              </View>
              )
          }
         }
         showsVerticalScrollIndicator= {false}
         renderItem={(items)=>{
         return  (
          <Pressable  
          onPress={() =>  {
           this.setState({
              selectedKey:items.item.text
            })
            console.log(this.state.selectedKey)
            this.handleAnimation()
           }
          }
          >
          {this.state.selectedKey === items.item.text && 
          (
            <Animated.View
          style={[styles.itemContainer,{
            transform:[
              {translateX: this.state !=null ?  this.state.animation : 0 }
            ]
          }]}
          > 

             <Text>
              {items.item.text}
             </Text>
          </Animated.View>
          )   
          }      
       {this.state.selectedKey != items.item.text &&     
        (
          <View
          style={styles.itemContainer}
          >  

        <Text>
                {items.item.text}
               </Text>           
           </View>
           )   
        }
          </Pressable>
         )


        } 


      }
       />
       </SafeAreaView>
      );

    }
}

const styles = StyleSheet.create({

  container:{
    marginTop:20,
  },
  itemSeparator:{
    height:2.0,
    backgroundColor:'black',
    margin:2.0,
    marginHorizontal:.0
  },
  itemContainer:{
    flexDirection:'row',
    height:70,
    width:370,
    borderColor:'blue',
    borderWidth:1.0,
    padding:16,
    marginHorizontal:14.0,
    marginVertical:4.0,
    backgroundColor:'lightgrey',   
  }
})

export default SocialMediaPage;

Important Things to Note -

  • Define proper animation in handleAnimation() function.
  • Properly render different components in Flatlist based on condition on the value of this.state.selectedKey.
  • When this.state.selectedKey = item.key render Animated.View component.

Voohooooooo!! We have completed the project. Cheers !!

Follow me on Twitter for more tech content on React Native and Flutter.

Did you find this article valuable?

Support Parjanya Aditya Shukla by becoming a sponsor. Any amount is appreciated!