Animated Sliding tab bar in React Native
I’ll show you in this tutorial how to make this cool animated custom bottom tab bar using React Navigation.
In order to do this, I found this great tutorial by Oksana Ivanchenko :
But unfortunately, it’s already outdated due to the recent publication of React Navigation v5. And it comes with lots of modifications.
Dependencies
npm install @react-navigation/nativenpm install @react-navigation/bottom-tabsexpo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
Custom MenuItem
import React from "react";
import { View } from "react-native";
import { blue, grey } from "../../styles";
import { AntDesign } from "@expo/vector-icons";type Props = {
iconName: string;
isCurrent?: boolean;
};export const BottomMenuItem = ({ iconName, isCurrent }: Props) => {
return (
<View
style={{
height: "100%",
justifyContent: "center",
alignItems: "center",
}}
>
<AntDesign
name={iconName}
size={32}
style={{ color: isCurrent ? blue : grey }}
/>
</View>
);
};
This represented each item in the tab bar. You can adjust this component with anything you want. Here it requires an iconName
in order to display the correct icon and a isCurrent
property which changes the icon color if it is currently selected.
The Custom TabBar component
import React, { useState } from "react";
import {
View,
TouchableOpacity,
Dimensions,
StyleSheet,
} from "react-native";
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
import { BottomMenuItem } from "./BottomMenuItem";
import { blue } from "../../styles";export const TabBar = ({
state,
descriptors,
navigation,
}: BottomTabBarProps) => {
const totalWidth = Dimensions.get("window").width;
const tabWidth = totalWidth / state.routes.length; return (
<View style={[style.tabContainer, { width: totalWidth }]}>
<View style={{ flexDirection: "row" }}>
<View style={style.slider}/>{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;const isFocused = state.index === index;const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}const onLongPress = () => {
navigation.emit({
type: "tabLongPress",
target: route.key,
});
};return (
<TouchableOpacity
accessibilityRole="button"
accessibilityStates={isFocused ? ["selected"] : []}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
style={{ flex: 1 }}
key={index}
>
<BottomMenuItem
iconName={label.toString()}
isCurrent={isFocused}
/>
</TouchableOpacity>
);
})}
</View>
</View>
);
};const style = StyleSheet.create({
tabContainer: {
height: 60,
shadowOffset: {
width: 0,
height: -1,
},
shadowOpacity: 0.1,
shadowRadius: 4.0,
backgroundColor: "white",
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
elevation: 10,
position: "absolute",
bottom: 0,
},
slider: {
height: 5,
position: "absolute",
top: 0,
left: 10,
backgroundColor: blue,
borderRadius: 10,
width: 50
},
});
Connect the custom TabBar to the Navigation System (BottomMenu component)
import React from "react";
import {
createBottomTabNavigator,
BottomTabBarProps,
} from "@react-navigation/bottom-tabs";
import { TabBar } from "./TabBar";
import { AppsScreen } from "../../screens/AppsScreen";
import { DashboardScreen } from "../../screens/DashboardScreen";
import { GroupScreen } from "../../screens/GroupScreen";
import { ProfileScreen } from "../../screens/ProfileScreen";
import { useSafeArea } from "react-native-safe-area-context";
import { View } from "react-native";export const BottomMenu = () => {
const Tab = createBottomTabNavigator();
return (
<View style={{ flex: 1, position: "relative"}}>
<Tab.Navigator
tabBar={(props: BottomTabBarProps) => <TabBar {...props} />}
>
<Tab.Screen name="search1" component={AppsScreen} />
<Tab.Screen name="dashboard" component={DashboardScreen} />
<Tab.Screen name="profile" component={GroupScreen} />
<Tab.Screen name="user" component={ProfileScreen} />
</Tab.Navigator>
{useSafeArea().bottom > 0 && (
<View
style={{
height: useSafeArea().bottom - 5,
backgroundColor: "white",
}}
/>
)}
</View>
);
};
we use the useSafeArea
piece of code in order to render our tab bar higher if there is the horizontal bar on the recent iOS devices for example.
Then you simply have to place this BottomMenu
anywhere. In App
component for example:
import React from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { NavigationContainer } from "@react-navigation/native";
import {BottomMenu} from "./src/components/BottomMenu/BottomMenu";export default function App() {
return (
<NavigationContainer>
<SafeAreaProvider>
<BottomMenu/>
</SafeAreaProvider>
</NavigationContainer>
);
}
Now you should have the bottom tab bar on your app but we need to animate the “slider” now.
So in the `TabBar` component, you need:
To add a state variable :
const [translateValue] = useState(new Animated.Value(0));
To change the current View representing the slider to :
<Animated.View
style={[
style.slider,
{
transform: [{ translateX: translateValue }],
width: tabWidth - 20,
},
]}
/>
And finally, in the onPress
function, add this piece of code:
Animated.spring(translateValue, {
toValue: index * tabWidth,
velocity: 10,
useNativeDriver: true,
}).start();
You can find the entire code example in this repository:
Feel free to post a comment on this tutorial if you need help.