一、简言
在软件开发中,不论是Web还是App,它们的应用程序都是由很多的功能视图组成的。对于这些组合的视图,如何实现页面间平滑地过渡,应用都有统一的一套跳转机制,这个功能就是路由或者叫导航。应用程序通过导航,可以自由地实现页面之间的切换、前进和后退。在React中使用的是React Router,在iOS中使用的是UIKit的导航视图UINavigation和导航控制器。而在React-Native中,也提供了专门切换视图的导航栏组件NavigatorIOS和Navigator,这两个是兄弟组件,功能基本相同,区别在于封装性高低。NavigatorIOS组件的封装程度更高提供了基础的API和属性,开发者可以很方便的定制一个功能丰富的导航栏,而Navigator组件相比较而言比较简陋,它提供了足够的空间让开发者去定制,对于复杂的导航栏,它定制能力更高,而且可跨平台使用。其实,NavigatorIOS组件本质上是对UIKit的UINavigation的包装,使用NavigatorIOS组件进行页面的切换实质上就是调用了UIKit原生导航功能。本文将一一研究。
二、navigator对象
在组件视图进行切换的时候,navigator会作为一个属性对象被传递。我们可以通过this.props.navigator获取navigator对象。掌握navigator对象是使用NavigatorIOS组件和Navigator组件的前提基础,因为它可以控制器路由的跳转和组件的加载。navigator对象的主要API如下:
//加载一个新的页面或者视图或者路由,并跳转到该页面 push(route) //返回到上一个页面 pop() //一次性返回N个页面,也即返回到指定页面,当N=1,与pop()相同 popN(n) //替换当前的路由 replace(route) //替换前一个页面的视图并且回退过去 replacePrevious(route) //取代最顶层的路由并且回退回去 resetTo(route) //回到最顶层视图 popToTop()
三、NavigatorIOS组件
1、路由initialRoute默认是一个JS对象,代表一个页面或者视图,NavigatorIOS组件默认路由提供了initialRoute属性,如下所示:
import React, { Component } from 'react'; import { NavigatorIOS } from 'react-native'; export default class NavigatorBar extends Component{ render(){ return ( <NavigatorIOS initialRoute={{ component: MyComponent, title: "MyComponent Title", passProps: {myProp:"XYQ"} }} /> ); } } //component : 表示当前页面需要加载的组件视图 //title: 表示需要显示的标题 //passProps:表示用于页面间传递数据
2、NavigatorIOS组件属性有两大部分,分别是自身属性和持有的路由的属性,如下所示:
自身属性:
//导航条的背景颜色 barTintColor //初始化路由,它是一个JavaScript对象 initialRoute //每一页定制样式,可以设置每一个页面的背景颜色 itemWrapperStyle //是否隐藏导航栏 navigationBarHidden //是否隐藏阴影 shadowHidden //导航栏上按钮的颜色设置 tintColor //导航栏上字体的颜色 titleTextColor //导航栏是否半透明
translucent
initialRoute的属性
//加载的视图组件 component:function //当前视图标题 title: string //传递的数据 passProps: object //后退按钮图标 backButtonIcon: Image.propTypes.source //后退按钮标题 backButtonTitle: string //左边按钮图标 leftButtonIcon: Image.propTypes.source //左边按钮标题 leftButtonTitle: string //左边按钮点击事件 onLeftButtonPress: function //右边按钮图标 rightButtonIcon: Image.propTypes.source //右边按钮标题 rightButtonTitle: string //右边按钮点击事件 onRightButtonPress: function //包裹样式 warpperStyle: [Object Object]
四、Navigator组件
1、注意事项:如果RN版本升级到0.43以上的话,Navigator已过期,不能直接从react-native里面获取了,而是作为一个单独的库模块,解决方案如下:
//安装 npm install react-native-deprecated-custom-components --save //已过期失效,推荐使用第二个命令进行安装 yarn add react-native-deprecated-custom-components //可使用 //导入 import {Navigator} from 'react-native-deprecated-custom-components'
2、Navigator组件提供的属性如下:
/** * 配置跳转动画 * (route, routeStack) => Navigator.SceneConfigs.FloatFromRight */ configureScene: PropTypes.func, /** * 渲染场景 * (route, navigator) => * <MySceneComponent title={route.title} navigator={navigator} /> */ renderScene: PropTypes.func.isRequired, /** * 配置路由 */ initialRoute: PropTypes.object, initialRouteStack: PropTypes.arrayOf(PropTypes.object), /** * 路由即将跳转时调用 */ onWillFocus: PropTypes.func, /** * 路由完成跳转时调用 */ onDidFocus: PropTypes.func, /** * 导航栏 */ navigationBar: PropTypes.node, /** * navigator对象 */ navigator: PropTypes.object, /** * 场景样式 */ sceneStyle: ViewPropTypes.style,
3、虽然Navigator组件很粗糙,但是它的定制性极强,可以
很好解决NavigatorIOS组件不能跨平台和自定义的问题。Navigator组件只提供跳转功能,界面需要开发者自己去定制。使用步骤大致如下:
//1、配置路由 initialRoute={{component:this.props.component, passProps:{}}} //2、配置跳转 configureScene={()=>{return Navigator.SceneConfigs.FloatFromBottom;}} //3、渲染场景 renderScene={(route, navigator) => { // ...扩展符, 作用:如果是对象,就获取对象中所有值,如果是数组,就获取数组中所有值 const Component = route.component; return ( <View style={{flex:1}}> //使用flex设置导航尺寸 <Component navigator={navigator} route={route} {... route.passProps}> </View> ) }
4、Navigator组件支持的跳转方向和类型有很多,如下所示:
Navigator.SceneConfigs.PushFromRight //(默认)
Navigator.SceneConfigs.FloatFromRight
Navigator.SceneConfigs.FloatFromLeft
Navigator.SceneConfigs.FloatFromBottom
Navigator.SceneConfigs.FloatFromBottomAndroid
Navigator.SceneConfigs.FadeAndroid
Navigator.SceneConfigs.HorizontalSwipeJump
Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
Navigator.SceneConfigs.VerticalUpSwipeJump
Navigator.SceneConfigs.VerticalDownSwipeJump
5、导航过渡过程中,可以配置的回调函数如下:
//每当导航切换完成或初始化之后,调用此回调,参数为新场景的路由 onDidFocus function //会在导航切换之前调用,参数为目标路由 onWillFocus function
五、使用NavigatorIOS组件
需求:新闻列表页跳转到新闻详情页。
index.ios.js程序入口文件
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import NavigatorBar from './src/NavigatorBar' import { AppRegistry, View } from 'react-native'; export default class ReactNativeDemo extends Component { render() { return ( <NavigatorBar/> ); } } AppRegistry.registerComponent('ReactNativeDemo', () => ReactNativeDemo);
Navigator.js创建的NavigatorIOS导航栏组件文件
import React, { Component } from 'react'; import NewsHome from './NewsHome' import { NavigatorIOS } from 'react-native'; export default class NavigatorBar extends Component{ render(){ return ( <NavigatorIOS style={{flex: 1}} //设置导航栏样式 barTintColor={'purple'} //设置导航栏颜色 titleTextColor={'white'} //设置导航标题颜色 itemWrapperStyle={{backgroundColor:'orange'}} //设置导航页面背景颜色 initialRoute={{ component: NewsHome, //设置路由,列表主页 title: "新闻列表", //设置导航标题 passProps: {} //传递数据 }} /> ); } }
NewsHome.js新闻列表页文件
import React, { Component } from 'react'; import List from './List' import { View, StyleSheet } from 'react-native'; /* * 此处使用扩展运算符...this.props,将属性navigator传递到List组件中,因为List组件中无法直接获取navigator组件对象 * */ export default class NewsHome extends Component{ render() { return ( <View style={styles.container}> <List title="1、多地校长、专家在京探讨全息教育" {...this.props}/> <List title="2、中国网络安全产业高峰论坛在京召开" {...this.props}/> <List title="3、启动四天多 北京冬奥赛会志愿者报名人数超46万" {...this.props}/> <List title="4、丰台消防救援支队筑牢辖区冬季火灾防线" {...this.props}/> </View> ); } } const styles = StyleSheet.create({ container:{ marginTop: 64, backgroundColor:'white' } });
List.js新闻列表组件文件
import React, { Component } from 'react'; import NewDetail from "./NewsDetail"; import { StyleSheet, View, Text } from 'react-native'; export default class List extends Component{ constructor(props){ super(props); this.push = this.push.bind(this); //绑定push事件,绑定方式有多种,这是其中方法之一 } //跳转到详情页 push(){ let {navigator} = this.props; //作用域本地化 navigator.push({ //navigator对象调用push函数 component: NewDetail, //新闻详情页 title:"详情页", //设置新闻详情页导航标题 leftButtonTitle:"返回", //设置新闻详情页导航左边按钮 rightButtonTitle:"消息", //设置新闻详情页导航右边按钮 onLeftButtonPress:(() => navigator.pop()), //返回上一页:列表主页,navigator对象调用pop函数 onRightButtonPress:(() => alert("welcome to you")), //弹出吐司 wrapperStyle: {backgroundColor:'yellow'} //设置页面背景色 }) } render() { return ( <View style={styles.list_item}> <Text style={styles.list_font} onPress={this.push}>{this.props.title}</Text> </View> ); } } const styles = StyleSheet.create({ list_item: { height: 60, borderBottomWidth: 1, borderBottomColor: '#DDD', justifyContent: 'center' }, list_font: { fontSize: 20, fontWeight: 'bold', color: 'black' } });
NewsDetail.js新闻详情页文件
import React, { Component } from 'react'; import { StyleSheet, View } from 'react-native'; export default class NewsDetail extends Component{ render() { return ( <View style={styles.container}/> ) } } const styles = StyleSheet.create({ container:{ marginTop: 64, backgroundColor:'white' } });
模拟器运行结果如下:
六、使用Navigator组件
1、无导航栏,只使用路由功能:
inde.ios.js
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import NavigatorCustomBar from './src/NavigatorCustomBar' import { AppRegistry } from 'react-native'; export default class ReactNativeDemo extends Component { render() { return ( <NavigatorCustomBar/> ); } } AppRegistry.registerComponent('ReactNativeDemo', () => ReactNativeDemo);
NavigatorCustomBar.js
import React, { Component } from 'react'; import { Navigator } from 'react-native-deprecated-custom-components'; import { View, Text, StyleSheet } from 'react-native'; /* * 封装Navigator * 所有的切换动画都是从底部往上,回退时从上往底部 * 这里通过{...passProps}模仿NavigatorIOS的passProps进行传值 * initialRoute中的component: 传递的入口组件 * * */ export default class NavigatorCustomBar extends Component{ render(){ return ( <Navigator style={{flex:1}} //设置样式 initialRoute={{component:HomePage, passProps:{}}} //配置路由 configureScene={()=> {return Navigator.SceneConfigs.FloatFromBottom;}} //设置跳转动画 renderScene={(route, navigator) => { //渲染页面场景 const Component = route.component; return ( <Component navigator={navigator} route={route} {...route.passProps}/> ) }} /> ) } } //首页 class HomePage extends Component{ render(){ return ( <View style={styles.home}> <Text style={styles.font} onPress={()=>this.props.navigator.push({component: DetailPage})}> HomePage </Text> </View> ); } } //详情页 class DetailPage extends Component{ render(){ return ( <View style={styles.detail}> <Text style={styles.font} onPress={()=>this.props.navigator.pop()}> DetailPage </Text> </View> ); } } //样式 const styles = StyleSheet.create({ home:{ flex: 1, backgroundColor:'red', justifyContent: 'center', alignItems:'center', }, detail:{ flex: 1, backgroundColor:'green', justifyContent: 'center', alignItems:'center', }, font:{ fontSize:28, color:'white' } });
2、有导航栏,且使用路由功能:
inde.ios.js
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import NavigatorCustomBar from './src/NavigatorCustomBar' import { AppRegistry } from 'react-native'; export default class ReactNativeDemo extends Component { render() { return ( <NavigatorCustomBar/> ); } } AppRegistry.registerComponent('ReactNativeDemo', () => ReactNativeDemo);
NavigatorCustomBar.js
import React, { Component } from 'react'; import { Navigator } from 'react-native-deprecated-custom-components'; import { View, Text, StyleSheet } from 'react-native'; /* * 封装Navigator * 所有的切换动画采用默认方式 * 这里通过{...passProps}模仿NavigatorIOS的passProps进行传值 * initialRoute中的component: 传递的入口组件 * */ export default class NavigatorCustomBar extends Component{ //1、路由初始化 initialRoute = { name:"HomePage", //路由名称 component:HomePage, //路由页面 index:0, //页面索引 passProps:{leftTitle:"返回",midTitle:"首页",rightTitle:""} //透传数据 }; //2、动画初始化 configureScene = () => { return Navigator.SceneConfigs.PushFromRight; } //3、场景初始化 renderScene = (route, navigator) => { const Component = route.component; return <Component navigator={navigator} route={route} {...route.passProps}/> } //4、Mapper初始化 navBarRouteMapper = { //左边按钮 LeftButton(route, navigator, index){ if (index <= 0) return; return ( <Text style={styles.left} onPress={ () => navigator.pop()}> {route.passProps.leftTitle} </Text> ); }, //中间标题 Title(route, navigator){ return ( <Text style={styles.mid}> {route.passProps.midTitle} </Text> ); }, //右边按钮 RightButton(route, navigator){ if(!route.passProps.rightTitle) return; return ( <Text style={styles.right} onPress={() => alert('welcome to me!')}> {route.passProps.rightTitle} </Text> ); } }; render(){ return ( <Navigator style={{flex:1}} //设置样式 initialRoute={this.initialRoute} //配置路由 configureScene={this.configureScene} //设置跳转动画 renderScene={this.renderScene} //渲染页面场景 navigationBar={ //设置导航栏Mapper <Navigator.NavigationBar style={styles.navContainer} routeMapper={this.navBarRouteMapper}/> } /> ) } } //首页 class HomePage extends Component{ //设置详情页导航栏 pushToDetailPage(){ this.props.navigator.push({ index:1, component: DetailPage, passProps:{ leftTitle: this.props.leftTitle, midTitle:"详情", rightTitle:"消息" } }) } render(){ return ( <View style={styles.home}> <Text style={styles.font} onPress={this.pushToDetailPage.bind(this)}> this is HomePage </Text> </View> ); } } //详情页 class DetailPage extends Component{ render(){ return ( <View style={styles.detail}> <Text style={styles.font} onPress={()=>this.props.navigator.pop()}> this is DetailPage </Text> </View> ); } } //样式 const styles = StyleSheet.create({ navContainer:{ height:64, backgroundColor:'#41ABF7' }, left:{ paddingLeft:20, paddingTop:10, flex:1/3, fontSize:20, color:'white' }, mid:{ paddingTop:10, flex:1/3, fontSize:20, color:'white' }, right:{ paddingRight:20, paddingTop:10, flex:1/3, fontSize:20, color:'white' }, home:{ flex: 1, marginTop:64, backgroundColor:'orange', justifyContent: 'center', alignItems:'center', }, detail:{ flex: 1, marginTop:64, backgroundColor:'brown', justifyContent: 'center', alignItems:'center', }, font:{ fontSize:28, color:'white' } });