DEV Community

Cover image for React Native Tab Bar Animation
Anas Nabil
Anas Nabil

Posted on • Updated on

React Native Tab Bar Animation

Here's what are we going to implement but without The Pose Effect, which will be added later in a separate article.

Image description

First of all, Let's setup our Tab.Navigator to Support Custom TabBar Components, By adding the following code in MainTabs.tsx file

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

import TabBar from './TabBar';

import Home from '../Stack/Home';
import Categoryfrom '../Stack/Category';
import Offers from '../Stack/Offers';
import Account from '../Stack/Account';
import Cart from '../Stack/Cart';

const Tab = createBottomTabNavigator();

const TabNavigator: React.FC = () => {
  const tabs = [
    {
      name: 'Home',
      label: 'Home',
      component: Home,
    },
    {
      name: 'Category',
      label: 'Category',
      component: Category,
    },
    {
      name: 'Offers',
      label: 'Offers',
      component: Offers,
    },
    {
      name: 'Account',
      label: 'Account',
      component: Account,
    },
    {
      name: 'Cart',
      label: 'Cart',
      component: Cart,
    },
  ];

  return (
    <Tab.Navigator
      tabBar={(props) => <TabBar {...props} />}
      initialRouteName={'Home'}
    >
      {tabs.map((_, index) => {
        return (
          <Tab.Screen
            key={index}
            name={_.name}
            component={_.component}
            options={{
              tabBarLabel: _.label,
            }}
          />
        );
      })}
    </Tab.Navigator>
  );
};

export default TabNavigator;
Enter fullscreen mode Exit fullscreen mode

Now we have to create our TabBar Component which will support the Animation by Adding the following code into our TabBar.tsx file

import React, { useEffect, useRef } from 'react';
import { COLORS, DEVICE_HEIGHT as height, DEVICE_WIDTH as width, ICONS } from '../../common';
import { AppIcon, AppText } from '../../components';
import { StyleSheet, View, TouchableWithoutFeedback, Animated } from 'react-native';

const TAB_BAR_WIDTH = width / 5;
const ANIMATED_PART_HEIGHT = 5;

const TabBar = ({ state, descriptors, navigation }) => {
  const animationHorizontalValue = useRef(new Animated.Value(0)).current;

  const animate = (index) => {
    Animated.spring(animationHorizontalValue, {
      toValue: index * TAB_BAR_WIDTH,
      useNativeDriver: true,
    }).start();
  };

  useEffect(() => {
    animate(state.index);
  }, [state.index]);

  return (
    <View style={styles.container}>
      <Animated.View style={styles.animatedWrapper}>
        <Animated.View
          style={[
            styles.animatedView,
            {
              transform: [{ translateX: animationHorizontalValue }],
            },
          ]}
        />
      </Animated.View>

      <View style={{ flexDirection: 'row' }}>
        {state.routes.map((route, index) => {
          const { options } = descriptors[route.key];
          const label = options.tabBarLabel || 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 (
            <TouchableWithoutFeedback
              accessibilityRole="button"
              accessibilityState={isFocused ? { selected: true } : {}}
              accessibilityLabel={options.tabBarAccessibilityLabel}
              testID={options.tabBarTestID}
              onPress={onPress}
              onLongPress={onLongPress}
              style={styles.tabButton}
              key={`${index}--${route.key}`}
            >
              <View style={styles.innerView}>
                <AppIcon name={label} color={isFocused ? COLORS.main : COLORS.black} />
                <AppText numberOfLines={1} type="heavy" style={[styles.iconText, { color: isFocused ? COLORS.main : COLORS.black }]}>
                  {label}
                </AppText>
              </View>
            </TouchableWithoutFeedback>
          );
        })}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
    borderTopColor: COLORS.gray,
    borderTopWidth: 0.5,
    backgroundColor: COLORS.white,
  },
  tabButton: {
    flex: 1,
  },
  innerView: {
    paddingVertical: height * 0.01,
    justifyContent: 'center',
    alignItems: 'center',
  },
  iconText: {
    width: TAB_BAR_WIDTH,
    textAlign: 'center',
  },
  animatedView: {
    width: TAB_BAR_WIDTH,
    height: ANIMATED_PART_HEIGHT,
    backgroundColor: COLORS.main,
  },
  animatedWrapper: { width: TAB_BAR_WIDTH, alignItems: 'center', justifyContent: 'center' },
});

export default TabBar;
Enter fullscreen mode Exit fullscreen mode

As we can see, It's very easy to implement, nothing much to explain.

Happy Coding ❤

Top comments (1)

Collapse
 
ahmedmasoud profile image
Ahmed Masoud

Great, best of luck ❤️