`

[React Native]navigation&tab bar整合

阅读更多

1.react-navigation安装:

安装:npm i react-navigation --save

 

中文社区:https://reactnative.cn/docs/0.51/navigation.html#content

react navigation:官方文档:https://reactnavigation.org/

 

2.navigation&tab bar整合:

原理分析:首先无论tab bar还是navigation bar本质都是navigation,而RN与ios原生不同的是,它只有通过导航器来实现页面的跳转,区别于IOS有两种方式:push(针对navigation),present(一般跳转).而RN通过导航器跳转都会默认自带导航栏(可以手动通过UI隐藏,但是没有一般跳转).

 

一般操作:参考:https://www.jianshu.com/p/93d7838701db

里面有些方法已经过期:需要换成:

import {
  createStackNavigator,
  // TabNavigator,
  createBottomTabNavigator,

} from 'react-navigation';

 

const MainScreentNavigator = createBottomTabNavigator({

 

const MyNavigatior = createStackNavigator({

 

然后把MyNavigatior注册成为组件,通过index.js入口进入.

AppRegistry.registerComponent('xxxx', () => MyNavigatior);

 一般混合栈的逻辑是:tab stack<---in--navigation stack<--in---screen

 

3.StackNavigation:

这个是顶部的导航栏,形式如下:

const MainStack = createStackNavigator({
  Home: { screen: HomeScreen },//展示的第一个页面
  BookVC: { screen: BookVC },
});

 首先引入页面:(页面要继承React.Component并且export才可以引入)

import HomeScreen from './HomeScreen';
import BookVC from './BookVC';

***刚开始踩坑,以为screen里面只能装组件,不能装navigation.其实不是的,报的object未定义的错,其实是js编译时从上到下,前面的stack调用了后面才定义的stack才会报错,只要把需要用到的stack放在前面定义即可.

 

这里有一个很容易混淆的问题:

BookVC: { screen: BookVC },

 screen表示显示的页面,这容易理解.前面BookVC很容易出错.首先这里和tab bar不同,不能随便命名.一般使用的是你import进来的页面的命名import BookVC from './BookVC';如果另外命名,会跳转失效.

 

 

4.Tab bar(createBottomTabNavigator):

export const TabNavigator = createBottomTabNavigator({
  Home: { screen: HomeScreen},//第一个tab
  Book: { screen: BookStack},
  Mybook: {screen: MemberStack},
  Member: {screen: MemberStack},
  Record: {screen: RecordStack}
});

 Home: { screen: HomeScreen} Home就是tab的UI名称,同时也是tab stack的index,后面有用.

可以看到上面的tab stack除了Home,里面都装了navigation stack,那么效就是每一个tab里面的页面都可以通过导航器跳转了.

 

5.navigationOptions:

如果想隐藏导航栏,怎么做呢.两种方法:

一.在建栈的时候就通过如下方式:

const MemberStack = createStackNavigator({
  Member: {screen: MemberScreen},
  UpdateMemberScreeen: {screen: UpdateMemberScreeen, navigationOptions:{header: null}},
});

 二.在类内:

export default class UpdateMemberScreeen extends React.Component {
  static navigationOptions = { header: null }

 

如果想隐藏tab bar呢.同理:

一.在建栈时:

const LoginTabNavigator = createBottomTabNavigator({
  LoginScreen: { screen: LoginScreen, navigationOptions:{tabBarVisible:false}},
  Record: {screen: RecordStack}
});

 二.在class内:

static navigationOptions = { header: null ,tabBarVisible: false}

这样是导航栏和tab bar都隐藏了. 

 

上面对于单个stack来说操作navigationOptions都很简单,但是混合起来就很容易出现操作失败.

例如

 

const RecordStack = createStackNavigator({
  RecordVC: {screen: RecordVC},

});
 

 

const LoginTabNavigator = createBottomTabNavigator({
  LoginScreen: { screen: LoginScreen, navigationOptions:{tabBarVisible:false}},
  Record: {screen: RecordStack}
});

 

我这里把LoginScreen的tab bar隐藏了,那么如果我想跳转到RecordStack里面又把tab bar显示出来呢?

const RecordStack = createStackNavigator({
  RecordVC: {screen: RecordVC, navigationOptions:{tabBarVisible:false}},

});

 上面这样做,是不会显示回tab bar的,这是因为

createStackNavigator,createBottomTabNavigator

 都是继承NavigationContainer,而navigationOptions是NavigationContainer的父类属性,所以子类在创建的时候都分别有各自的navigationOptions,上面的navigationOptions显然是针对RecordStack,然而createStackNavigator里面并没有tabBarVisible这个属性,所以修改失败.(虽然没有这个属性,但IDE并没有报错,很容易让人误解可以修改tabBarVisible.

综上,使用navigationOptions,要看清楚究竟是哪个stack的,跨stack是无效的.

 

6.tab bar& navigation跳转:

 参考:https://reactnavigation.org/docs/en/tab-based-navigation.html

官方的是简单跳转,基本原理和上面说的一样.

实现点击按钮后跳到另外一个tab:

例如我上面的home页面:

render() {
        return (
            <View style={{flex: 1, flexDirection: 'row', marginTop: 64}}>
            <Button style={{flex: 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center'}}
            title="预约服务"
            onPress={() => this.props.navigation.navigate('Book', { title: '選擇服務', des: '我是返回点击我' })}
            />
            <Button style={{flex: 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center'}}
            title="我的预约"
            onPress={() => this.props.navigation.navigate('Mybook', { title: '選擇服務', des: '我是返回点击我' })}
            />
            <Button style={{flex: 1, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center'}}
            title="我的会籍"
            onPress={() => this.props.navigation.navigate('Member', { title: '選擇服務', des: '我是返回点击我' })}
            />
          </View>

 有三个按钮,因为home已经装进tab stack,所以点击相应按钮,就可以用

this.props.navigation.navigate('Member', { title: '選擇服務', des: '我是返回点击我' })}

 跳转到Member标签里面了.this.props.navigation.push也是可以的.注意navigate('Member'这个参数就是上面你定义的tab stack的index名称.

 

难点来了,如果我想实现ios里present页面的效果呢.其实就是出栈另外进入一个页面,然后再重新进入原来的栈(因为出栈了,所以上下都没有bar).有些资料提示是混编,进入回ios,用present dismiss.但是纯RN要解决这个问题,就需要一些迂回的思路了.

首先尝试了一般的隐藏bar方法,但是因为上面说了tab和navigator的navigationOptions是分开的,在tab stack隐藏了,进入了login页面,但是login页面再跳转无法再重新显示回来.最烦人的是,RN无论如何都要建stack跳转.那么我们想到再建一个tab stack,只装login就可以.那么就可以在里面隐藏tab bar了.(后来看到另一篇文章讲了一种方法可以跨stack隐藏的,参考https://www.jianshu.com/p/97cafea3ca38?utm_source=desktop&utm_medium=timeline)

例如:

const LoginTabNavigator = createBottomTabNavigator({
  LoginScreen: { screen: LoginScreen, navigationOptions:{tabBarVisible:false}},
});

export const TabNavigator = createBottomTabNavigator({
  Home: { screen: HomeScreen},
  Book: { screen: BookStack},
  Mybook: {screen: LoginNavigator},
  Member: {screen: MemberStack},
  Record: {screen: RecordStack}
},

 然后在login里面button使用

this.props.navigation.navigate('Member', { title: '選擇服務', des: '我是返回点击我' })}

 就可以跳转回原来的tab stack的Member里面了,而且tab bar又重新显示出来了.手动模拟了出栈进栈的效果.

后来再发现了createSwitchNavigator,更方便,不用navigationOptions:{tabBarVisible:false},直接跳进去就没有tab bar了.所以操作是:

onst LoginNavigator = createSwitchNavigator({
  LoginScreen: { screen: LoginScreen},
});

// 通过TabNavigator做路由映射 
export const TabNavigator = createBottomTabNavigator({
  Home: { screen: HomeScreen},
  Book: { screen: BookStack},
  Mybook: {screen: 持久化结果?LoginNavigator:MemberStack,navigationOptions:{tabBarVisible:持久化结果?false:true}},
  Member: {screen: MemberStack},
  Record: {screen: RecordStack}
},

 上面的代码

持久化结果?LoginNavigator:MemberStack

  想实现的是,如果我已经登录了,那么我肯定就不需要再跳进去login页面多一次了.怎么设计呢.我们这里可以通过三元运算符.第一次跳进去login页面了,然后登录后给一个已经登录的flag持久化.然后点击按钮跳回Mybook这个tab,里面获取持久化的值,如果已经登录了,就进入MemberStack里面.

 最后发现不用再装一个栈这么麻烦,直接

export const TabNavigator = createBottomTabNavigator({
  Home: { screen: HomeScreen},
  Book: { screen: BookStack},
  Mybook: {screen: 持久化结果?LoginScreen:MemberStack,navigationOptions:{tabBarVisible:持久化结果?false:true}},
  Member: {screen: MemberStack},
  Record: {screen: RecordStack}
},

 

7.<Modal>

今天在最新研究上面这种出栈入栈操作时候,发现了Modal这种操作.一般这是用于弹出一个dialog的操作,但是如果我的dialog是整个页面,就可以实现login页面覆盖上去tab页面的效果了,类似IOS ModalAndView也就present的效果.

login页面:加上持久化(如果已经登录下次不进入login):

 

import React, { Component } from 'react';
import { Button, Text, View , Modal, AsyncStorage, Dimensions} from 'react-native';

export const deviceWidth = Dimensions.get('window').width;      //设备的宽度,单位dp
export const deviceHeight = Dimensions.get('window').height;

export default class LoginScreen extends React.Component {

  state = {
    visible: false,
    transparent: true,
};


getLoginFalg = function() {
       
    AsyncStorage.getItem("LoginFalg", (error, result) => {
      if (!error) {
        if(result == "true1"){
            console.log('已经login' + result)
            this.setState({visible:false})
        }else{
            this.setState({visible:true})
        }
      }
    })    
  };

  loginClick = function() {
    console.log('Login页面')
    AsyncStorage.setItem("LoginFalg", "true1", (error) => {
        if (!error) {
            // Alert.alert("login success.");
            console.log('已经login success')
            this.setState({visible:true})
            return
        }
        Alert.alert("login failed.");

    })
  };

    render() {
      this.getLoginFalg()
      console.log('Login页面')
      return (
        
        <View style={{ flex: 1}}>
            <Modal
                visible={this.state.visible}
                animationType='slide'
                // transparent={this.state.transparent}
                onRequestClose={()=>{
                    // Alert.alert("Modal has been closed.");
                }}
                onShow={()=>{
                    // Alert.alert("Modal has been show.");
                }}>
                <View style={{justifyContent: 'center', alignItems: 'center',height:deviceHeight,width:deviceWidth,backgroundColor:'white'}}>
                    <Button title='Login' onPress={()=>this.loginClick()}/>
                </View>

            </Modal>
        </View>
      );
    }
  }
 

 

解释一下持久化:

 获取:

AsyncStorage.getItem("LoginFalg", (error, result) => {
      if (!error) {
        if(result == "true1"){
            console.log('已经login' + result)
            this.setState({visible:false})
        }else{
            this.setState({visible:true})
        }
      }
    })
 getItem("LoginFalg", (error, result): LoginFalg是Key值,对应setItem的Key.

 

 

设置:

 

AsyncStorage.setItem("LoginFalg", "true1", (error) => {
        if (!error) {
            // Alert.alert("login success.");
            console.log('已经login success')
            this.setState({visible:true})
            return
        }
        Alert.alert("login failed.");

    })
 setItem("LoginFalg", "true1", (error),=> {,"true1"就是设置的value值.

 

这样只要在login后的页面里引用一下控件:

import LoginScreen from './LoginScreen'
<LoginScreen/>

 即可Modal覆盖上去

 

 

 

 8.<Navigator>

 有一些资料是使用这个标签,重新写navigator的,例如一下项目:

https://github.com/chjwrr/RN-tabbar-navigation

这个标签在0.55版本已经过期了,只能在0.54一下版本使用,要使用的话就需要引入另外一个react-native-deprecate组件.

 

这里简单说一下,这个组件很多东西都要手动配置,重载方法,看起来是没有最新版本的stack清晰易懂.

creatNavigator (component){

        return (

            <Navigator
                initialRoute={{ name: component, component: component }}//默认加载的页面
                configureScene={this.configureScene}
                renderScene={this.renderScene}
                style={{flex:1}}
                navigationBar={
                  <Navigator.NavigationBar style={HomePageNavStyle.navStyleBase}
                  routeMapper={NavigationBarRouteMapper}/>}
            />
        )

    }

 这个是核心部分.如果想去掉bar的话,不写navigationBar这个属性部分就可以了.

 

var NavigationBarRouteMapper = {
    // 标题

    Title(route, navigator, index, navState) {


        return (

            <View>
                <Text>

你好
                </Text>
            </View>
        );
    },
    // 左键
    LeftButton(route, navigator, index, navState) {
        if (index > 0) {
            return (
                <View>
                    <TouchableOpacity
                        underlayColor='transparent'
                        onPress={() => {
                            if (index > 0) {
                                navigator.pop()
                            }
                        }}>
                        <Text style={HomePageNavStyle.navLeftButtonStyle}>
                            返回
                        </Text>
                    </TouchableOpacity>
                </View>
            );
        } else {
            return null;
        }
    },
    RightButton(route, navigator, index, navState) {
        if (route.onPress)
            return (
                <View>
                    <TouchableOpacity
                        onPress={() => route.onPress()}>
                        <Text style={HomePageNavStyle.navRightButtonStyle}>
                            right
                        </Text>
                    </TouchableOpacity>
                </View>
            );
    },

};

 这个部分是画navigator bar的ui的,通过style CSS可以控制位置样式等等.

只要在页面使用了

this.props.navigator.push({
            component:FirstPage_first
        })

 就可以跳转到下一个页面,而且也有navigator bar

 

 在入口类render内使用:

import 类进来,然后

<HPTB_Navigator/>

 会自动进入

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics