Creating an animatable bottom bar with Animated and React Navigation in React Native
EchoEye
Posted on February 2, 2020
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:
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'
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}
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} />
}
}
})
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} />
}
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)
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>
)
}
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>
)
}
This is how the app should look.
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>
)
}
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>
)
}
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!
Posted on February 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 7, 2021