React-Native 之 Navigator和Navigat

作者: 珍此良辰 | 来源:发表于2016-11-24 18:34 被阅读3948次

    前言

    • 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习

    • 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所偏差,在学习中如果有错会及时修改内容,也欢迎万能的朋友们批评指出,谢谢

    • 文章第一版出自简书,如果出现图片或页面显示问题,烦请转至 简书 查看 也希望喜欢的朋友可以点赞,谢谢

    紧急提醒:

    • react-native 0.44 开始 不再支持 Navigatior,可以使用 react-native-Navigation 进行替代,具体使用方法过段时间会推出。

    Navigator 与 NavigatorIOS 介绍


    • 开发中,几乎所有的APP中或多或少都会涉及到多个界面间的切换,在React Native中有两个组件负责实现这样的效果 —— Navigator 和 NavigatorIOS

    • Navigator可以在iOS和Android同时使用,而NavigatorIOS则是包装了UIKit库的导航功能,使用户可以使用左划功能来返回到上一界面

    Navigator 属性


    • 官方文档中是这样解释的:使用导航器可以让你在应用的不同场景(页面)间进行切换。导航器通过路由对象来分辨不同的场景。利用renderScene方法,导航栏可以根据指定的路由来渲染场景

    • 可以通过configureScene属性获取指定路由对象的配置信息,从而改变场景的动画或者手势。查看Navigator.SceneConfigs来获取默认的动画和更多的场景配置选项

    • configureScene:可选的函数,用来配置场景动画和手势。会带有两个参数调用(一个是当前的路由,一个是当前的路由栈)然后它会返回一个场景配置对象

      • Navigator.SceneConfigs.PushFromRight(默认)


          (route, routeStack) => Navigator.SceneConfigs.FloatFromRight
      
      

      效果:


      PushFromRight.gif
      • Navigator.SceneConfigs.FloatFromLeft


          (route, routeStack) => Navigator.SceneConfigs.FloatFromLeft
      
      

      效果:


      FloatFromLeft.gif
      • Navigator.SceneConfigs.FloatFromBottom


          (route, routeStack) => Navigator.SceneConfigs.FloatFromBottom
      
      

      效果:


      FloatFromBottom.gif
      • Navigator.SceneConfigs.FloatFromBottomAndroid


          (route, routeStack) => Navigator.SceneConfigs.FloatFromBottomAndroid
      
      

      效果:


      FloatFromBottomAndroid.gif
      • Navigator.SceneConfigs.FadeAndroid


          (route, routeStack) => Navigator.SceneConfigs.FadeAndroid
      
      

      效果:


      FadeAndroid.gif
      • Navigator.SceneConfigs.HorizontalSwipeJump


          (route, routeStack) => Navigator.SceneConfigs.HorizontalSwipeJump
      
      

      效果:


      HorizontalSwipeJump.gif
      • Navigator.SceneConfigs.HorizontalSwipeJumpFromRight


          (route, routeStack) => Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
      
      

      效果:


      HorizontalSwipeJumpFromRight.gif
      • Navigator.SceneConfigs.VerticalUpSwipeJump


          (route, routeStack) => Navigator.SceneConfigs.VerticalUpSwipeJump
      
      

      效果:


      VerticalUpSwipeJump.gif
      • Navigator.SceneConfigs.VerticalDownSwipeJump


          (route, routeStack) => Navigator.SceneConfigs.VerticalDownSwipeJump
      
      

      效果:


      VerticalDownSwipeJump.gif
    • initialRoute:定义启动时加载的路由。路由是导航栏用来识别渲染场景的一个对象。 initialRoute 必须是 initialRouteStack(路由栈) 中的一个路由。initialRoute 默认为 initialRouteStack 中最后一项

    • initialRouteStack:提供一个路由集合用来初始化。如果没有设置初始路由的话则必须设置该属性。如果没有提供该属性,它将被默认设置成一个只含有 initialRoute 的数组

    • naviagtionBar:可选参数,提供一个在场景切换的时候保持的导航栏

    • navigator:可选参数,提供从父导航器获得的导航器对象

    • onDidFocus:每当导航切换完成或初始化之后,调用此回调,参数为新场景的路由

    • onWillFocus:会在导航切换之前调用,参数为目标路由器

    • renderScene:必要参数,用来渲染指定路由的场景。调用的参数是路由和导航器

          (route, navigator) => <MySceneComponent title={route.title} navigator={navigator} />
          
      
    • sceneStyle:将会应用在每个场景的容器上的样式

    Navigator 方法


    • 如果你得到一个 navigator对象 的引用,则可以调用许多方法来进行导航
      • getCurrentRoutes():获取当前栈里的路由,也就是push进来,没有pop掉的那些
      • jumpBack():跳回之前的路由,当前前提是保留现在的,还可以再跳回来,会给你保留原样
      • jumpForward():上一个方法不是盗用之前的路由,用这个就可以跳回来了
      • push(route):跳转到新场景,并且将场景入栈,你可以稍后跳转过去
      • pop():跳转回去并且卸载掉当前场景
      • replace(route):用一个新的路由替换掉当前场景
      • replaceAtIndex(route, index):替换掉指定序列的路由场景
      • replacePrevious(route):替换掉之前的场景
      • resetTO(route):跳转到新场景,并且重置整个路由栈
      • immediatelyResetRouteStack(routeStack):用新的路由数组来重置路由栈
      • popToRoute(route):pop到路由指定的场景,在整个路由栈中,处于指定场景之后的场景将会被卸载
      • popToTop():pop到栈中的第一个场景,卸载掉所有的其它场景

    Navigator 使用


    • 这边我们先来完成一个最基本的导航控制器,然后慢慢深入,做一个完整的导航控制器

    • 首先,我们先创建2个组件(home、Temp)并初始化组件,以供使用

      • home组件代码


          import React, { Component } from 'react';
          import {
              AppRegistry,
              StyleSheet,
              Text,
              View
          } from 'react-native';
      
          var Home = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          <Text>点击跳转</Text>
                      </View>
                  );
              }
          });
      
          var styles = StyleSheet.create({
              container: {
                  backgroundColor:'yellow',
                  flex:1,
                  justifyContent:'center',
                  alignItems:'center'
              },
          });
      
          module.exports = Home;
      
      
      • temp组件代码


          import React, { Component } from 'react';
          import {
              AppRegistry,
              StyleSheet,
              Text,
              View
          } from 'react-native';
          
          var Temp = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          <Text>点击返回</Text>
                      </View>
                  );
              }
          });
      
          var styles = StyleSheet.create({
              container: {
                  backgroundColor:'yellow',
                  flex:1,
                  justifyContent:'center',
                  alignItems:'center'
              },
          });
      
          module.exports = Temp;
      
      
    • 实例化 Navigator 需要2个必要的属性 —— initialRoute 和 renderSence,它们的作用分别是告诉导航器需要渲染的场景、根据路由描述渲染出来

          <Navigator
              style={{flex: 1}}       // 布局
              initialRoute={{
                  name:'Home',    // 名称
                  component:Home  // 要跳转的板块
              }}
      
              renderScene={(route, navigator) => {    // 将板块生成具体的组件
                      let Component = route.component;    // 获取路由内的板块
                      return <Component {...route.params} navigator={navigator} />    // 根据板块生成具体组件
              }}
          />
      
      
    • 实际上这样我们的导航器就已经创建完毕了,但是从界面上我们看不到导航栏,但是已经具备导航功能,我们分别进到 hometemp 文件中修改代码,看看是否真的已经实现导航功能

      • home代码


          // 引入外部文件
          var Temp = require('./Temp');
      
          var Home = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          <TouchableOpacity
                              onPress={() => {this.props.navigator.push({
                                  component:Temp
                              })}}
                          >
                              <Text>点击跳转</Text>
                          </TouchableOpacity>
                      </View>
                  );
              }
          });
      
      
      • temp代码


          var Temp = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          <TouchableOpacity
                              onPress={() => {this.props.navigator.pop()}}
                          >
                              <Text>临时页面</Text>
                          </TouchableOpacity>
                      </View>
                  );
              }
          });
      
      

      效果:


      navigator基本效果.gif
    • 在这里,我们还可以为导航器设置转场动画,以满足我们开发需求,官方已经提供了几种常用的转场动画,这边我们就取其中一种作示例,其它的效果可以参考上面的案例

          <Navigator
              initialRoute={{
                  name:'Home',    // 名称
                  component:Home  // 要跳转的板块
              }}
      
              configureScene={(route) => {    // 跳转动画
                  return Navigator.SceneConfigs.FloatFromBottom;
              }}
      
              style={{flex: 1}}
              renderScene={(route, navigator) => {    // 将板块生成具体的组件
                      let Component = route.component;    // 获取路由内的板块
                      return <Component {...route.params} navigator={navigator} />    // 根据板块生成具体组件
              }}
          />
      
      

      效果:


      navigator转场动画.gif
    • 为了让导航栏更加人性化,我们可以自己定制导航栏的样式,定制方式和我们自定义界面一样,只不过将按钮的响应改为导航栏对应的功能即可,这边就以最基本的导航栏样式为例

      • 视图部分


          var Home = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          {/* 实例化导航栏 */}
                          {this.setupNavBar()}
                      </View>
                  );
              },
      
              setupNavBar(){
                  return(
                      <View style={styles.navBarStyle}>
                          {/* 左边按钮 */}
                          <TouchableOpacity
                              onPress={() => {this.props.navigator.push({
                                  component:Temp
                              })}}
                          >
                              <Text style={styles.leftButtonTitleStyle}>按钮</Text>
                          </TouchableOpacity>
      
                          {/* 中间标题 */}
                          <Text style={styles.navBarTitleStyle}>标题</Text>
      
                          {/* 右边按钮 */}
                          <TouchableOpacity>
                              <Text style={styles.rightButtonTitleStyle}>按钮</Text>
                          </TouchableOpacity>
                      </View>
                  )
              }
          });
      
      
      • 样式部分


          var styles = StyleSheet.create({
              container: {
                  backgroundColor:'yellow',
                  flex:1
              },
      
              navBarStyle: {
                  // 尺寸
                  width:width,
                  // 当前系统为iOS时,导航栏高度为64
                  height:Platform.OS === 'ios' ? 64 : 44,
                  // 背景颜色
                  backgroundColor:'rgba(255, 255, 255, 0.9)',
                  // 底部分隔线
                  borderBottomWidth:0.5,
                  borderBottomColor:'gray',
                  // 主轴方向
                  flexDirection:'row',
                  // 对齐方式
                  alignItems:'center',
                  justifyContent:'space-between',
                  // 当前系统为iOS时,下次移动15
                  paddingTop:Platform.OS === 'ios' ? 15 : 0
              },
      
              leftButtonTitleStyle: {
                  // 字体大小
                  fontSize:15,
                  // 字体颜色
                  color:'blue',
                  // 内边距
                  paddingLeft:8
              },
      
              navBarTitleStyle: {
                  // 字体大小
                  fontSize:17,
                  // 字体颜色
                  color:'black'
              },
      
              rightButtonTitleStyle: {
                  // 字体大小
                  fontSize:15,
                  // 字体颜色
                  color:'blue',
                  // 内边距
                  paddingRight:8
              }
          });
      
      

      iOS运行效果:


      iOS运行效果.gif

    Android运行效果:


    android运行效果.gif

    NavigatorIOS 属性


    • barTintColor:导航条的背景颜色

          barTintColor='red'    // 导航栏背景颜色
      
      

      效果:


      导航栏背景色
    • 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, wrapperStyle: [object Object]} ): 使用“路由”对象来包含要渲染的子视图、它们的属性、已经导航条配置。“push”和任何其他的导航函数的参数都是这样的路由对象(下面实例模块会详细讲解)

    • itemWrapperStyle:导航器中的组件的默认属性。一个常见的用途是设置所有页面的背景颜色

    • navigationBarHidden:布尔值,决定导航栏是否隐藏

          navigationBarHidden={true}      // 隐藏导航栏
      
      

      效果:


      navigationBarHidden.gif
    • shadowHidden:布尔值,决定是否要隐藏1像素的阴影

          shadowHidden={true}     // 隐藏导航栏下面的阴影
      
      

      效果:


      隐藏阴影
    • tintColor:导航栏上按钮的颜色

          tintColor='orange'  // 按钮的颜色
      
      

      效果:


      导航栏按钮颜色
    • titleTextColor:导航器标题的文字颜色

          titleTextColor='green'  // 导航栏标题的文字颜色
      
      

      效果:


      导航栏标题颜色
    • translucent:布尔值,决定导航条是否半透明(注:当不半透明时页面会向下移动导航栏等高的距离,以防止内容被遮盖)

          translucent={false}     // 决定导航栏是否半透明(注:当不半透明时页面会向下移动导航栏等高的距离,以防止内容被遮盖)
          
      

      效果:


      导航栏不半透明效果
    • interactivePopGestureEnabled:决定是否启用滑动返回手势。不指定此属性时,手势会根据 navigationBar 的显隐情况决定是否启用(显示时启用手势,隐藏时禁用手势),指定此属性后,手势与 navigationBar 的显隐情况无关

          interactivePopGestureEnabled={false}    // 决定是否启用滑动返回手势
      
      

      效果:


      interactivePopGestureEnabled关闭.gif

    NavigatorIOS 方法


    • push(route):导航器跳转到一个新的路由

    • pop():回到上一页

    • popN():回到N页之前。当 N=1 的时候,效果和 pop() 一样

    • replace(route):替换当前页的路由,并立即加载新路由的视图

    • replacePrevious(route):替换上一页的路由/视图

    • replacePreviousAndPop(route):替换上一页的路由/视图并且立即切换回上一页

    • resetTO(route):替换最顶级的路由并且回到它

    • replaceAtIndex:替换指定路由

    • popToRoute(route):一直回到某个指定的路由

    • popToTop():回到最顶层的路由

    NavigatorIOS 使用


    • 先来看看怎么使用 NavigatorIOS,我们需要给他指定一个路由,这样它才能知道显示哪个页面

    • Navigator 一样 NavigatorIOS 需要有个根视图来完成初始化,所以我们需要先创建一个组件来描述这个界面,并将这个组件通过路由的形式告诉 NavigatorIOS,这样就可以将这个界面展示出来

      • 首先,创建一个 Home 组件,用来作为 NavigatorIOS 的根视图

        • 视图部分


            var Home = React.createClass( {
                render() {
                    return (
                        <View style={styles.container}>
                            <Text>点击跳转页面</Text>
                        </View>
                    );
                }
            }); 
        
        
        • 样式部分


            var styles = StyleSheet.create({
                container: {
                    // 背景颜色
                    backgroundColor:'yellow',
                    flex:1,
                    // 对齐方式
                    justifyContent:'center',
                    alignItems:'center'
                },
            });
        
        
      • 接着我们在 index.ios.js 内获得 Home 文件

          // 引用外部文件
          var Home = require('./home');
      
      
      • 然后我们实例化一个 NavigatorIOS 并设置路由


          var navigatorDemo = React.createClass( {
              render() {
                  return (
                      <NavigatorIOS
                          initialRoute={{
                              component: Home,    // 要跳转的页面
                              title:'首页'    // 跳转页面导航栏标题
                          }}
                          style={{flex:1}}  // 此项不设置,创建的导航控制器只能看见导航条而看不到界面
                      />
                  );
              }
          });
      
      

      效果:


      基本效果
    • 这样我们就完成了基本的导航控制器了,那么怎么进行跳转呢?其实也很简单,官方提供的方法内有多种供我们选择(具体参考上面的方法一栏)

      • 这边我们就来实现最简单的跳转和返回,我们使用 TouchableOpacityHome 中的 <Text>标签 拥有接收事件的能力,并且当点击的时候通过调用 props 来获取 navigator,并传递给他一个路由,使其知道跳转到哪个页面


          var Home = React.createClass( {
              render() {
                  return (
                      <View style={styles.container}>
                          <TouchableOpacity
                              onPress={() => {this.props.navigator.push({
                                  component:Temp,     // 需要跳转的页面
                                  title:'跳转的界面'       // 跳转页面导航栏标题
                              })}}
                          >
                              <Text>点击跳转页面</Text>
                          </TouchableOpacity> 
                      </View>
                  );
              }
          });
      
      

      效果:


      NavigatorIOS效果.gif
    • 这边顺便来看下导航栏左右两边的按钮怎么设置,并且响应点击事件

          initialRoute={{
             component: Home,    // 要跳转的页面
             title:'首页',   // 跳转页面导航栏标题
             leftButtonTitle:'左边',   // 实例化左边按钮
             onLeftButtonPress:() => {alert('左边')},  // 左边按钮点击事件
             rightButtonTitle:'右边',  // 实例化右边按钮
             onRightButtonPress:() => {alert('右边')}  // 右边按钮点击事件
              }}
      
      

      效果:


      NavigatorIOS左右按钮.gif
    • 当然图片设置的方式也是一样的,只需要调用 leftButtonIcon 和 'rightButtonIcon` 即可(和TabBarIOS一样,只支持本地图片)

    补充


    • props:组件中的props是一种父级向子级传递数据的方式
      • this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点
      • 它里面包含了所有的属性,所以上面我们在别的文件中可以通过 this.props.navigator 的方式获取 navigator
    • state:state 是React中组件的一个对象.React把用户界面当做是状态机,想象它有不同的状态然后渲染这些状态,可以轻松让用户界面与数据保持一致。React中,更新组件的 state,会导致重新渲染用户界面(不要操作DOM).简单来说,就是用户界面会随着 state 变化而变化
      • 原理:常用的通知React数据变化的方法是调用 setState(data,callback).这个方法会合并data到 this.state,并重新渲染组件.渲染完成后,调用可选的。callback 回调.大部分情况不需要提供 callback,因为React会负责吧界面更新到最新状态
      • 哪些组件应该有 state
        • 大部分组件的工作应该是从 props 里取数据并渲染出来.但是,有时需要对用户输入,服务器请求或者时间变化等作出响应,这时才需要 state
        • 组件应该尽可能的无状态化,这样能隔离state,把它放到最合理的地方(Redux做的就是这个事情?),也能减少冗余并易于解释程序运作过程
        • 常用的模式就是创建多个只负责渲染数据的无状态(stateless)组件,在他们的上层创建一个有状态(stateful)组件并把它的状态通过props
        • 传给子级.有状态的组件封装了所有的用户交互逻辑,而这些无状态组件只负责声明式地渲染数据
      • 哪些应该作为 state
        • state 应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据.这中数据一般很小且能被JSON序列化.当创建一个状态化的组件的时候,应该保持数据的精简,然后存入 this.state。 在render()中在根据 state 来计算需要的其他数据.因为如果在state里添加冗余数据或计算所得数据,经常需要手动保持数据同步
      • 哪些不应该作为 state
        • this.state 应该仅包括能表示用户界面状态所需要的最少数据.因此,不应该包括:
          • 计算所得数据:
            • React组件:在render()里使用props和state来创建它
            • 基于props的重复数据:尽可能保持用props来做作为唯一的数据来源.把props保存到state中的有效的场景是需要知道它以前的值得时候,因为未来的props可能会变化

    相关文章

      网友评论

      • 宁夏灼雪__:大佬
        <TouchableOpacity
        onPress={() => {this.props.navigator.push({
        component:Home, // 需要跳转的页面
        title:'首页' // 跳转页面导航栏标题
        })}}
        >
        使用NavigatorIOS 跳转的时候会出现 undefined is not a object (evaluating:' this.props.navigator.push')
        宁夏灼雪__:@宁夏灼雪__ 我做的事对cell的点击,跳转
        宁夏灼雪__:@黑森林工作室 我尝试过使用网上的做法bind(this) 好像没效果,最后在使用FlatList 添加了一句 navigator={this.props.navigator} 就可以了,不知道为什么
        珍此良辰:@宁夏灼雪__ 检查引用,查看当前this指向,若指向错误进行相应绑定操作
      • 两个小棉袄:您好,请教个问题。
        在原生项目里集成RN, 只会有部分页面会用rn实现。那么NavigatorIOS 是没法用的吧。
        比如 原生的UINavigationController rootViewController 是 A, 然后点击跳转到原生的页面B, 此时需要点击控件,跳转到 RN 页面 C, 在页面C里,存在两种场景,点击进入下一个RN 页面D,或者点击进入原生页面E。
        这个场景下,用何种方式比较好呢?望解答,谢谢。
        两个小棉袄:@黑森林工作室 谢谢
        珍此良辰:@两个小棉袄 这个功能是可以实现的,建议你直接集成试试,因为react native最终也是调用的UIKit,所以并没有这样的问题,你可以参考一下这篇文章(https://www.cnblogs.com/shaoting/p/6388502.html),最后,感谢您的支持,由于某些原因,不怎么上博客,有问题可以直接联系我的邮箱:blush:
      • wuweijie_92d6:IOS 里面你所写的Home组件和navigatorDemo组件是在同一个文件还是分开写的
      • wuweijie_92d6:博主 IOS 感觉看的得有点乱,图中出现多个个' Home'',哪个是指组件哪个是指页面,还有Home组件是和navigatorDemo是写在一个js文件里面呢还是分开写呢
      • 977d656db419:好文 点赞
      • 用心在飞:写的 很有用,不过有点过时了,
        珍此良辰:@用心在飞 props并不是用来获取全局对象,它的作用是获取父组件的属性,也可以说父组件传递给子组件,子组件通过this.props来获取父组件的属性对象!react中,每一个组件都拥有自己的props!
        用心在飞:为什么我用this.props.navigator获取不到全局的navigator,求大神指点
        珍此良辰:@用心在飞 :blush: 毕竟到现在还一直是0.48测试版,等稳定后再更新!
      • 捏泥巴的小人:撸主 有源码吗 刚学习
        捏泥巴的小人:@雨泽Forest 谢谢 会关注的
        珍此良辰:@Ad布 很遗憾,当时这部分没有上传源码,不过可以参考后面实战部分,有源码和视频,希望能帮到你
      • hauibojek:博主,动态隐藏显示导航栏怎么处理 比如一级页面自定义的导航,二级要显示出来
        珍此良辰:@HUswIft 哎呦,推荐直接使用Navigator,这样安卓那边也可以用,我明白你的意思了,就是一级页面用的自定义Nav样式,后面push的页面要使用默认样式,如果使用Navigator的话,就组件化2套Nav样式(一级页面一套,其他类似页面一套),在需要的地方调用一下即可
        hauibojek:@雨泽Forest :smile: 其实就是一级页面用的自定义的View,所以要把NavigatorIOS隐藏掉 跳转到二级页面再显示出来,之前不会在外部操作navigator的属性,问题已经解决了。。
        珍此良辰:@HUswIft 私信详细说下,没明白你的意思
      • Sleet:博主,我按照你的方法,用NavigatorIOS 跳转以后出了问题.因为评论没有办法图片所以我私信给你了
        珍此良辰:共同进步:grin:
        Sleet:@雨泽Forest 啊啊啊,因为发现简信也没办法发图片,不过我自己又研究研究把bug解决了.谢谢博主分享的文章
        珍此良辰:@Alex_Dufresne 咦,没有收到您的简信啊:flushed:

      本文标题:React-Native 之 Navigator和Navigat

      本文链接:https://www.haomeiwen.com/subject/wrubpttx.html