在浏览器中我们可以通过标签与url实现不同页面之间的跳转,利用浏览器的返回按钮返回之前浏览的页面,但是在React Native中却没有集成内嵌的全局栈来实现这个界面的跳转,因此需要使用第三方库来实现这个功能。React Navigation 就是这样一个源于ReactNative社区的用于实现界面导航的js库。通过如下方法安装react navigation到你的项目中:
yarn add react-navigation
# 或者通过npm安装:
# npm install --save react-navigation
React Navigation常用有三个组件,其中StackNavigator用于实现页面跳转,TabNavigation用于标签页之间切换,DrawerNavigation用于实现抽屉式侧边栏。使用前需要先引入组件:
import {DrawerNavigator, TabNavigator, StackNavigator} from 'react-navigation'
1、StackNavigator
StackNavigator 组件用于实现在不同的页面之间的跳转,并且对页面历史进行管理,就像浏览器那样,用一个栈保存浏览页面。当你打开一个新页面时,将页面压入栈顶,当你需要返回时,从栈顶弹出页面。不同的是,通过React Navigation可以在移动设备上获得更加真实的用户体验,比如利用屏幕手势操作,以及更加逼真的动画切换效果。
1.1、定义路由
总体上看StackNavigator() 接收两个参数,分别是需要跳转管理的路由模块以及Navigator的基本设置,返回一个React Component组件。因此可以将你的所有页面作为路由模块放在StackNavigator中形成根路由RootStack,然后将其作为ReactNative的入口导出。
在每个路由模块中通过screen 关键字定义对应渲染的ReactNative组件
const RootStack = StackNavigator(
{//定义路由
Home : { //定义Home对应HomeScreen组件
screen : HomeScreen ,
},
Details : {
screen : DetailsScreen ,
},
},
{//定义配置
initialRouteName : 'Home' , //设置初始路由为Home
}
);
export default class App extends Component {
render () { //将Navigation作为根路径导出
return < RootStack /> ;
}
}
嵌套路由:由于StackNavigator返回一个React组件,因此它也可以作为子组件添加到另一个StackNavigator路由中,形成层叠路由结构。例如Home Screen与DetailScreen构成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 可以得到上一个页面传入的参数,例如在Details页面获取上面的数据:
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 : '详情页'
};
} 函数返回:由于上面通过static设置的静态常量,它不是组件的一个实例,无法通过this.props访问到navigation.state.params。但如果通过函数的方式返回navigationOptions对象,React Navigation在调用函数时会函数默认传入参数props,就可以拿到params。例如:
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对象放在前面的1.1提到的路由定义中。页面内的navigationOptions比通用设置具有更高的优先级。
注意自定义组件返回的是JSX元素,而不是React Component类。
const RootStack = StackNavigator(
{//定义路由
Home : {screen : HomeScreen },
Details :{screen : DetailsScreen },
},
{//定义配置
initialRouteName : 'Home' ,
navigationOptions :{ //导航栏通用设置
headerStyle :{
backgroundColor :'#7276ff'
}
}
}
);
1.5、头部与组件通信
在navigationOptions中设置的组件无法通过this访问到页面组件DetailsScreen,如果希望二者之间进行通信,则需要借助navigation.params。例如:
class DetailsScreen extends React.Component {
state ={count :0 };
static navigationOptions = (props) => {
const params = props.navigation.state .params;
return {
headerRight : ( //通过params为按钮绑定increase方法
< Button onPress = {params .increase } title ="+1" />
),
};
};
componentWillMount () { //通过setParams将increase方法绑定到_increase
this .props.navigation.setParams({ increase : this ._increase });
}
_increase =()=>{ //设置state.count+1
this .setState(preState=>{return {count :preState.count +1 }});
};
render () {
return (
< View style = {styles .container }>
< Text > 计数为:{this .state .count }Text >
View >
);
}
} 在navigationOptions中的
想要修改DetailsScreen的state.count,是无法通过this的,需要先给按钮绑定params.increase方法,然后在组件挂载前通过setParams将increase绑定到DetailsScree的内部方法this._increase,才可以访问修改state.count。
2、TabNavigator
React Navigation提供了TabNavigator 来实现不同标签页之间的跳转。安卓的标签栏默认显示在头部,IOS在底部。
2.1、定义路由组件
同StackNavigator一样,使用TabNavigator首先需要定义每个路由页面以及其对应的组件,Tabnavigator方法的第一个参数就是所有标签页的路由,第二个为设置选项,最后返回一个React Component,因此可以把它返回当作index.js的入口
//默认导出TabNavigator组件给index.js
export default TabNavigator (
{
Home :{screen :HomeScreen }, //标签页Home对应HomeScreen组件
Message :{screen :MessageScreen }
},
{
//TabNavigator的设置
}
)
//index.js中引入TabNavigator并定义为入口
import { AppRegistry } from 'react-native' ;
import TabNavigator from './TabNavigation' ;
AppRegistry.registerComponent ('Navigation' , () => TabNavigator);
TabNavigator可以与StackNavigator嵌套使用 ,比如进入App后有两个标签页Home与Message,点击Home中的按钮从Home跳转到详情页Detail,因此HomeScreen与DetailScreen通过StackNavigator构成了一个HomeStack,然后与MessageScreen一起又构成了TabNavigator:
const HomeStack =StackNavigator(
{
Home :{screen :HomeScreen },
Detail :{screen :DetailScreen }
}
);
export default TabNavigator (
{
Home :{screen :HomeStack }, //标签页Home对应HomeScreen组件
Message :{screen :MessageScreen }
}
)
2.2、TabNavigator的设置 在
TabNavigator() 的第二个参数可以对标签栏进行详细的配置
export default TabNavigator (
{
Home :{screen :HomeStack }, //标签页Home对应HomeScreen组件
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 , //将文字标签隐藏,默认为true开启
showIcon :true , //显示图标,默认为false隐藏
}
}
)
在每个路由可以分别通过navigationOptions 对tabBar标签进行其他设置:
Find :{
screen :FindScreen , //定义路由对应的组件
navigationOptions :{
title :'消息' , //设置标题
tabBarVisible :false , //隐藏标签栏,默认为true显示
swipeEnabled :true , //是否允许滑动切换标签页,默认接收TabNavigator中的设置
tabBarIcon :(tab)=>renderIcon (tab, 'message' ), //定义渲染Icon的方法
tabBarLabel :'消息页' , //定义标签文字或者渲染方法,如不设置默认渲染title
tabBarOnPress :(obj)=>tapTab(obj) //标签被点击时触发的方法
}
},
渲染标签的icon :首先需要在之前的TabNavigator()设置中开启显示icon,之后通过tabBarIcon属性对应的方法来渲染icon,默认传入参数{ focused: boolean, tintColor: string }
(我将它命名为tab),其中focused表示当前标签是否被选中,tintColor为前景色。我定义了renderIcon方法来实现Icon的渲染,并传入另外一个参数component代表不同组件,用于匹配对应的icon,例如当传入'message'且未激活时,iconSrc为'tabbar_message':
function renderIcon (tab, component){
console .log ('ictest' );
let iconSrc ='' ;
if (tab.focused){ //标签激活状态下icon的路径
iconSrc =component+'_highlighted' ;
}else { //未激活状态下的icon
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组件,将它暴露给index.js,作为程序的默认入口。
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' , //定义侧边栏位置右边,默认left左边
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 , //标签icon样式
}
}
);
在每个组件内对侧边栏标签的label、icon进行设置:
class HomeScreen extends React.Component {
static navigationOptions = {
drawerLabel : '主页' , //设置标签label文字
drawerIcon : ({focused, tintColor}) => ( //设置标签的icon
< Image
source = {{uri : 'mipmap/tabbar_home' }}
style = {[styles .icon , {tintColor : tintColor}]}
/>
),
};
}
3.4、自定义侧边栏
通过上面的contentComponent来自定义DrawerNavigator组件为CustomDrawer:
class CustomDrawer extends Component{
constructor (props){
super (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'
把自定义的侧边栏内容放在中。
如果希望保留导航标签可以通过绘制出标签列表,其中传入props参数数组,并用操作符"..."展开。
代码的GitHub链接如下:
https://github.com/SuperTory/ReactNativeNavigation