DEV Community

Cover image for Creating an animatable bottom bar with Animated and  React Navigation in React Native
EchoEye
EchoEye

Posted on

Creating an animatable bottom bar with Animated and React Navigation in React Native

This tutorial is essentially to show how you can apply animations to the bottom bar view when you press any of the tab buttons in a react native app. I recently saw a UI design concept illustrating this and with a few tweaks from the docs from react-navigation, I was able to achieve this. Note that you should have prior knowledge of creating basic animations in react native as this is not a tutorial to get started with it. We'll need react-navigation and react-navigation-tabs for this project. The full code can be viewed here

Here's a gif showing what we'll be building:
video

First we import our necessary files in the App.js file with the following code:

import React, {useState} from 'react'
import {View, Dimensions, Animated} from 'react-native'
import {createBottomTabNavigator, BottomTabBar} from 'react-navigation-tabs'
import {ScreenOne, ScreenTwo, ScreenThree} from './Screens'
Enter fullscreen mode Exit fullscreen mode

Now we create the respective screens for each navigator item. Create a new file and name it Screens.js and input the following

import React from 'react'
import {View, Text, StyleSheet} from 'react-native'
const ScreenOne = () => {
    return(
        <View style={styles.container}>
        <Text>This is the first screeen</Text>
        </View>
    )
}

const ScreenTwo = () =>{
    return(
         <View style={styles.container}>
        <Text>This is the second screeen</Text>
        </View>
    )
}

const ScreenThree = () =>{
    return(
         <View style={styles.container}>
        <Text>This is the third screeen</Text>
        </View>
    )
}

const styles = StyleSheet.create({
    container:{
        flex: 1,
        backgroundColor: '#fff',
        justifyContent: 'center',
        alignItems: 'center'
    }
})
export {ScreenOne, ScreenTwo, ScreenThree}
Enter fullscreen mode Exit fullscreen mode

Back to our App.js, let's map our respective screens to the bottom tab component. It receives two arguement. An object containing the screens, and the configuration for the bottom tab component We continue the code:

const bottomComponent = createBottomTabNavigator({
    Home:{
        screen: ScreenOne,
        navigationOptions:{
        tabBarIcon: ({tintColor}) => <Ionicons name='md-home' color={tintColor} size={24} />
    }
    },
    Notifications:{
        screen: ScreenTwo,
        navigationOptions:{
        tabBarIcon: ({tintColor}) => <Ionicons name='md-notifications' color={tintColor} size={24} />
    }
    },
    Profile:{
        screen: ScreenThree,
        navigationOptions:{
        tabBarIcon: ({tintColor}) => <Ionicons name='md-person' color={tintColor} size={24} />
    }
    }
})
Enter fullscreen mode Exit fullscreen mode

We can customize the look of the bottom bar with the BottomTabBar component imported from createBottomTabNavigator. To do this, we add our we create a configuration object that would be passed to the createBottomTabNavigator. It should look like this:

const config= {
    tabBarOptions:{
    activeTintColor: '#fff',
    inactiveTintColor: 'rgba(0,0,0,0.7)'
  },
  tabBarComponent: (props) => <CustomBottomBar {...props} />
}
Enter fullscreen mode Exit fullscreen mode

Our createBottomTabNavigator should now look like this:

const bottomNavigator = createBottomTabNavigator({
  Home:{
    screen: ScreenOne,
    navigationOptions:{
      tabBarIcon: ({tintColor}) => <Ionicons name='md-home' color={tintColor} size={24} />
    }
  },
  Notifications:{
    screen: ScreenTwo,
    navigationOptions:{
      tabBarIcon: ({tintColor}) => <Ionicons name='md-notifications' color={tintColor} size={24} />
    }
  },
  Profile:{
    screen: ScreenThree,
    navigationOptions:{
      tabBarIcon: ({tintColor}) => <Ionicons name='md-person' color={tintColor} size={24} />
    }
  }
}, config)
Enter fullscreen mode Exit fullscreen mode

The important property here is the tabBarComponent. This accepts a component to act as the default bottom tab bar. We named this CustomBottomBar. It also has props that should be passed down to it so that the custom view object still behaves like the default tab bar. React navigation gives us access to the BottomTabBar component so we can configure such component into how we want this to look instead of using the default look and feel of the bottom component.

Now, let's go on to creating the CustomBottomBar component. Still in the same App.js, do the following:

const CustomBottomBar = (props) =>{
    //We use the spread operator to pass down all default properties of a bottom bar
    return(
        <View>
        <BottomBar {...props} style={{backgroundColor: 'tranparent'}}>
        </View>
    )
}
Enter fullscreen mode Exit fullscreen mode

Note that the background color of the BottomBar should be set to transparent so that it doesn't overshadow the custom indicator.
To avoid complexity, we won't be adding any further styling to the bottom bar. You'll notice we wrapped a view around the BottomBar component. This is so that we can add a custom indicator of our choice to it. Since our aim is to create that movable colored view in the image above, we add a new component to the CustomBottomBar. The code should now look like this:

const CustomBottomBar = (props) =>{
    //We use the spread operator to pass down all default properties of a bottom bar

    //custom styles for our indicator
    //The width of the indicator should be of equal size with each tab button. We have 3 tab buttons therefore, the width of a single tab button would be the total width Dimension of the screen divided by 3

    const {width} = Dimensions.get('screen')
    const animStyles = {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom:0,
        width: width/3,
        backgroundColor: 'rebeccapurple'
    }
    return(
        <View>
        <Animated.View style={animStyles} />
        <BottomTabBar {...props} style={{backgroundColor: 'tranparent'}} />
        </View>
    )
}
Enter fullscreen mode Exit fullscreen mode

This is how the app should look.
Screesnhot

Now, we animate it. The indicator is required to move to the very position of the tab button pressed. The BottomTab component receives an onTabPress prop which is a method and this returns the route name of the tab pressed. This method executes an action everytime a tab button is pressed. We'll use this action to properly animate the indicator to the specific location. Still in the CustomBottomBar component, we modify it as:

const CustomBottomBar = (props) =>{
    //We use the spread operator to pass down all default properties of a bottom bar

    //custom styles for our indicator
    //The width of the indicator should be of equal size with each tab button. We have 3 tab buttons therefore, the width of a single tab button would be the total width Dimension of the screen divided by 3

    const {width} = Dimensions.get('screen')

    //Create an animated value 
    const [position] = useState(new Animated.ValueXY())

    //We attach the x,y coordinates of the position to the transform property of the indicator so we can freely animate it to any position of our choice.
    const animStyles = {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom:0,
        width: width/3,
        backgroundColor: 'rebeccapurple',
        transform: position.getTranslateTransform()
    }
    return(
        <View>
        <Animated.View style={animStyles} />
        <BottomTabBar {...props} style={{backgroundColor: 'tranparent'}} />
        </View>
    )
}
Enter fullscreen mode Exit fullscreen mode

With everything set, we can now animate our position to where we want it. We create a method called animate, This method would be passed to the onTabPress prop of the BottomBar component. So we should have our code like this:

const CustomBottomBar = (props) =>{
    //We use the spread operator to pass down all default properties of a bottom bar

    //custom styles for our indicator
    //The width of the indicator should be of equal size with each tab button. We have 3 tab buttons therefore, the width of a single tab button would be the total width Dimension of the screen divided by 3

    const {width} = Dimensions.get('screen')

    //Create an animated value 
    const [position] = useState(new Animated.ValueXY())

    //We attach the x,y coordinates of the position to the transform property of the indicator so we can freely animate it to any position of our choice.
    const animStyles = {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom:0,
        width: width/3,
        backgroundColor: 'rebeccapurple',
        transform: position.getTranslateTransform()
    }

    const animate = (value, route) =>{
        //navigate to the selected route on click
        props.navigation.navigate(route)

        //animate indicator
        Animated.timing(position, {
            toValue: {x: value, y: 0},
            duration: 300,
            useNativeDriver: true
        }).start()
    }

    return(
        <View>
        <Animated.View style={animStyles} />
        <BottomTabBar {...props} onTabPress={({route}) =>{
            switch(route.key){
                case 'Home':
                //animated position should be 0
                     animate(0, route.key)
                     break
                     case 'Notifications':
                     //animated position is width/3
                      animate(width/3 , route.key)
                      break
                      case 'Profile':
                      //animated position is width of screen minus width of single tab button
                       animate(width - (width/3), route.key)
                       break
            }
        }} style={{backgroundColor: 'transparent'}} />
        </View>
    )
}
Enter fullscreen mode Exit fullscreen mode

And that's it. We have successfully animated our indicator for the bottom bar to focus on the actively selected screen of the bottom bar. Here is the full. We just had to modify the BottomTabBar component imported from react-navigation-tabs as we would do for a normal react component. Nothing much here!

Top comments (7)

Collapse
 
baptistearnaud profile image
Baptiste Arnaud

Great tuto, merci EchoEye :)

Your code is valid with React Navigation v4. I made a similar tutorial for React Navigation v5 here :
dev.to/baptistearnaud/animated-sli...

Collapse
 
echoeyecodes profile image
EchoEye

Thank you😊 checking it out now!

Collapse
 
hhtu profile image
H2 • Edited

How to show animations if I set a background color in BottomTabBar? I tried to add zIndex, but it will lead to cover my icon or text.

Collapse
 
ryantando profile image
Ryan Tando

You can actually set the background color on the parent

Collapse
 
echoeyecodes profile image
EchoEye

Make sure the view you're animating is rendered before the tab bar Items

Collapse
 
hugoh59 profile image
Hugo Houyez

Can we also change the pink bg color depending on which route we are?

Collapse
 
echoeyecodes profile image
EchoEye • Edited

Yes. You can interpolate the position value to an array of colors(in RGB) like this:

const [position] = useState( new Animated.ValueXY() );

//I'm using position.x because it's the x coordinate that is actually changing.

const myColor = position.x.interpolate({
 inputRange: [0, width],
 outputRange: ['rgb(255, 255, 255)', 'rgb(0,0,0)'],
 extrapolate: 'clamp',
});