本文是在react-navigation v2版本基础上编辑的。中文文档传送口
为了便于牢记react-navigation的功能,我将其分成了几个小块:
- StackNavigator
堆栈导航- Tab Navigator
Tab菜单- Drawer Navigator
侧边栏菜单- SwitchNavigator
- navigation
navigation属性- withNavigation
传递navigation- android物理返回键重写
- Stack navigator、Tab navigator等相互嵌套
- SafeAreaView
1.StackNavigator
stack navigator 为你的应用提供了一种在屏幕之间切换并管理导航历史记录的方式。 如果您的应用程序只使用一个 stack navigator ,则它在概念上类似于Web浏览器处理导航状态的方式 - 当用户与它进行交互时,应用程序会从导航堆栈中新增和删除页面,这会导致用户看到不同的页面。 Web浏览器和 React Navigation 工作原理的一个主要区别是:React Navigation 的 stack navigator 提供了在 Android 和 iOS 设备上,在堆栈中的路由之间导航时你期望的手势和动画。
创建一个stack navigator
通过createStackNavigator(RouteConfigs, StackNavigatorConfig)
函数,返回一个React组件。
RouteConfigs:
createStackNavigator({
// For each screen that you can navigate to, create a new entry like this:
Profile: {
// `ProfileScreen` is a React component that will be the main content of the screen.
screen: ProfileScreen,
// When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.
// Optional: When deep linking or using react-navigation in a web app, this path is used:
path: 'people/:name',
// The action and route params are extracted from the path.
// Optional: Override the `navigationOptions` for the screen
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile'`,
}),
},
...MyOtherRoutes,
});
screen添加到stack navigation中时,以key:object
的形式添加,key必须唯一,object必须属性:screen,选配属性path、navigationOptions。此处的navigationOptions是针对本screen的配置。
StackNavigatorConfig:
-
initialRouteName
:设置堆栈的默认屏幕。 必须匹配路由配置中的某个 Key。 -
initialRouteParams
: 初始路由的参数 -
navigationOptions
: 用于屏幕的默认导航选项。是对stack navigator内所有screen的配置,可重写。 -
mode
: 定义渲染和转换的样式;
card - 使用标准的 iOS 和 Android 屏幕转换。 这是默认设置。
modal -使屏幕从底部滑动, 这是一个常见的 iOS 模式。仅在 iOS 上工作, 对 Android 没有影响。 -
headerMode
: 指定标题栏的呈现方式:
float -呈现一个位于顶部的单个标题栏, 并在屏幕被更改时进行动画显示。这是 iOS 上常见的模式。
screen -每个屏幕都有一个标头, 并且标题栏随屏幕一起淡入和淡出。这是 Android 的常见模式。
none -不会呈现标题栏。
2. Tab Navigator
屏幕底部的一个简单的选项卡栏, 可以在不同的路由之间切换。路由被懒加载--他们的屏幕组件在第一次聚焦之前不会载入。(以底部选项卡进行说明)
-
创建方式
:有三种,分别对应了底部选项卡栏、屏幕底部的材料设计主题标签栏、屏幕顶部的材料设计主题标签栏。 -
initialRouteName
:第一次加载时初始选项卡路由的 routeName。 -
order
:定义选项卡顺序的 routeNames 数组。 -
backBehavior
:控制 "返回" 按钮是否会导致 Tab 页切换到初始 Tab 页? 如果是, 设置为 initialRoute, 否则 none。 默认为 initialRoute的行为。 -
tabBarComponent
: -可选,覆盖用作标签栏的组件。 -
BottomTabNavigatorConfig
:tabbar配置选项。 -
navigationOptions
:导航器内部的navigationOptions。
示例代码:
const MineStack = createStackNavigator(
{
Mine:MineScreen,
Setting:SettingScreen
}
)
const HomeStack = createStackNavigator(
{
Home:HomeScreen
}
)
const TabNavigator = createBottomTabNavigator(
{
Home2:HomeStack,
Detail: DetailScreen,
Mine: MineStack,
}
)
需要注意的是,TabNavigator中的tab页面是没有标题栏的。
3. Drawer Navigator
侧边抽屉式菜单导航。
drawerWidth
:抽屉的宽度或返回它的函数。drawerPosition
:选项为 左 或 右. 默认值为 左 位置。contentComponent
:用于呈现抽屉内容 (例如, 导航项) 的组件。 接收用于抽屉的 navigation 支柱。 默认为 DrawerItems。contentOptions
:配置抽屉内容useNativeAnimations
:启用本机动画。默认值为 true。drawerBackgroundColor
:使用某种颜色的抽屉背景。默认值为 白色。initialRouteName
:第一次加载时初始选项卡路由的 routeName。order
:定义选项卡顺序的 routeNames 数组。backBehavior
:控制 "返回" 按钮是否会导致 Tab 页切换到初始 Tab 页? 如果是, 设置为 initialRoute, 否则 none。 默认为 initialRoute的行为。navigationOptions
:页面导航选项.title
:可用作headerTitle和tabBarLabel的后备的通用标题。drawerLabel
:字符串,React 元素或给定{focused:boolean,tintColor:string}的函数返回一个React.Node,以显示在抽屉边栏中。 未定义时,使用场景titledrawerIcon
:React 元素或给定{ focused: boolean, tintColor: string }的函数返回一个 React.Node,用于在标签栏中显示。drawerLockMode
:指定抽屉的锁定模式。 这也可以通过在顶级路由器上使用 screenProps.drawerLockMode 动态更新。open/close
:通过navigation的api开启/关闭drawer navigator。
4. SwitchNavigator
SwitchNavigator的目的是一次只显示一个屏幕。默认情况下,它不处理返回操作,并在你切换时将路由重置为默认状态。
-
initialRouteName
:第一次加载时初始选项卡路由的 routeName。 -
resetOnBlur
:切换离开屏幕时,重置所有嵌套导航器的状态。 默认为true。 -
backBehavior
:控制 "返回" 按钮是否会导致 Tab 页切换到初始 Tab 页? 如果是, 设置为 initialRoute, 否则 none。 默认为none行为。
5. Navigation Prop
重要的是要强调导航道具没有传递到所有组件;只有屏幕组件自动接收navigation!
-
navigate
:转到另一个屏幕,如果已存在,转到已存在的屏幕,如果不存在,则增加。
navigation.navigate({routeName, params, action, key})
navigation.navigate(routeName, params, action)
-
addListener
:订阅导航生命周期的更新。
React Navigation发射事件以屏幕订阅它们的组件:willBlur、willFocus、didFocus、didBlur
const didBlurSubscription = this.props.navigation.addListener(
'didBlur',
payload => {
console.debug('didBlur', payload);
}
);
// Remove the listener when you are done
didBlurSubscription.remove();
The JSON payload:
{
action: { type: 'Navigation/COMPLETE_TRANSITION', key: 'StackRouterRoot' },
context: 'id-1518521010538-2:Navigation/COMPLETE_TRANSITION_Root',
lastState: undefined,
state: undefined,
type: 'didBlur',
};
-
isFocused
:如果屏幕聚焦则返回true,否则返回false。 -
state
:屏幕的当前状态/路由
{
// the name of the route config in the router
routeName: 'profile',
//a unique identifier used to sort routes
key: 'main0',
//an optional object of string options for this screen
params: { hello: 'world' }
}
-
dispatch
:向路由器发送一个动作
import { NavigationActions } from 'react-navigation';
const navigateAction = NavigationActions.navigate({
routeName: 'Profile',
params: {},
// navigate can have a nested navigate action that will be run inside the child router
action: NavigationActions.navigate({ routeName: 'SubProfileRoute' }),
});
this.props.navigation.dispatch(navigateAction);
-
push
:添加新路由到堆栈,不管堆栈中是否已有相同key的路由。
navigation.push(routeName, params, action)
-
pop
:带你到堆栈中的前一个屏幕。如果您提供一个数字“n”,它将指定多少屏幕将您带回堆栈。
navigation.pop(n)
-
popToTop
:将其调用以跳回堆栈中的顶部路径,关闭所有其他屏幕。。 -
replace
:用新的路由替换当前的路由。
navigation.replace(routeName, params, action)
如果当前导航器是堆栈导航器,navigation prop才具有额外的 push、pop、popToTop、replace。
6. withNavigation
withNavigation是一个高阶组件,它可以将 navigation 这个 prop 传递到一个包装的组件。 当你无法直接将navigation 这个 prop 传递给组件,或者不想在深度嵌套的子组件中传递它时,它将非常有用。
withNavigation (Component)
返回一个Component
import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';
class MyBackButton extends React.Component {
render() {
return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
}
}
// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);
7. android物理返回键重写
默认情况下,当用户按下 Android 物理返回键时,react-navigation 会返回到新页面,如果没有可返回的页面,则退出应用。 这是一个合理的默认行为,但有些情况下你可能想要实现自定义处理。
推荐的第三方依赖:react-navigation-backhandler
我的方式是:定义BaseComponent,需要重写android返回键的,集成该组件。
let lastBackPressed = 0;
export default class BaseComponent extends Component {
_didFocusSubscription;
_willBlurSubscription;
constructor(props) {
super(props);
this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
componentDidMount() {
this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
componentWillUnmount() {
this._didFocusSubscription && this._didFocusSubscription.remove();
this._willBlurSubscription && this._willBlurSubscription.remove();
}
onBackButtonPressAndroid = () => {
let now = new Date().getTime();
if(now - lastBackPressed < 2500) {
return false;
}
lastBackPressed = now;
ToastAndroid.show('再点击一次退出应用',ToastAndroid.SHORT);
return true;
};
}
其中使用到了navigation的addListener属性。
onBackButtonPressAndroid
返回true,则react-navigation将不会接收到返回事件,当返回false时,react-navigation将处理返回事件,返回上个路由或者退出应用。
8. Stack navigator、Tab navigator等相互嵌套
navigator之间的相互嵌套示例:
const DrawNavigator = createDrawerNavigator(
{
Draw1:DrawScreen1,
Draw2: DrawScreen2,
}
)
const MineStack = createStackNavigator(
{
Mine:MineScreen,
Setting:SettingScreen
}
)
const HomeStack = createStackNavigator(
{
Home:HomeScreen
}
)
HomeStack.navigationOptions={tabBarLabel: 'Home!',}
//****stack navigator嵌套在Tab navigator中
const TabNavigator = createBottomTabNavigator(
{
Home2:HomeStack,
Detail: DetailScreen,
Mine: MineStack,
},
)
TabNavigator.navigationOptions = {header:null}
//****Draw navigator 和Tab navigator 嵌套在stack navigator中
const RootStack = createStackNavigator(
{
Draw:DrawNavigator,
Tab: TabNavigator
},{
initialRouteName: 'Tab',
}
);
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
关注下TabNavigator.navigationOptions = {header:null}
代码设置,其效果是将父navigator(stack navigator)的标题栏隐藏。
Why?
TabNavigator是一个React组件,TabNavigator.navigationOptions配置的是TabNavigator组件的的导航配置,是相对于父navigator(组件)的导航配置。我们其称之为TabNavigator外部navigationOptions
。
那么内部的navigationOptions如何理解呢?
TabNavigator的内部navigationOptions
指的是tab navigator包含的组件(screen)中的tabBar相关的configs(tab navigator包含的screen是没有标题栏的)。那么tabNavigator的内部navigationOptions该如何设置呢?示例代码如下:
const TabNavigator = createBottomTabNavigator(
{
Home2:HomeStack,
Detail: DetailScreen,
Mine: MineStack,
},{
navigationOptions:{
title:'Test'
}
}
)
上述代码中navigationOptions是针对TabNavigator内部所有的screen而言,可在screen中重写效果。
关于外部navigationOptions
和内部navigationOptions
的概念,同样适用于其他navigator,如HomeStack.navigationOptions={tabBarLabel: 'Home!',}
,HomeStack是Tab navigator的包含的一个组件,外部的navigationOptions就是设置这个组件的tabbar配置。
9. SafeAreaView
默认情况下,React Navigation 会帮助确保您的应用程序在iPhoneX上正确显示。 它通过在可能与传感器集群(“齐刘海”)或 Home 主页指示器交互的UI元素内部使用SafeAreaView来实现。
当我们隐藏/自定义导航栏或选项卡栏时,可能存在以下问题:
Landscape Mode(横屏模式)时,可能的错误:
此时,我们可以 通过使用SafeAreaView
包装内容,避免这些问题。示例代码如下:
import { SafeAreaView } from 'react-navigation';
class App extends Component {
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.paragraph}>
This is top text.
</Text>
<Text style={styles.paragraph}>
This is bottom text.
</Text>
</SafeAreaView>
);
}
}
使用 forceInset
获得更多控制
在某些情况下,你可能需要更多控制应用哪些填充。 例如,你可以通过将forceInset 这个 prop 传递给SafeAreaView来移除底部填充。
<SafeAreaView style={styles.container} forceInset={{ bottom: 'never' }}>
<Text style={styles.paragraph}>
This is top text.
</Text>
<Text style={styles.paragraph}>
This is bottom text.
</Text>
</SafeAreaView>
forceInset使用 key(取值:top | bottom | left | right | vertical | horizontal)和value(取值:'always' | 'never')组成一个对象 或者,您可以通过传递一个整数来完全重写填充。