2018-09-03

2018-09-03  本文已影响0人  HT_Jonson

本文是基于最新的react-navigation^2.9.1来书写的。

要感谢挂着铃铛的兔
看到一篇不错的介绍,这里做下记录

react-native-router-flux使用技巧(API篇)

识兔,一款用来识别图片的开源项目,在未来还会添加更多有意思的东西

react-navigation的Demo

react-navigation使用技巧(进阶篇)

什么是react-navigation?

react-native从开源至今,一直存在几个无法解决的毛病,偶尔就会复发让人隐隐作痛,提醒你用的不是原生,其中包括列表的复用问题,导航跳转不流畅的问题等等。
终于facebook坐不住了,在前一段时间开始推荐使用react-navigation,并且在0.44发布的时将之前一直存在的Navigator废弃了。
react-navigation是致力于解决导航卡顿,数据传递,Tabbar和navigator布局,支持redux。虽然现在功能还不完善,但基本是可以在项目中推荐使用的。

属性

react-navigation分为三个部分。
StackNavigator类似顶部导航条,用来跳转页面和传递参数。
TabNavigator类似底部标签栏,用来区分模块。
DrawerNavigator抽屉,类似从App左侧滑出一个页面,在这里不做讲解。
下面会分开讲解官网提供的配置方法,但顺序可能会官网不一样。

React-Navigation V2版使用教程

距离我上一个版本的react-navigation教程已经有1年多的时间了,虽然一直在缝缝补补,但那个教程真的老了。正好react-navigation V2版本也即将要正式发布了,趁着这次机会重新梳理一下教程,并把之前的坑和遗憾填补一下。

我会将本文分成三部分,第一部分是使用小技巧,第二部分是介绍API,第三部分是常用属性方法。

跳转

navigate('Detail',{
                   title:'图片详情',
                   url:item.url,
                   });

Detail:在StackNavigator中注册的页面,需要一一对应,才能跳转到相应的页面
title:在跳转的页面可以通过this.props.navigation.state.params.title获取到这个参数。当然这个参数可以随便填写,都可以通过this.props.navigation.state.params.xxx获取。

回调传参

navigate('Detail',{
                   // 跳转的时候携带一个参数去下个页面
                   callback: (data)=>{
                         console.log(data); // 打印值为:'回调参数'
                     }
                   });

const {navigate,goBack,state} = this.props.navigation;
// 在第二个页面,在goBack之前,将上个页面的方法取到,并回传参数,这样回传的参数会重走render方法
state.params.callback('回调参数');
goBack();

自定义

项目中基本是没可能用自带的那个导航条的,自带导航条左侧的按钮永远是蓝色的,如果我们需要更改按钮颜色,就需要用到自定义的功能了。

const StackOptions = ({navigation}) => {
    console.log(navigation);
    let {state,goBack} = navigation;

    // 用来判断是否隐藏或显示header
    const visible= state.params.isVisible;
    let header;
    if (visible === true){
        header = null;
    }
    const headerStyle = {backgroundColor:'#4ECBFC'};
    const headerTitle = state.params.title;
    const headerTitleStyle = {fontSize:FONT_SIZE(20),color:'white',fontWeight:'500'}
    const headerBackTitle = false;
    const headerLeft = (
        <Button
            isCustom={true}
            customView={
                            <Icon
                                name='ios-arrow-back'
                                size={30}
                                color='white'
                                style={{marginLeft:13}}
                            />
                        }
            onPress={()=>{goBack()}}
        />
    );
    return {headerStyle,headerTitle,headerTitleStyle,headerBackTitle,headerLeft,header}
};

然后通过下面的方法调用就可以自定制导航了。

const MyApp = StackNavigator({
    MyTab: {
        screen: MyTab,
    },
    Detail: {
        screen: Detail,
        navigationOptions: ({navigation}) => StackOptions({navigation})
    },
)};

在页面中使用的时候,在跳转页面的时候需要传递title参数,才能看到效果哦。

自定义tabbar

早上有人问我,tabbar的图标可不可以使用原图,选中状态下可不可以设置其他图标。研究了一下官方文档,发现tabBarIcon除了tintColor还有另一个属性,用来判断选中状态的focused

 tabBarIcon: ({tintColor,focused}) => (
                focused
                    ?
                    <Image
                        source={{uri : '识兔'}}
                        style={tabBarIcon}
                    />
                    :
                    <Image
                        source={{uri : '干货'}}
                        style={[tabBarIcon, {tintColor: tintColor}]}
                    />
            ),

通过判断focused,选中状态下使用识兔图标,未选中状态使用干货图标。
如果想使用图标原来的样子,那就将styletintColor去掉,这样就会显示图标原本的颜色。

再封装

export const TabOptions = (tabBarTitle,normalImage,selectedImage,navTitle) => {
    // console.log(navigation);
    const tabBarLabel = tabBarTitle;
    console.log(navTitle);
    const tabBarIcon = (({tintColor,focused})=> {
        return(
            focused
                ?
                <Image
                    source={{uri : normalImage}}
                    style={[TabBarIcon, {tintColor: tintColor}]}
                />
                :
                <Image
                    source={{uri : selectedImage}}
                    style={[TabBarIcon, {tintColor: tintColor}]}
                />
        )
    });
    const headerTitle = navTitle;
    const headerTitleStyle = {fontSize:FONT_SIZE(20),color:'white'};
    // header的style
    const headerStyle = {backgroundColor:'#4ECBFC'};
    return {tabBarLabel,tabBarIcon,headerTitle,headerTitleStyle,headerStyle};
};

在static中使用this方法

我之前文章中是将navaigationOptions的方法写在了app.js中,没有在页面中通过static navaigationOptions来初始化页面,这段时间刚好有人问,所以在这里就写一下该怎么弄。

首先需要在componentDidMount(){}中动态的添加点击事件
属性给params
componentDidMount(){

    this.props.navigation.setParams({
        title:'自定义Header',
        navigatePress:this.navigatePress
    })
}

navigatePress = () => {
    alert('点击headerRight');
    console.log(this.props.navigation);
}

接下来就可以通过params方法来获取点击事件了
static navigationOptions = ({ navigation, screenProps }) => ({
        title: navigation.state.params?navigation.state.params.title:null,
        headerRight:(
            <Text onPress={navigation.state.params?navigation.state.params.navigatePress:null}>
                返回
            </Text>
        )
});

让安卓实现push动画

之前我群里的讨论怎么让安卓实现类似iOS的push动画,后来翻看官方issues的时候,真的发现了实现push动画的代码,在这里共享下

// 先引入这个方法
import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';

// 在StackNavigator配置headerMode的地方,使用transitionConfig添加
{
    headerMode: 'screen',
    transitionConfig:()=>({
        screenInterpolator:CardStackStyleInterpolator.forHorizontal,
    })
}

关于goBack返回指定页面

react-navigation是提供了goBack()到指定页面的方法的,那就是在goBack()中添加一个参数,但当你使用goBack('Main')的时候,你会发现并没有跳转,原因是react-navigation默认goBack()中的参数是系统随机分配的key,而不是手动设置的routeName,而方法内部又没有提供可以获得key的方法,所以这里只能通过修改源码将key换成routeName了。
下面的内容直接引用了hello老文的内容

把项目/node_modules/react-navigation/src/routers/StackRouter.js文件里的 
const backRoute = state.routes.find((route: *) => route.key === action.key); 
改成 const backRoute = state.routes.find(route => route.routeName === action.key);

但不是很完美, 这里的component要填想返回的组件的前一个组件的routeName, 比如你的栈里顺序是home1, home2, home3, home4, 在home4里要返回home2, 使用this.props.navigation.goBack('home3');; 并且又会带出一个问题: goBack()方法没反应了, 必须加个null进去, 写成goBack(null)...

关于goBack返回指定页面的修改完善版

if (action.type === NavigationActions.BACK) {
    let backRouteIndex = null;
    if (action.key) {

      const backRoute = state.routes.find(
        /* $FlowFixMe */
        /* 修改源码 */
        route => route.routeName === action.key
        /* (route: *) => route.key === action.key */
      );
      /* $FlowFixMe */
      console.log('backRoute =====',backRoute);
      backRouteIndex = state.routes.indexOf(backRoute);
      console.log('backRoute =====',backRouteIndex);
    }
    if (backRouteIndex == null) {
      return StateUtils.pop(state);
    }
    if (backRouteIndex >= 0) {
      return {
        ...state,
        routes: state.routes.slice(0, backRouteIndex+1),
        index: backRouteIndex - 1 + 1,
      };
    }
  }

感谢群友conan的贡献,将源码改成上面的样子,就可以使用goBack()返回指定页面了,这样的优点不言而喻,但缺点就是每次调用goBack(),如果只是简单的返回上一页需要加上null参数,类似这样goBack(null)

如果这样修改,在滑动返回的时候,会有很大几率让项目卡死,请注意使用该方法,推荐集成redux。

关于快速点击会导致多次跳转的问题解决办法

感谢群友编程大叔的贡献,如果想解决快速点击跳转的问题,需要修改部分源码。

修改react-navigation目录下,scr文件夹中的addNavigationHelpers.js文件,可以直接替换成下面的文本,也可以查看原版链接

 export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {
  // 添加点击判断
  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,
        }),
      ),
  };
}

安卓上,使用TextInput的时候会让TabBar顶起来的解决办法

最简单的解决办法就是在android目录中,添加一句话

目录:android/app/src/main/AndroidManifest.xml中,添加

 android:windowSoftInputMode="stateAlwaysHidden|adjustPan|adjustResize"

ps:在iOS下如果想一劳永逸的解决键盘问题,请使用IQKeyBoardManager

API

createSwitchNavigator

SwitchNavigator的目的是一次只显示一个屏幕。默认情况下,它不处理回退操作,并在您切换时将路由重置为默认状态。这是我们从登录流程(包含注册,登录,忘记密码等)到主屏幕的必要流程。

这个方法在1.x中叫做SwitchNavigator,在2.x中统一命名为createSwitchNavigator
createSwitchNavigator(RouteConfigs, SwitchNavigatorConfig);

RouteConfigs

路由的配置表,详细配置可以参考下面的createStackNavigator

SwitchNavigatorConfig

SwitchNavigator属性

export default createSwitchNavigator(
  {
    AuthLoading: AuthLoadingScreen,
    App: AppStack,
    Auth: AuthStack,
  },
  {
    initialRouteName: 'AuthLoading',
  }
);

createStackNavigator

配置路由属性和参数

这个方法在1.x中叫做StackNavigator,在2.x中统一命名为createSwitchNavigator
createStackNavigator(RouteConfigs, StackNavigatorConfig);

RouteConfigs

StackNavigatorConfig

可选的路由属性
可选的视觉选项

navigationOptions

导航页面的属性和方法

Navigator Props

由StackNavigator(...)创建的导航器组件采用以下道具:

createTabNavigator

createTabNavigator已弃用。请改用createBottomTabNavigator和/或createMaterialTopTabNavigator

createBottomTabNavigator

屏幕底部的简单标签栏,可让您在不同路由之间切换。路由是被懒惰初始化的 - 它们的屏幕组件直到第一次选中时才会初始化。

这个方法在1.x中叫做TabNavigator,在2.x中统一命名为createBottomTabNavigator
createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig);

RouteConfigs

属性请参考createStackNavigator
它的navigationOptionscreateStackNavigator不一样,下面会有说明。

BottomTabNavigatorConfig

navigationOptions

标签栏的属性和方法

[图片上传失败...(image-d10d-1535967589937)]

tabBarOnPress: async (obj: any) => {
            console.log(obj);
            try {
                const userData = await AsyncStorage.getItem('USER_INFO');
                if (userData) {
                    obj.defaultHandler();
                }
                else {
                    obj.navigation.navigate('Login');
                }
            } catch (e) {
                Toast.show(e.message, 'center', 1000);
            }
        }

createMaterialTopTabNavigator

实现了类似react-native-scrollable-tab-view的左右滚动效果,但每个tab页是没有懒加载的,就是说,当使用这个生成导航的时候,每个页面都会初始化,对内存影响较大。
这个导航是基于react-native-tab-view实现的,如果有需要可以研究。

createMaterialTopTabNavigator(RouteConfigs, TabNavigatorConfig);

RouteConfigs

属性请参考createStackNavigator

TabNavigatorConfig

navigationOptions

[图片上传失败...(image-8865d7-1535967589937)]

tabBarOnPress: async (obj: any) => {
            console.log(obj);
            try {
                const userData = await AsyncStorage.getItem('USER_INFO');
                if (userData) {
                    obj.defaultHandler();
                }
                else {
                    obj.navigation.navigate('Login');
                }
            } catch (e) {
                Toast.show(e.message, 'center', 1000);
            }
        }

createDrawerNavigator

创建侧边栏导航,有一些坑,需要用过才知道。

createDrawerNavigator(RouteConfigs, DrawerNavigatorConfig)

RouteConfigs

属性请参考createStackNavigator

DrawerNavigatorConfig

contentComponent

提供自定义的抽屉效果
抽屉的默认组件是可滚动的,只包含RouteConfig中路由的链接。您可以轻松地覆盖默认组件,以向抽屉中添加页眉,页脚或其他内容。默认情况下,抽屉可滚动并支持iPhone X安全区域。如果您自定义内容,请务必将内容包装在SafeAreaView中

DrawerItemscontentOptions

Screen Navigation Options

enum('unlocked', 'locked-closed', 'locked-open')

createMaterialBottomTabNavigator

这个方法在2.0正式版中被砍掉了,但官方文档没有更新,如果喜欢material风格,可以参考react-native-material-bottom-navigation

常用属性方法

这部分会分为createStackNavigatorcreateDrawerNavigator两部分,因为它们属性不太一样。

createStackNavigator

常用方法

[图片上传失败...(image-c747f-1535967589936)]

在新版的react-navigation中实现了很多常用的api,比如说pushpoppopToTop等常用方法,在本文中会将属性和使用方法简单说明。

NavigationActions

import { NavigationActions } from 'react-navigation';
const navigateAction = NavigationActions.navigate({
  routeName: 'Profile',
  params: {},
  action: NavigationActions.navigate({ routeName: 'SubProfileRoute' }),
});
this.props.navigation.dispatch(navigateAction);

import { NavigationActions } from 'react-navigation';
const backAction = NavigationActions.back({
  key: 'Profile',
});
this.props.navigation.dispatch(backAction);

在调用SetParams时,路由器将产生一个新的状态,该状态已经改变了由key标识的特定路由参数

* `params` - Object - 可选 - 新的参数被合并到现有的路径参数中。
* `key` - String - 必须 - 获取新参数的路由键。

import { NavigationActions } from 'react-navigation';

const setParamsAction = NavigationActions.setParams({
  params: { title: 'Hello' },
  key: 'screen-123',
});
this.props.navigation.dispatch(setParamsAction);

Reset操作将重置整个导航状态并将其替换为新的导航。

* `index` - number - 必须 - 导航中`routes`活动路由的索引`state`。

* `actions` - array - 必须 - 将替换导航数组。
* `key` - string | null - 可选 - 如果设置,具有给定键的导航器将重置。如果为null,则根导航器将被重置。

import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
  index: 0,
  actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);

Replace操作将给定key上的路线替换为另一条路线。

* `key` - string - 必须 - 要替换​​路由的`key`。

* `newKey` - string - 用于替换路由的`key`。如果未提供,则自动生成。
* `routeName` - string - 用于替换路由的`routeName`。
* `params` - object - 要传入替换路由的参数。
* `action` - object - 可选的子操作。

Push操作会在堆栈顶部添加一条路径并向前导航。这与之前的不同之处在于,如果某个组件已经存在路由中,navigate则会弹出到堆栈中的较早版本。Push将始终添加在顶部,因此可以多次安装组件。

* `routeName` - string - 要跳转路由的`routeName`。

* `params` - object - 传递的参数,可以通过(this.props.navigation.state.params)找到。
* `action` - 子操作。

Pop操作将使您回到堆栈中的前一个屏幕。n参数允许您指定要多少个屏幕出栈。

* `n` - number - 要出栈的屏幕数量。

PopToTop操作会将您带回堆栈中的第一个屏幕,解除所有其他屏幕。它的功能与StackActions.pop({n: currentIndex})类似。

----------------------------------------------新老版本的分割线--------------------------------------------------------

screenProps

之前是没有介绍这个属性的,但经过这么久发现,很多人都不知道这个属性,不知道它能干嘛,在这里我就简单的介绍下

screenProps:react-navigation自带的一个属性,属于navigationOptions的一个属性,可以全局控制navigationOptions中的某些值,比如说你想做换肤功能,修改这个属性绝对是最简单的方式。

// 假设App就是项目中的入口文件,如果还不知道,可以看下Demo,在这里我将主题色通过screenProps属性修改成'red'
<App screenProps={{themeColor:'red'}}>

// 在页面中就可以通过screenProps来直接改变了,这个在Demo
中的Test2里面

static navigationOptions = ({navigation,screenProps}) => ({
        // 这里面的属性和App.js的navigationOptions是一样的。
                headerStyle:{backgroundColor:screenProps?
                screenProps.themeColor:
                '#4ECBFC'},

    )
})

StackNavigator 基础用法/属性介绍

const MyApp = StackNavigator({
    // 对应界面名称
    MyTab: {
        screen: MyTab,
    },
    Detail: {
        screen: Detail,
        navigationOptions:{
            headerTitle:'详情',
            headerBackTitle:null,
        }
    },
}, {
    headerMode: 'screen',
});

导航配置

screen:对应界面名称,需要填入import之后的页面。

navigationOptions:配置StackNavigator的一些属性。

在最新版本的react-navigation中,安卓居中可以使用 flex:1, textAlign: 'center'来实现。
// 设置滑动返回的距离
gestureResponseDistance:{horizontal:300},

注:beta13新出的东西,挺有意思,以后可以手动控制返回了

导航视觉效果

mode:定义跳转风格。

headerMode:边缘滑动返回上级页面时动画效果。

cardStyle:自定义设置跳转效果。

transitionConfig: 自定义设置滑动返回的配置。
onTransitionStart:当转换动画即将开始时被调用的功能。
onTransitionEnd:当转换动画完成,将被调用的功能。

path:路由中设置的路径的覆盖映射配置。
initialRouteName:设置默认的页面组件,必须是上面已注册的页面组件。
initialRouteParams:初始路由的参数。

path:path属性适用于其他app或浏览器使用url打开本app并进入指定页面。path属性用于声明一个界面路径,例如:【/pages/Home】。此时我们可以在手机浏览器中输入:app名称://pages/Home来启动该App,并进入Home界面。

TabNavigator 基础用法/属性介绍

const MyTab = TabNavigator({
    ShiTu: {
        screen: ShiTu,
        navigationOptions:{
            tabBarLabel: '识兔',
            tabBarIcon: ({tintColor}) => (
                <Image
                    source={{uri : '识兔'}}
                    style={[tabBarIcon, {tintColor: tintColor}]}
                />
            ),
        },
    }, {
    tabBarPosition: 'bottom',
    swipeEnabled:false,
    animationEnabled:false,
    tabBarOptions: {
        style: {
            height:49
        },
        activeBackgroundColor:'white',
        activeTintColor:'#4ECBFC',
        inactiveBackgroundColor:'white',
        inactiveTintColor:'#aaa',
        showLabel:false,
    }
});

屏幕导航配置

screen:和导航的功能是一样的,对应界面名称,可以在其他页面通过这个screen传值和跳转。
navigationOptions:配置TabNavigator的一些属性

tabBarOnPress:(obj)=>{
            console.log(obj);

            obj.jumpToIndex(obj.scene.index)
        },

标签栏配置

tabBarPosition:设置tabbar的位置,iOS默认在底部,安卓默认在顶部。(属性值:'top','bottom')
swipeEnabled:是否允许在标签之间进行滑动。
animationEnabled:是否在更改标签时显示动画。
lazy:是否根据需要懒惰呈现标签,而不是提前制作,意思是在app打开的时候将底部标签栏全部加载,默认false,推荐改成true哦。
initialRouteName: 设置默认的页面组件
backBehavior:按 back 键是否跳转到第一个Tab(首页), none 为不跳转

tabBarOptions:配置标签栏的一些属性

iOS属性
安卓属性
上一篇 下一篇

猜你喜欢

热点阅读