ReactNative进阶(七):导航组件 react-navigation
一、前言
在项目开发过程中,经常会看到如下形式的路由跳转。
render() {
return (
2
)
}
在RN多页面应用程序开发过程中,页面跳转是通过路由或导航器来实现的。在0.44版本之前,开发者可以直接使用官方提供的组件来实现页面跳转,不过从0.44版本开始,被官方从的核心组件库中剥离出来,放到模块中。
如果开发者需要继续使用,则需要先执行命令后再使用。不过,官方并不建议开发者这么做,而是建议开发者直接使用导航库。是React Native社区非常著名的页面导航库,可以用来实现各种页面的跳转操作。
今天就结合官方推荐的路由导航组件,深入了解下相关技能知识。
二、总览
中,官方推荐使用 来实现各个界面的跳转和不同板块的切换。据称有原生般的性能体验效果。可能会成为未来导航组件的主流军。
目前,支持三种类型的导航器,分别是、和。具体区别如下:
1、: 类似于普通的,屏幕上方导航栏,用于实现各个页面之间的跳转;2、: 相当于里面的,屏幕下方的标签栏,主要用于同一个页面上不同界面之间的切换;3、 : 抽屉效果,侧边滑出;
三、StackNavigator 导航栏
StackNavigator(RouteConfigs, StackNavigatorConfig)
组件采用堆栈式的页面导航来实现各个界面跳转。如果想要使用,一定要先注册导航。
其中,用于配置栈管理的页面。在模式下,为了方便对页面进行统一管理,首先新建一个文件,并使用注册页面。对于应用的初始页面还需要使用进行申明。同时,导航器栈还需要使用函数进行包裹。示例代码如下:
import {createAppContainer,createStackNavigator} from 'react-navigation';
import MainPage from './MainPage'
import DetailPage from "./DetailPage";
const AppNavigator = createStackNavigator({
MainPage: MainPage,
DetailPage:DetailPage
},{
initialRouteName: "MainPage",
},
);
export default createAppContainer(AppNavigator);
接下来,在入口文件中以组件的方式引入文件即可。例如:
import StackNavigatorPage from './src/StackNavigatorPage'
export default class App extends Component {
render() {
return (
);
}
}
然后,就可以进行导航配置了。
const Navigator = StackNavigator(
{
Tab: { screen: Tab },
Web: { screen: WebScene },
GroupPurchase: { screen: GroupPurchaseScene },
},
{
navigationOptions: {
// headerStyle: { backgroundColor: color.theme }
headerBackTitle: null,
headerTintColor: '#333333',
showIcon: true,
},
}
);
这里给导航器配置了三个界面,如果需要更多界面,可以在之后进行添加。主要有一个用于主界面之间能够切换的界面和其他两个能够展示数据的界面。
3.1 StackNavigatorConfig 导航器配置
- 导航器组件中初始显示页面的路由名称,如果不设置,则默认第一个路由页面为初始显示页面;
- 给初始路由的参数,在初始显示的页面中可以通过 来获取;
-路由页面的配置选项,它会被 参数中 的对应属性覆盖;
-路由中设置的路径覆盖映射配置;
- 页面跳转方式,有 和 两种,默认为 :
- 原生系统默认的的跳转,左右互换;
- 只针对iOS平台,模态跳转,上下切换;
- 页面跳转时,头部的动画模式,有 三种:
-渐变,类似的原生效果,无透明;
- 标题与屏幕一起淡入淡出,如微信一样;
- 没有动画;
-为各个页面设置统一的样式,比如背景色,字体大小等;
- 配置页面跳转的动画,覆盖默认的动画效果;
- 页面跳转动画即将开始时调用;
- 页面跳转动画一旦完成会马上调用;
代码示例:
const StackNavigatorConfig = {
initialRouteName: 'Home',
initialRouteParams: {initPara: '初始页面参数'},
navigationOptions: {
title: '标题',
headerTitleStyle: {fontSize: 18, color: '#666666'},
headerStyle: {height: 48, backgroundColor: '#'},
},
paths: 'page/main',
mode: 'card',
headerMode: 'screen',
cardStyle: {backgroundColor: "#ffffff"},
transitionConfig: (() => ({
screenInterpolator: CardStackStyleInterpolator.forHorizontal,
})),
onTransitionStart: (() => {
console.log('页面跳转动画开始');
}),
onTransitionEnd: (() => {
console.log('页面跳转动画结束');
}),
};
3.2 navigationOptions 为对应路由页面的配置选项
- 可以作为头部标题 ,或者标题 ;
-自定义的头部组件,使用该属性后系统的头部组件会消失,如果想在页面中自定义,可以设置为,这样就不会出现页面中留有一个高度为64 navigationBar的高度;
- 头部的标题,即页面的标题 - 返回标题,默认为 ;
- 返回标题不能显示时(比如返回标题太长了)显示此标题,默认为 “”;
- 头部右边组件;
- 头部左边组件;
- 头部组件的样式;
- 头部标题的样式;
- 头部返回标题的样式;
- 头部颜色 以上风格的波纹颜色 ;
- 否能侧滑返回, 默认 , 默认 ;
使用示例
// 注册导航
const Navs = StackNavigator({
Home: { screen: Tabs },
HomeTwo: {
screen: HomeTwo, // 必须, 其他都是非必须
path:'app/homeTwo', // 使用url导航时用到, 如 web app 和 Deep Linking
navigationOptions: {} // 此处参数设置会覆盖组件内的`static navigationOptions`设置. 具体参数详见下文;
},
HomeThree: { screen: HomeThree },
HomeFour: { screen: HomeFour }
}, {
initialRouteName: 'Home', // 默认显示界面
navigationOptions: { // 屏幕导航的默认选项, 也可以在组件内用 static navigationOptions 设置(会覆盖此处的设置)
header: { // 导航栏相关设置项
backTitle: '返回', // 左上角返回键文字
style: {
backgroundColor: '#fff'
},
titleStyle: {
color: 'green'
}
},
cardStack: {
gesturesEnabled: true
}
},
mode: 'card', // 页面切换模式, 左右是card(相当于iOS中的push效果), 上下是modal(相当于iOS中的modal效果)
headerMode: 'screen', // 导航栏的显示模式, screen: 有渐变透明效果, float: 无透明效果, none: 隐藏导航栏
onTransitionStart: ()=>{ console.log('导航栏切换开始'); }, // 回调
onTransitionEnd: ()=>{ console.log('导航栏切换结束'); } // 回调
});
当然页面配置选项 还可以在对应页面中去静态配置。
// 配置页面导航选项
static navigationOptions = ({navigation}) => ({
title: 'HOME',
titleStyle: {color: '#ff00ff'},
headerStyle:{backgroundColor:'#000000'}
});
render() {
return (
)
};
}
在页面采用静态方式配置 中的属性,会覆盖 构造函数中两个参数 和 配置的 里面的对应属性。
中属性的优先级是:
至此,就可以在组件中直接使用该配置了,当然我们也可以像文章开头提过的那样使用:
const Navigator = StackNavigator(RouteConfigs, StackNavigatorConfig);
export default class Main extends Component {
render() {
return (
)
};
}
至此,我们已经配置好导航器和对应的路由页面,但是我们还需要才能实现页面的跳转。
3.3 navigation 控制页面跳转
在导航器中的每一个页面,都有 属性,该属性有以下几个属性/方法,可以在组件下查看。
- 跳转到其他页面;
- 当前页面导航器的状态;
- 更改路由的参数;
- 返回;
- 发送一个;
this.props.navigation.navigate(‘Two’, { name: ‘two’ }) // push下一个页面
: 注册过的目标路由名称;
: 传递的参数,传递到下一级界面 ;
:如果该界面是一个的话,将运行这个;
里面包含有传递过来的参数 、 、路由名称 。
: 路由名;
: 路由身份标识;
: 参数;
该方法允许界面更改中的参数,可以用来动态更改导航栏的内容。比如可以用来更新头部的按钮或者标题。
返回上一页,可以不传参数,也可以传参数,还可以传 。
this.props.navigation.goBack(); // 回退到上一个页面
this.props.navigation.goBack(null); // 回退到任意一个页面
this.props.navigation.goBack('Home'); // 回退到Home页面
this.props.navigation.dispatch
可以一些,主要支持的有:
import { NavigationActions } from 'react-navigation'
const navigationAction = 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(navigationAction)
方法会清除原来的路由记录,添加上新设置的路由信息, 可以指定多个,是指定默认显示的那个路由页面, 注意不要越界!
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Profile'}),
NavigationActions.navigate({ routeName: 'Two'})
]
})
this.props.navigation.dispatch(resetAction)
为指定的更新参数,该参数必须是已经存在于的中。
import { NavigationActions } from 'react-navigation'
const setParamsAction = NavigationActions.setParams({
params: {}, // these are the new params that will be merged into the existing route params
// The key of the route that should get the new params
key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)
3.4 页面跳转,传值,回调传参
跳转,传值
const {navigate} = this.props.navigation;
{
navigate('PlanDetail',{name:'leslie',id:100});
}}>
// 返回上一页
this.props.navigation.goBack();
在下一界面接收参数通过 接收参数
export default class Home1 extends Component {
static navigationOptions = {
// title 可以这样设置成一个函数, state 会自动传过来
title: ({state}) => `${state.params.name}`,
};
componentDidMount() {
const {params} = this.props.navigation.state;
const id = params.id;
}
}
3.5 回调传参
当前界面进行跳转
navigate('Detail',{
// 跳转的时候携带一个参数去下个页面
callback: (data)=>{
console.log(data); // 打印值为:'回调参数'
}
});
下一界面在返回之前传参
const {navigate,goBack,state} = this.props.navigation;
// 在第二个页面,在goBack之前,将上个页面的方法取到,并回传参数,这样回传的参数会重走render方法
state.params.callback('回调参数');
goBack();
四、TabNavigator 即 Tab 选项卡
TabNavigator(RouteConfigs, TabNavigatorConfig)
和 类似,参数 是路由配置,参数 是选项卡配置。
如果要实现底部选项卡切换功能,可以直接使用提供的接口,并且此导航器需要使用函数包裹后才能作为组件被正常调用。例如:
import React, {PureComponent} from 'react';
import {StyleSheet, Image} from 'react-native';
import {createAppContainer, createBottomTabNavigator} from 'react-navigation'
import Home from './tab/HomePage'
import Mine from './tab/MinePage'
const BottomTabNavigator = createBottomTabNavigator(
{
Home: {
screen: Home,
navigationOptions: () => ({
tabBarLabel: '首页',
tabBarIcon:({focused})=>{
if(focused){
return(
//选中的图片
)
}else{
return(
//默认图片
)
}
}
}),
},
Mine: {
screen: Mine,
navigationOptions: () => ({
tabBarLabel: '我的',
tabBarIcon:({focused})=>{
…
}
})
}
}, { //默认参数设置
initialRouteName: 'Home',
tabBarPosition: 'bottom',
showIcon: true,
showLabel: true,
pressOpacity: 0.8,
tabBarOptions: {
activeTintColor: 'green',
style: {
backgroundColor: '#fff',
},
}
}
);
const AppContainer = createAppContainer(BottomTabNavigator);
export default class TabBottomNavigatorPage extends PureComponent {
render() {
return (
);
}
}
4.1 RouteConfigs 路由配置
路由配置和 中是一样的,配置路由以及对应的 页面, 为对应路由页面的配置选项:
- 标题,可用作 和 回退标题;
-是否可见,没有设置的话默认为 ;
- 的组件,可以根据 方法来返回一个组件;
-中显示的标题字符串或者组件,也可以根据 ;方法返回一个组件;
代码示例:
Mine: {
screen: MineScene,
navigationOptions: ({ navigation }) => ({
tabBarLabel: '我的',
tabBarIcon: ({ focused, tintColor }) => (
)
}),
},
自定义组件
class TabBarItem extends PureComponent {
render() {
let selectedImage = this.props.selectedImage ? this.props.selectedImage : this.props.normalImage
return (
);
}
}
五、TabNavigatorConfig Tab选项卡配置
- Tab选项卡组件,有 和 两个值,在中默认为 ,在中默认为 。
- 在页面顶部;
- 在页面底部;
- Tab选项卡的位置,有或两个值
:上面
:下面
- 是否可以滑动切换选项卡;
- 点击选项卡切换界面是否需要动画;
- 是否懒加载页面;
- 初始显示的对应的页面路由名称;
- 用路由名称数组来表示选项卡的顺序,默认为路由配置顺序;
- 路径配置;
- 点击返回键时的处理,有 和 两个值:
- 返回初始界面;
- 退出;
- Tab配置属性,用在和时有些属性不一致:
用于 时:
- 选中的文字颜色;
- 未选中的文字颜色;
-是否显示图标,默认显示;
- 是否显示标签,默认显示;
- 是否使用大写字母,默认使用;
- 以上的MD风格波纹颜色;
- 以下或者按下的透明度;
- 是否可以滚动;
- 单个Tab的样式;
- 指示器的样式;
- 标签的样式;
- 的样式;
-整个TabBar的样式;
用于 时:
- 选中Tab的文字颜色;
- 未选中Tab的的文字颜色;
- 选中Tab的背景颜色;
-未选中Tab的背景颜色;
- 是否显示标题,默认显示;
- 整个TabBar的样式;
-标签的样式;
- 单个Tab的样式;
使用底部选项卡:
import React, {Component} from 'react';
import {StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";
export default class MainComponent extends Component {
render() {
return (
);
}
}
const TabRouteConfigs = {
Home: {
screen: HomeScreen,
navigationOptions: ({navigation}) => ({
tabBarLabel: '首页',
tabBarIcon: ({focused, tintColor}) => (
),
}),
},
NearBy: {
screen: NearByScreen,
navigationOptions: {
tabBarLabel: '附近',
tabBarIcon: ({focused, tintColor}) => (
),
},
}
,
Mine: {
screen: MineScreen,
navigationOptions: {
tabBarLabel: '我的',
tabBarIcon: ({focused, tintColor}) => (
),
},
}
};
const TabNavigatorConfigs = {
initialRouteName: 'Home',
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
lazy: true,
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
Tab: {
screen: Tab,
}
};
const StackNavigatorConfigs = {
initialRouteName: 'Tab',
navigationOptions: {
title: '标题',
headerStyle: {backgroundColor: '#5da8ff'},
headerTitleStyle: {color: '#333333'},
}
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
使用顶部选项卡:
import React, {Component} from "react";
import {StackNavigator, TabBarTop, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
export default class MainComponent extends Component {
render() {
return (
);
}
}
const TabRouteConfigs = {
Home: {
screen: HomeScreen,
navigationOptions: ({navigation}) => ({
tabBarLabel: '首页',
}),
},
NearBy: {
screen: NearByScreen,
navigationOptions: {
tabBarLabel: '附近',
},
}
,
Mine: {
screen: MineScreen,
navigationOptions: {
tabBarLabel: '我的',
},
}
};
const TabNavigatorConfigs = {
initialRouteName: 'Home',
tabBarComponent: TabBarTop,
tabBarPosition: 'top',
lazy: true,
tabBarOptions: {}
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
Tab: {
screen: Tab,
}
};
const StackNavigatorConfigs = {
initialRouteName: 'Tab',
navigationOptions: {
title: '标题',
headerStyle: {backgroundColor: '#5da8ff'},
headerTitleStyle: {color: '#333333'},
}
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
当然,除了支持创建底部选项卡之外,还支持创建顶部选项卡,此时只需要使用提供的即可。如果要使用实现抽屉式菜单功能,还可以使用提供的。
六、DrawerNavigator 抽屉导航
有一些都会采用侧滑抽屉来实现主页面导航,利用 在中可以很方便实现抽屉导航。
DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)
和的构造函数一样,参数配置也类似。
抽屉导航的路由配置 ,和 的路由配置完全一样, 对应路由页面配置, 对应页面的抽屉配置:
- 抽屉标题,和 、 一样;
-标签字符串,或者自定义组件, 可以根据 函数来返回一个自定义组件作为标签;
- 抽屉,可以根据 函数来返回一个自定义组件作为 ;
属性配置
- 抽屉宽度,可以使用获取屏幕宽度,实现动态计算; -抽屉位置,可以是 或者 ;
- 抽屉内容组件,可以自定义侧滑抽屉中的所有内容,默认为;
- 用来配置抽屉内容的属性。当用来配置 是配置属性选项:
- 抽屉栏目的路由名称数组,可以被修改;
- 当前选中页面的;
- 选中条目状态的文字颜色;
- 选中条目的背景色;
- 未选中条目状态的文字颜色;
- 未选中条目的背景色
- 条目按下时会调用此方法;
- 抽屉内容的样式; -抽屉的条目标题/标签样式;
- 初始化展示的页面路由名称;
-抽屉导航栏目顺序,用路由名称数组表示;
- 路径;- -点击返回键时的处理,有和两个值:
initailRoute:返回初始界面;
none :退出
抽屉导航示例:
import React, {Component} from 'react';
import {DrawerNavigator, StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";
export default class MainComponent extends Component {
render() {
return (
);
}
}
const DrawerRouteConfigs = {
Home: {
screen: HomeScreen,
navigationOptions: ({navigation}) => ({
drawerLabel : '首页',
drawerIcon : ({focused, tintColor}) => (
),
}),
},
NearBy: {
screen: NearByScreen,
navigationOptions: {
drawerLabel : '附近',
drawerIcon : ({focused, tintColor}) => (
),
},
},
Mine: {
screen: MineScreen,
navigationOptions: {
drawerLabel : '我的',
drawerIcon : ({focused, tintColor}) => (
),
},
}
};
const DrawerNavigatorConfigs = {
initialRouteName: 'Home',
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
lazy: true,
tabBarOptions: {}
};
const Drawer = DrawerNavigator(DrawerRouteConfigs, DrawerNavigatorConfigs);
const StackRouteConfigs = {
Drawer: {
screen: Drawer,
}
};
const StackNavigatorConfigs = {
initialRouteName: 'Drawer',
navigationOptions: {
title: '标题',
headerStyle: {backgroundColor: '#5da8ff'},
headerTitleStyle: {color: '#333333'},
}
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
6.1 扩展功能
默认不可滚动。要实现可滚动视图,必须使用自定义容器:
{
drawerWidth:200,
抽屉位置:“对”
contentComponent:props =>
ScrollView>
}
可以覆盖导航使用的默认组件,使用自定义导航组件:
import {DrawerItems} from 'react-navigation';
const CustomDrawerContentComponent = (props) => (
);
6.2 嵌套抽屉导航
如果嵌套,抽屉将显示在父导航下方。
自定义适配顶部导航栏标题测试中发现,在上标题栏的标题为居中状态,而在上则是居左对齐。所以需要修改源码,进行适配。的326行代码处,修改为如下:
title: {
bottom: 0,
left: TITLE_OFFSET,
right: TITLE_OFFSET,
top: 0,
position: 'absolute',
alignItems: 'center',
}
上面方法通过修改源码的方式其实略有弊端,毕竟扩展性不好。还有另外一种方式就是,在中设置为 ’ ‘即可解决。去除返回键文字显示
的91行代码处,修改为如下即可。
{
Platform.OS === 'ios' &&
title &&
{backButtonTitle}
}
动态设置头部按钮事件当我们在头部设置左右按钮时,肯定避免不了要设置按钮的单击事件,但是此时会有一个问题,是被修饰为类型的,所以在按钮的方法中不能直接通过来调用中的方法。如何解决呢?
在官方文档中,作者给出利用设置的思想来动态设置头部标题。我们可以利用这种方式,将单击回调函数以参数的方式传递到,然后在中利用来取出,并设置到即可:
componentDidMount () {
/**
* 将单击回调函数作为参数传递
*/
this.props.navigation.setParams({
switch: () => this.switchView()
});
}
/**
* 切换视图
*/
switchView() {
alert('切换')
}
static navigationOptions = ({navigation,screenProps}) => ({
headerTitle: '企业服务',
headerTitleStyle: CommonStyles.headerTitleStyle,
headerRight: (
navigation.state.params.switch() }/>
),
headerStyle: CommonStyles.headerStyle
});
结合BackHandler处理返回和点击返回键两次退出App效果点击返回键两次退出效果的需求屡见不鲜。相信很多人在下实现该功能都遇到了很多问题,例如,其他界面不能返回。也就是手机本身返回事件在之前拦截了。如何结合实现呢?和大家分享两种实现方式:
(1)在注册的界面中,注册
componentWillMount(){
BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid );
}
componentUnWillMount(){
BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid);
}
_onBackAndroid=()=>{
let now = new Date().getTime();
if(now - lastBackPressed < 2500) {
return false;
}
lastBackPressed = now;
ToastAndroid.show('再点击一次退出应用',ToastAndroid.SHORT);
return true;
}
(2)监听
/**
* 处理安卓返回键
*/
const defaultStateAction = AppNavigator.router.getStateForAction;
AppNavigator.router.getStateForAction = (action,state) => {
if(state && action.type === NavigationActions.BACK && state.routes.length === 1) {
if (lastBackPressed + 2000 < Date.now()) {
ToastAndroid.show(Constant.hint_exit,ToastAndroid.SHORT);
lastBackPressed = Date.now();
const routes = [...state.routes];
return {
...state,
...state.routes,
index: routes.length - 1,
};
}
}
return defaultStateAction(action,state);
};
实现中界面跳转左右切换动画在中默认的界面切换动画是上下。如何实现左右切换呢?很简单的配置即可:
import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
然后在配置下添加如下代码:
transitionConfig:()=>({
screenInterpolator: CardStackStyleInterpolator.forHorizontal,
})
解决快速点击多次跳转当我们快速点击跳转时,会开启多个重复的界面,如何解决呢?其实在官方中也有提示,解决这个问题需要修改源码。
找到文件夹中的文件,替换为如下文本即可:
export default function(navigation: NavigationProp) {
// 添加点击判断
let debounce = true;
return {
...navigation,
goBack: (key?: ?string): boolean =>
navigation.dispatch(
NavigationActions.back({
key: key === undefined ? navigation.state.key : key,
}),
),
navigate: (routeName: string,
params?: NavigationParams,
action?: NavigationAction,): boolean => {
if (debounce) {
debounce = false;
navigation.dispatch(
NavigationActions.navigate({
routeName,
params,
action,
}),
);
setTimeout(
() => {
debounce = true;
},
500,
);
return true;
}
return false;
},
/**
* For updating current route params. For example the nav bar title and
* buttons are based on the route params.
* This means `setParams` can be used to update nav bar for example.
*/
setParams: (params: NavigationParams): boolean =>
navigation.dispatch(
NavigationActions.setParams({
params,
key: navigation.state.key,
}),
),
};
}