React Native 使用 React Navigation 实现界面导航与跳转
在浏览器中,我们可以通过 <a> 标签与 URL 实现不同页面之间的跳转,并利用浏览器的返回按钮返回之前浏览的页面。但是在 React Native 中,没有集成内嵌的全局栈来实现界面跳转,因此需要使用第三方库。React Navigation 就是一个源于 React Native 社区的用于实现界面导航的 JavaScript 库。
安装 React Navigation 到项目中:
yarn add react-navigation
# 或者通过 npm 安装:
# npm install --save react-navigation
React Navigation 常用有三个组件:StackNavigator 用于实现页面跳转,TabNavigator 用于标签页之间切换,DrawerNavigator 用于实现抽屉式侧边栏。使用前需要先引入组件:
import { DrawerNavigator, TabNavigator, StackNavigator } from 'react-navigation'
1、StackNavigator
StackNavigator 组件用于实现在不同的页面之间的跳转,并且对页面历史进行管理,类似于浏览器的栈机制。当你打开一个新页面时,将页面压入栈顶;当需要返回时,从栈顶弹出页面。React Navigation 提供了更真实的移动设备用户体验,如屏幕手势操作和动画切换效果。
1.1、定义路由
StackNavigator() 接收两个参数:需要跳转管理的路由模块以及 Navigator 的基本设置,返回一个 React 组件。因此,可以将所有页面作为路由模块放在 StackNavigator 中形成根路由 RootStack,然后将其作为 React Native 的入口导出。
在每个路由模块中,通过 screen 关键字定义对应渲染的 React Native 组件。
const RootStack = StackNavigator(
{
Home: {
screen: HomeScreen,
},
Details: {
screen: DetailsScreen,
},
},
{
initialRouteName: 'Home',
}
);
export default class App extends Component {
render() {
return <RootStack />;
}
}
嵌套路由:由于 StackNavigator 返回一个 React 组件,它可以作为子组件添加到另一个 StackNavigator 路由中,形成层叠路由结构。例如,HomeScreen 与 DetailsScreen 构成 MainStack,MainStack 与 ModalScreen 构成 RootStack。
1.2、路由跳转
StackNavigator 会为每个注册的路由组件传递参数 navigation 属性。通过 this.props.navigation.navigate() 方法可以实现页面间的跳转,参数为 StackNavigator 中已定义的路由。例如,从主页跳转到详情页 Details:
<Button title='跳转到详情' onPress={() => this.props.navigation.navigate('Details')} />
返回:每个界面的头部导航栏左边默认设置了一个返回按钮,通过它可以返回到之前一个页面。手动触发返回可以调用 this.props.navigation.goBack()。
路由跳转实际上就是新打开一个页面并将路由压入栈中;点击返回时,从栈顶弹出一个页面。与浏览器不同,当给 navigate 传递的参数是本页面时,它依旧会压入栈内,点击返回时会弹出本页面。
1.3、参数传递
页面跳转时,可以把需要传递的数据作为参数放在 navigate 方法的第二个参数中:
<Button
title='跳转到详情'
onPress={() => this.props.navigation.navigate('Details', {
userName: 'Tory',
userInfo: 'Hello'
})}
/>
在路由页面内,通过 this.props.navigation.state.params 可以得到上一个页面传入的参数:
class DetailsScreen extends Component {
render() {
const data = this.props.navigation.state.params;
return (
<View style={styles.container}>
<Text>你好,{data.userName}!</Text>
</View>
);
}
}
1.4、导航栏设置
通过组件内的静态常量 navigationOptions 可以对组件的导航栏进行设置。
1.4.1、设置与修改
静态对象:navigationOptions 可以直接接收一个对象,例如设置详情页导航栏的标题:
class DetailsScreen extends Component {
static navigationOptions = {
title: '详情页'
};
}
函数返回:通过函数的方式返回 navigationOptions 对象,可以访问 props 中的参数:
static navigationOptions = (props) => {
return {
title: props.navigation.state.params.title
};
};
修改 navigationOptions:通过 this.props.navigation.setParams() 方法可以对 params 进行修改,例如修改标题:
<Button
title='修改标题'
onPress={() => {
this.props.navigation.setParams({ title: '修改后的标题' })
}}
/>
1.4.2、navigationOptions 属性
static navigationOptions = {
title: '详情页',
header: HeaderComponent, // 自定义头部组件
headerTitle: TitleComponent, // 自定义标题组件
headerLeft: LeftComponent, // 自定义左边组件,会替换掉默认返回按钮
headerRight: <Text>右边元素</Text>, // 自定义右边元素,注意这里不可以放组件
headerBackImage: { uri: 'mipmap/ic_launcher' }, // 自定义返回按钮的图片
headerStyle: { // 导航栏样式设置
backgroundColor: '#8bc9ff',
},
headerTintColor: '#fff', // 按钮、标题颜色设置
headerTitleStyle: { // 标题字体样式设置
fontWeight: 'bold',
},
headerTransparent: true, // 使头部背景透明
gesturesEnabled: true, // 开启手势操作
gestureDirection: 'inverted', // 修改返回手势操作方向为从右到左,默认为从左到右
gestureResponseDistance: { // 定义手势响应距离屏幕边界的距离
horizontal: 100,
vertical: 50
}
};
如果在组件内部定义 navigationOptions,它只会对当前页面起作用。如果希望对所有组件设置通用 options,可以把 navigationOptions 对象放在路由定义中。页面内的 navigationOptions 比通用设置具有更高的优先级。
注意自定义组件返回的是 JSX 元素,而不是 React Component 类。
const RootStack = StackNavigator(
{
Home: { screen: HomeScreen },
Details: { screen: DetailsScreen },
},
{
initialRouteName: 'Home',
navigationOptions: {
headerStyle: {
backgroundColor: '#7276ff'
}
}
}
);
1.5、头部与组件通信
在 navigationOptions 中设置的组件无法通过 this 访问到页面组件。如果希望二者之间进行通信,则需要借助 navigation.params:
class DetailsScreen extends React.Component {
state = { count: 0 };
static navigationOptions = (props) => {
const params = props.navigation.state.params;
return {
headerRight: (
<Button onPress={params.increase} title="+1" />
),
};
};
componentWillMount() {
this.props.navigation.setParams({ increase: this._increase });
}
_increase = () => {
this.setState(preState => { return { count: preState.count + 1 } });
};
render() {
return (
<View style={styles.container}>
<Text>计数为:{this.state.count}</Text>
</View>
);
}
}
2、TabNavigator
React Navigation 提供了 TabNavigator 来实现不同标签页之间的跳转。安卓的标签栏默认显示在头部,iOS 在底部。
2.1、定义路由组件
同 StackNavigator 一样,使用 TabNavigator 首先需要定义每个路由页面以及其对应的组件。TabNavigator 方法的第一个参数是所有标签页的路由,第二个为设置选项,返回一个 React 组件,可以作为入口。
export default TabNavigator(
{
Home: { screen: HomeScreen },
Message: { screen: MessageScreen }
},
{
// TabNavigator 的设置
}
);
在 index.js 中引入:
import { AppRegistry } from 'react-native';
import TabNavigator from './TabNavigation';
AppRegistry.registerComponent('Navigation', () => TabNavigator);
TabNavigator 可以与 StackNavigator 嵌套使用。例如,进入 App 后有两个标签页 Home 与 Message,点击 Home 中的按钮跳转到详情页 Detail,因此 HomeScreen 与 DetailScreen 通过 StackNavigator 构成一个 HomeStack,然后与 MessageScreen 一起构成 TabNavigator:
const HomeStack = StackNavigator(
{
Home: { screen: HomeScreen },
Detail: { screen: DetailScreen }
}
);
export default TabNavigator(
{
Home: { screen: HomeStack },
Message: { screen: MessageScreen }
}
);
2.2、TabNavigator 的设置
在 TabNavigator() 的第二个参数可以对标签栏进行配置:
export default TabNavigator(
{
Home: { screen: HomeStack },
Message: { screen: MessageScreen }
},
{
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
animationEnabled: true,
swipeEnabled: true,
initialRouteName: 'Home',
tabBarOptions: {
style: {
backgroundColor: '#49a9ff',
},
tabStyle: {
width: 150
},
labelStyle: {
fontSize: 16
},
iconStyle: {
width: 20,
},
activeTintColor: 'blue',
activeBackgroundColor: 'white',
inactiveTintColor: 'white',
inactiveBackgroundColor: 'blue',
pressColor: '#9dbbff',
showLabel: false,
showIcon: true,
}
}
);
在每个路由中,可以通过 navigationOptions 对 tabBar 标签进行其他设置:
Find: {
screen: FindScreen,
navigationOptions: {
title: '消息',
tabBarVisible: false,
swipeEnabled: true,
tabBarIcon: (tab) => renderIcon(tab, 'message'),
tabBarLabel: '消息页',
tabBarOnPress: (obj) => tapTab(obj)
}
},
渲染标签的 icon:在 TabNavigator 设置中开启显示 icon,通过 tabBarIcon 属性对应的方法渲染 icon。定义 renderIcon 方法:
function renderIcon(tab, component) {
let iconSrc = '';
if (tab.focused) {
iconSrc = component + '_highlighted';
} else {
iconSrc = 'tabbar_' + component;
}
return <Image source={{ uri: 'mipmap/' + iconSrc }} style={{ width: 30, height: 30 }} />;
}
标签页跳转:除了滑动切换,还可以通过 this.props.navigation.navigate('组件名') 手动跳转。
3、DrawerNavigator
DrawerNavigator 用于实现屏幕侧边栏拉出的导航效果。
3.1、定义路由组件
在 DrawerNavigator 的路由之间实现跳转,需要定义路由组件。DrawerNavigator() 方法接收两个参数:第一个为路由组件,第二个为参数设置,返回一个 React 组件,作为程序的默认入口。
3.2、打开侧边栏
除了滑动打开,还可以通过函数手动打开侧边栏:
this.props.navigation.navigate('DrawerOpen'); // 打开侧边栏
this.props.navigation.navigate('DrawerClose'); // 关闭侧边栏
this.props.navigation.navigate('DrawerToggle'); // 切换侧边栏打开/关闭
3.3、DrawerNavigator 个性化设置
在构造方法的第二个参数进行设置:
export default DrawerNavigator(
{
Home: { screen: HomeScreen },
Notifications: { screen: NotificationsScreen },
},
{
drawerWidth: 200,
drawerPosition: 'right',
contentComponent: CustomDrawer,
drawerBackgroundColor: '#c8eaff',
contentOptions: {
activeTintColor: '#936eff',
activeBackgroundColor: '#8fc3ff',
inactiveTintColor: '#598dff',
inactiveBackgroundColor: '#c1e1ff',
itemsContainerStyle: {
borderTopWidth: 2,
borderTopColor: '#5153ff'
},
itemStyle: {
borderBottomWidth: 2,
borderBottomColor: '#41a6ff'
},
labelStyle: {
fontSize: 16
},
iconContainerStyle: styles.icon,
}
}
);
在每个组件内对侧边栏标签的 label、icon 进行设置:
class HomeScreen extends React.Component {
static navigationOptions = {
drawerLabel: '主页',
drawerIcon: ({ focused, tintColor }) => (
<Image
source={{ uri: 'mipmap/tabbar_home' }}
style={[styles.icon, { tintColor: tintColor }]}
/>
),
};
}
3.4、自定义侧边栏
通过 contentComponent 自定义 DrawerNavigator 组件:
class CustomDrawer extends Component {
constructor(props) {
super(props);
}
render() {
return (
<ScrollView>
<SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}>
<View style={{ flex: 1, alignItems: 'center' }}>
<Image source={{ uri: 'mipmap/user_icon' }} style={styles.userPic} />
</View>
<DrawerItems {...this.props} />
</SafeAreaView>
</ScrollView>
);
}
}
引入组件:
import { DrawerItems, SafeAreaView } from 'react-navigation'
把自定义内容放在 <SafeAreaView> 中,通过 <DrawerItems> 绘制标签列表。
代码的 GitHub 链接:https://github.com/SuperTory/ReactNativeNavigation