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 @ksushiva.
https://dev.to/ksushiva/animated-sliding-tab-bar-in-react-native-56nb
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/native
npm install @react-navigation/bottom-tabs
expo 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:
https://github.com/baptisteArnaud/animated-bottom-tab-react-native-example
Feel free to post a comment on this tutorial if you need help.
Top comments (1)
Thank you so much, bro.
I just want to ask one thing, how can I modify for example middle menu item to be a big button and overflowing the tab bar?
I've able to do that in default bottom tabs, but in this case still figuring it out.