Tab Bar
The TabBar
component gives you a native tabbed interface in iOS and Android. You create one TabBarItem
child per tab and idenfity them using the title
prop. On the 'contacts' scene in our email example, we separate the personal and work contacts by having a separate tab for each. We set the primary
prop to false because these aren't top level navigation tabs.
import {TabBar, TabBarItem} from 'navigation-react-native';
const Contacts = () => (
<TabBar primary={false}>
<TabBarItem title="Personal">
<ScrollView />
</TabBarItem>
<TabBarItem title="Work">
<ScrollView />
</TabBarItem>
</TabBar>
);
Adding Top Level Navigation Tabs
Top level navigation tabs are different. On iOS, and sometimes Android, they stay at the bottom of the screen the whole time. Each tab has its own stack of scenes. You navigate forward and back within the tab. To recreate these you render a TabBar
component and set its primary
prop to true. Then you assign each TabBarItem
a NavigationStack
component with its own StateNavigator
.
Let's add top level tabs to our email example. One tab for the user's inbox and another for their list of contacts. We create a new StateNavigator
for each of the tabs. The StateNavigator
for the 'inbox' tab has the 'inbox', 'mail' and 'compose' States
that we're already familiar with. The StateNavigator
for the 'contacts' tab contains two States
so the user can view all their contacts and edit the details of an individual contact.
const contactsNavigator = new StateNavigator([
{key: 'contacts'},
{key: 'contact', trackCrumbTrail: true}
]);
We create a scene that returns a TabBar
component. We give each tab its own NavigationStack
and StateNavigator
. For example, we give the 'contacts' tab the contactsNavigator
we just created. To make them look like top level tabs, we assign each one an image as well as a title. We hide the navigation bar otherwise, on iOS, it would block the navigation bar of the scenes inside the tabs.
const Tabs = () => (
<>
<NavigationBar hidden={true} />
<TabBar primary={true}>
<TabBarItem title="Inbox" image={require('./home.png')}>
<NavigationHandler stateNavigator={inboxNavigator}>
<NavigationStack>
<Scene stateKey="inbox"><Inbox /></Scene>
<Scene stateKey="mail"><Mail /></Scene>
<Scene stateKey="compose"><Compose /></Scene>
</NavigationStack>
</NavigationHandler>
</TabBarItem>
<TabBarItem title="Contacts" image={require('./contacts.png')}>
<NavigationHandler stateNavigator={contactsNavigator}>
<NavigationStack>
<Scene stateKey="contacts"><Contacts /></Scene>
<Scene stateKey="contact"><Contact /></Scene>
</NavigationStack>
</NavigationHandler>
</TabBarItem>
</TabBar>
</>
);
We replace the States
in the top-level StateNavigator
with a single one for the 'tabs' scene. Remember that the 'inbox', 'mail' and 'compose' States
now live inside the StateNavigator
of the 'inbox' tab. There's only one scene in the top-level NavigationStack
and it renders the Tabs
component.
const stateNavigator = new StateNavigator([
{key: 'tabs'}
]);
<NavigationStack>
<Scene stateKey="tabs"><Tabs /></Scene>
</NavigationStack>
Note
We could assign the number of unread emails to thebadge
prop of the 'inbox' TabBarItem
. But the badge won't display on Android unless we change to a MaterialComponents
theme in the styles.xml, for example, Theme.MaterialComponents.Light.NoActionBar
or Theme.Material3.Light.NoActionBar
Scrolling the Navigation Bar on Android
By wrapping the contacts scene in a CoordinatorLayout
component we prevent the navigation bar from getting in the way on Android. The navigation bar gradually scrolls offscreen as the user moves down through their contacts. As soon as the user starts to scroll back up the navigation bar reappears. We render an empty TabBar
inside the navigation bar to prevent the tabs scrolling off the screen, too. This functionality is only available when nested scrolling is enabled on the React Native ScrollViews
.
<CoordinatorLayout>
<NavigationBar title="Contacts">
<TabBar primary={false} />
</NavigationBar>
<TabBar primary={false}>
<TabBarItem title="Personal">
<ScrollView nestedScrollEnabled={true} />
</TabBarItem>
<TabBarItem title="Work">
<ScrollView nestedScrollEnabled={true} />
</TabBarItem>
</TabBar>
</CoordinatorLayout>
Any content inside a CollapsingBar
component will collapse as the user scrolls down. We can spruce up our contacts scene by placing an image in the navigation bar. We use an Animated.Image
component so we can add a parallax scrolling effect. As the navigation bar collapses the offsetChanged
event fires and we scale and reposition the image to match.
<CoordinatorLayout>
<NavigationBar
style={{height: 240}}
onOffsetChanged={Animated.event(
[{nativeEvent: {offset}}]
)}>
<CollapsingBar>
<Animated.Image style={{
transform: [{scaleY}, {translateY}]}}
}} />
</CollapsingBar>
<TabBar primary={false} />
</NavigationBar>
</CoordinatorLayout>