I'm using react navigation in a react native project and I want to customize the header with an image.
For a color I can use simple styling, but since react native doesn't support background images I need a different solution.
Update:
Since v2 of the library there's an special option for setting the header background, namely headerBackground.
This option accepts a React component, so when set to an Image component, it will use that.
For example:
export default createStackNavigator({
Home: {
screen: HomeScreen
},
}, {
navigationOptions: {
headerBackground: () => (
<Image
style={StyleSheet.absoluteFill}
source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg' }}
/>
),
}
});
Working example: https://snack.expo.io/#koen/react-navigation-header-background
Old answer, for when still using React Navigation v1:
Creating a custom header with an image is actually really simple.
By wrapping the Header with a view and placing an absolute positioned image in that view, the image will scale to its parent size.
Important is to set the backgroundColor of the default header to transparent.
const ImageHeader = props => (
<View style={{ backgroundColor: '#eee' }}>
<Image
style={StyleSheet.absoluteFill}
source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg' }}
/>
<Header {...props} style={{ backgroundColor: 'transparent' }}/>
</View>
);
And then use that component as header:
const SimpleStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},
}, {
navigationOptions: {
headerTitleStyle: { color: '#fff' },
header: (props) => <ImageHeader {...props} />,
}
});
Which would result in:
According to the official docs of react-navigation v5, it can be implemented as follows:
https://reactnavigation.org/docs/headers/#replacing-the-title-with-a-custom-component
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
// title: 'App Name'
options={{
headerTitle: (props) => ( // App Logo
<Image
style={{ width: 200, height: 50 }}
source={require('../assets/images/app-logo-1.png')}
resizeMode='contain'
/>
),
headerTitleStyle: { flex: 1, textAlign: 'center' },
}}
/>
</Stack.Navigator>
Update for React Navigation v5! (making this post for future references)
For react navigation 5, I found this solution.
In StackNavigator.js class you can set a different image for each page (Stack.Screen):
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title: <Image style={{ width: 250, height: 50 }}
source = require('../images/yourimage.png')}/>
}}
/>
Then, you must adjust width, height, and position of the image, but it works! I think it's the simpliest way. Here's the output (yes, it's my image, before adjustments).
Don't forget to import Image!
import { Image } from 'react-native'
Related
I have a tab bar that looks like this:
The two side buttons are stack navigators (Learn and Journal) and the middle button needs to navigate the Journal Stack, and depending on what screen in the Journal Stack the user is on, it needs to say and do different things.
const Tab = createBottomTabNavigator();
const TabBarIcon = ({ icon, title, focused }) => {
return (
<View style={styles.iconContainer}>
<FontAwesomeIcon
icon={icon}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={24}
style={styles.icon}
/>
<Text style={[styles.iconText, focused && styles.iconTextFocused]}>
{title}
</Text>
</View>
);
};
const NullScreen = () => null;
const TabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="Journal"
screenOptions={({ route }) => ({
...defaultNavOptions,
headerShown: false,
tabBarStyle: { backgroundColor: Colors.neutral[3] },
tabBarShowLabel: false,
})}
>
<Tab.Screen
name="Learn"
component={LearnStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
title={'Learn'}
icon={faUserGraduate}
/>
),
}}
/>
<Tab.Screen
name="Null Screen"
component={NullScreen}
options={{
tabBarButton: ({ focused }) => (
<View
style={{
position: 'relative',
bottom: 25,
width: 80,
height: 80,
borderRadius: '50%',
backgroundColor: 'grey',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowOpacity: 0.3,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 3,
}}
>
<TouchableOpacity onPress={() => Alert.alert('hello world')}> // This is the button that I want use for useful things
<View style={[styles.iconContainer, styles.paddingBottom10]}>
<FontAwesomeIcon
icon={faPlus}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={32}
/>
<Text style={styles.iconText}>{'Add Sport'}</Text>
</View>
</TouchableOpacity>
</View>
),
}}
/>
<Tab.Screen
name="Journal"
component={LogbookStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} title={'Journal'} icon={faPenAlt} />
),
}}
/>
</Tab.Navigator>
);
};
And here is what the LogbookStackNavigator looks like:
const LogbookStack = createStackNavigator();
const LogbookStackNavigator = () => {
return (
<LogbookStack.Navigator
screenOptions={{
...defaultNavOptions,
headerBackTitleVisible: false,
}}
>
<LogbookStack.Screen
name="Screen1"
component={screen1Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen2"
component={screen2Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen3"
component={screen3Component}
options={entryScreenOptions}
/>
<LogbookStack.Screen
name="Screen4"
component={screen4Component}
options={SaveLogbookScreenOptions}
/>
<LogbookStack.Screen
name="Screen5"
component={screen1Component5}
options={defaultNavOptions}
/>
</LogbookStack.Navigator>
);
};
I know how to use navigation.setOptions, but it only affects the immediate parent navigator, not the grandparent navigator.
Another thing I tried was to make the big circle button on the page itself, but it always rendered underneath the Tab Navigator. If there was a way to make it render above, I think I could just use that. I tried 'position: 'absolute', etc and it always rendered underneath the tab navigator. As it is, I had to basically make a dummy screen in the tab navigator to give me the button on top.
What I need to be able to do, is use big circle button on the Tab Navigator, to navigate to different screens in the LogbookStackNavigator. How do I do that?
Also, I need the title to change from "Add Sport" to "Add " depending on what screen the LogbookStackNavigator is on. How do I do that?
Thanks for your help
Finally figured this out. You have to use react-native-portalize. Just wrap the elements you want to be rendered on top in a
<Portal></Portal>. This will place it above a Bottom Tab navigator.
import { Portal } from 'react-native-portalize';
const FooterButton = () => {
return(
<Portal>
<View>
<Text>I appear above the Tab Navigator!</Text>
</View>
</Portal>
);
export default FooterButton;
Don't forget to wrap the whole app in the the Host:
//In app.js
import { Host } from 'react-native-portalize';
const App = () => {
return (
<Host>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</Host>
)
}
export default App;
NOTE: The elements inside the Portal, do not clear when the navigator navigates to another screen. So to get around this, you have to only display the Portal, when the screen is active. Thankfully React Navigation 5+ provides a useIsFocused hook that accomplishes this perfectly.
import { Portal } from 'react-native-portalize';
import { useIsFocused } from '#react-navigation/native';
const FooterButton = () => {
const isFocused = useIsFocused();
// Only display the button when screen is focused. Otherwise, it doesn't go away when you switch screens
return isFocused ? (
<Portal>
<View style={styles.buttonContainer}>
<View style={styles.footer}>{props.children}</View>
</View>
</Portal>
) : null;
};
export default FooterButton;
If you want a modal-style popup, you can wrap react-native-modalize and wrap it with react-native-modalize
Thanks to livin52 on Reddit for the solution
I'm new to react-native and I was playing around with react-navigation. I have a problem with positioning of the back-arrow in the navigation tab. I would like to target the back-arrow in order to position it.
Here is what I've done so far
static navigationOptions = ({navigation}) => {
return {
headerTitle: navigation.state.params.navTitle,
headerStyle: {
height: '45%',
backgroundColor: '#ffae19'
},
headerTintColor: 'white',
// this what I tried to implement
headerTitleStyle: { position: 'absolute', top: 10 }
}
}
You see I just need to make the back-arrow positioned at the top, because in my current tab the arrow is at the center of the nav-tab (vertically), which looks ugly. Any help ?
You cannot directly change the style of the automatic back arrow. However, you can override the back arrow with your custom component, as explained on React Navigation docs. The article is about right part of the bar, but as stated in the last part, the same holds for the left part of the bar, where the arrow is placed.
static navigationOptions = ({navigation}) => {
return {
headerTitle: navigation.state.params.navTitle,
headerStyle: {
height: '45%',
backgroundColor: '#ffae19'
},
headerTintColor: 'white',
headerLeft: (
<Button onPress={() => navigation.goBack()} title="Back" />
)
}
}
If you don't like the "Back" label, you can install react-native-vector-icons using npm and modify the previous code like
static navigationOptions = ({navigation}) => {
return {
headerTitle: navigation.state.params.navTitle,
headerStyle: {
height: '45%',
backgroundColor: '#ffae19'
},
headerTintColor: 'white',
headerLeft: (
<TouchableWithoutFeedback
style={{ /* Put your style here */}}
onPress={() => navigation.goBack()} >
>
<Icon name="md-arrow-round-back" size={16} color="#000" />
</TouchableWithoutFeedback>
)
}
}
Don't forget to import icons
import Icon from 'react-native-vector-icons/Ionicons;
I have a StackNavigator, where I've specified the same headerRight Icon for every screen:
export default StackNavigator(
{
Authorization: {
screen: AuthorizationScreen
},
SignIn: {
screen: SignInScreen
},
SignUp: {
screen: SignUpScreen
},
Main: {
screen: MainScreen
},
Language: {
screen: LanguageScreen
},
//...etc
},
{
navigationOptions: {
headerRight: (
<Icon color={'#77767c'}
name='ios-contact-outline'
size={30}
style={{ paddingRight: 30}}
type='ionicon'
/>
),
}
}
)
All the screens are imported from separate files. When this Icon is pressed, I want the same component to render regardless of what screen I'm on. The problem is, I can't think of a way to do this outside of writing in some kind of state handling and onPress function for every single screen I have, which would be really tedious to write and maintain. Is there any way to get around this and only write the component rendering once?
You can create one component for Header and pass it into all the screen's navigationOptions. Then you just need to handle the method on each screen and you can do your stuff at here.
Custom Header Class:
class Header extends Component {
render() {
const props = this.props;
return (<View>
<View
style={KEEP_YOUR_STYLE}
/>
<View style={styles.containerStyle} >
<Text
style={your_style}
numberOfLines={1}
>TITLE</Text>
<TouchableOpacity
style={styles.touchableOpacityStyle}
onPress={props.onPress}
>
<Image
source={YOUR_ICON}
style={{
position:'absolute',
right: 10,
width: 20,
height: 20,
resizeMode: 'cover',
}
} />}
</TouchableOpacity>
</View>
</View>
);
}
}
export { Header };
In your StackNavigator:
const defaultNavigation = ({ navigation }) => ({
header: (<Header
title='Hellow'
/>),
});
Language: {
screen: LanguageScreen,
navigationOptions: defaultNavigation,
},
In your particular Screen:
static navigationOptions = ({ navigation }) => ({
header: (
<Header
title='Your Title'
onPress={() => {
// DO YOUR STUFF
}}
/>),
});
How can we open a link from a navigator entry? For example, like:
const Home = DrawerNavigator ({
Account: { screen: Account },
Availability: { screen: Availability },
Favorites: { screen: Favorites },
Website: { screen: Linking.openURL('http://www.example.com') }, }
I know this was asked a while ago, but I've recently come across this need and found a working solution.
You want to first establish the drawer with createDrawerNavigator:
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem } from '#react-navigation/drawer';
import { Linking } from 'react-native';
const Drawer = createDrawerNavigator();
With the latest version of react-native navigation, we can't pass fields to DrawerNavigator like you're doing. One way to set up the drawer and its navigation contents is like so:
return (
<Drawer.Navigator
initialRouteName="Account"
drawerPosition="right"
drawerContentOptions={{
activeTintColor: 'white',
inactiveTintColor: 'blue',
activeBackgroundColor: 'blue',
labelStyle: {
fontFamily: 'Arial',
fontSize: 18,
textTransform: 'uppercase',
paddingTop: 5
}
}}
drawerContent={props => <CustomDrawerContent {...props}/>}
>
<Drawer.Screen name="Account" component={Account} />
<Drawer.Screen name="Availability" component={Availability} />
<Drawer.Screen name="Favorites" component={Favorites} />
{/* Custom Links (defined next) */}
</Drawer.Navigator>
);
I've included my return statement to show that this is what I'm rendering on the screen. drawerContentOptions shows one of the parameters you can pass to the drawer. All options are defined here:
https://reactnavigation.org/docs/drawer-navigator/
Next, we want to create the custom drawer content that we referenced in one of the Drawer.Navigator props. You can also apply very similar props like we did earlier to the drawer navigation items. Keep in mind that all custom links are going to display below the Drawer.Screen components referenced/defined earlier. This is due to the line DrawerItemList {...props}> which takes in the defined list of navigation links and displays them before our custom links. You can reverse this so custom displays first, but I'm not sure if you can put custom links in the middle.
// set up custom links for main navigation drawer
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label="Website"
inactiveTintColor={'blue'}
labelStyle= {{
fontFamily: 'Arial',
fontSize: 18,
textTransform: 'uppercase',
paddingTop: 5
}}
onPress={() => Linking.openURL('http://www.example.com')}
/>
</DrawerContentScrollView>
);
}
I am using React Navigation in React Native app and I want to change the backgroundColor for the header to be gradient and I found out there is a node module : react-native-linear-gradient to achieve gradient in react native.
I have Root StackNavigator like that :
const Router = StackNavigator({
Login: {
screen: Login,
navigationOptions: ({navigation}) => ({
headerTitle: <Text>SomeTitle</Text>
headerLeft: <SearchAndAgent />,
headerRight: <TouchableOpacity
onPress={() => { null }
</TouchableOpacity>,
headerStyle: { backgroundColor: '#005D97' },
}),
},
});
I can wrap Text or View to be gradient like that :
<LinearGradient colors={['#3076A7', '#19398A']}><Text style={styles.title}>{title}</Text></LinearGradient>,
How can I wrap the header background in the navigationOptions to use
the the LinearGradient module?
I know that I can create a custom header component and use it but when I am doing it all the native navigation animations from React Navigation not working like the Title Animation between two Routes so its not helping me.
Thanks for helping !
Just for your information, now with headerBackground props it's a way easier.
You can have a gradient header just doing this :
navigationOptions: {
headerBackground: (
<LinearGradient
colors={['#a13388', '#10356c']}
style={{ flex: 1 }}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
/>
),
headerTitleStyle: { color: '#fff' },
}
This solution works good even with SafeArea for IosX
The solution of Mark P was right but now you need to define headerStyle and do the absolute positioning there:
navigationOptions: {
header: props => <GradientHeader {...props} />,
headerStyle: {
backgroundColor: 'transparent',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
},
and the GradientHeader:
const GradientHeader = props => (
<View style={{ backgroundColor: '#eee' }}>
<LinearGradient
colors={['red', 'blue']}
style={[StyleSheet.absoluteFill, { height: Header.HEIGHT }]}
>
<Header {...props} />
</LinearGradient>
</View>
)
Similar to this issue: React Navigation; use image in header?
For a Linear Gradient you would simply do >
//imports
import { Image, StyleSheet, View } from 'react-native';
import { Header } from 'react-navigation' ;
import LinearGradient from 'react-native-linear-gradient';
//header
Create the Header component which is wrapped in the Linear Gradient.
by making the header backgroundColor: 'transparent' you will then show the Linear Gradient wrapping it.
const GradientHeader = props => (
<View style={{ backgroundColor: '#eee' }}>
<LinearGradient
colors={['#00a8c3', '#00373f']}
style={[StyleSheet.absoluteFill, styles.linearGradient]}
/>
<Header {...props} style={{ backgroundColor: 'transparent' }}/>
</View>
);
Return the screen with the header being your GradientHeader component.
const SimpleStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},
}, {
navigationOptions: {
headerTitleStyle: { color: '#fff' },
header: (props) => <GradientHeader {...props} />,
}
});
Should look something like this with the above code.
Gradient Header
You can use LinearGradient component from the expo. It is a useful component and you can't install another library like react-native-linear-gradient. https://docs.expo.io/versions/latest/sdk/linear-gradient/. By the way, you can change the back button. It is easy.
You can implement it on inside screen with navigationOptions like that
static navigationOptions = ({ navigation }: any) => {
const onGoBack = () => navigation.goBack();
return {
header: (props: any) => <GradientHeader {...props} />,
headerStyle: { height: 68, backgroundColor: "transparent", color: colors.white },
headerTitle: "Sign Up",
headerTitleStyle: { color: colors.white, fontSize: 18 },
headerLeft: (
<TouchableOpacity style={{ width: 32, height: 32, paddingLeft: 8 }} onPress={onGoBack}>
<Image source={images.back} resizeMode="center" style={{ width: 32, height: 32 }} />
</TouchableOpacity>
),
};
};