刚创建的React Native 微信公众号,欢迎微信扫描关注订阅号,每天定期会分享react native 技术文章,移动技术干货,精彩文章技术推送。同时可以扫描我的微信加入react-native技术交流微信群。欢迎各位大牛,React Native技术爱好者加入交流!
本篇内容为react-navigation的进阶内容以及高级用法。
(1)适配顶部导航栏标题:
测试中发现,在iphone上标题栏的标题为居中状态,而在Android上则是居左对齐。所以需要我们修改源码,进行适配。
【node_modules -- react-navigation -- src -- views -- Header.js】的326行代码处,修改为如下:
- title: {
- bottom: 0,left: TITLE_OFFSET,right: TITLE_OFFSET,top: 0,position: 'absolute',alignItems: 'center',}
上面方法通过修改源码的方式其实略有弊端,毕竟扩展性不好。还有另外一种方式就是,在navigationOptions中设置headerTitleStyle的alignSelf为 ' center '即可解决。
【node_modules -- react-navigation -- src -- views -- HeaderBackButton.js】的91行代码处,修改为如下即可。
- {Platform.OS === 'ios' &&
- title &&
- <Text
- onLayout={this._onTextLayout}
- style={[styles.title,{ color: tintColor }]}
- numberOfLines={1}
- >
- {backButtonTitle}
- </Text>}
(3)动态设置头部按钮事件:
当我们在头部设置左右按钮时,肯定避免不了要设置按钮的单击事件,但是此时会有一个问题,navigationOptions是被修饰为static类型的,所以我们在按钮的onPress的方法中不能直接通过this来调用Component中的方法。如何解决呢?在官方文档中,作者给出利用设置params的思想来动态设置头部标题。那么我们可以利用这种方式,将单击回调函数以参数的方式传递到params,然后在navigationOption中利用navigation来取出设置到onPress即可:
- componentDidMount () {
- /**
- * 将单击回调函数作为参数传递
- */
- this.props.navigation.setParams({
- switch: () => this.switchView()
- });
- }
- /**
- * 切换视图
- */
- switchView() {
- alert('切换')
- }
- static navigationOptions = ({navigation,screenProps}) => ({
- headerTitle: '企业服务',headerTitleStyle: CommonStyles.headerTitleStyle,headerRight: (
- <NavigatorItem icon={ Images.ic_navigator } onPress={ ()=> navigation.state.params.switch() }/>
- ),headerStyle: CommonStyles.headerStyle
- });
- componentDidMount () {
- /**
- * 将单击回调函数作为参数传递
- */
- this.props.navigation.setParams({
- switch: () => this.switchView()
- });
- }
- /**
- * 切换视图
- */
- switchView() {
- alert('切换')
- }
- static navigationOptions = ({navigation,headerStyle: CommonStyles.headerStyle
- });
(4)结合BackHandler处理返回和点击返回键两次退出App效果
点击返回键两次退出App效果的需求屡见不鲜。相信很多人在react-navigation下实现该功能都遇到了很多问题,例如,其他界面不能返回。也就是手机本身返回事件在react-navigation之前拦截了。如何结合react-natigation实现呢?和大家分享两种实现方式:
(1)在注册StackNavigator的界面中,注册BackHandler:
- componentWillMount(){
- BackHandler.addEventListener('hardwareBackPress',this._onBackAndroid );
- }
- componentUnWillMount(){
- BackHandler.addEventListener('hardwareBackPress',this._onBackAndroid);
- }
- _onBackAndroid=()=>{
- let now = new Date().getTime();
- if(now - lastBackPressed < 2500) {
- return false;
- }
- lastBackPressed = now;
- ToastAndroid.show('再点击一次退出应用',ToastAndroid.SHORT);
- return true;
- }
(2)监听react-navigation的Router
- /**
- * 处理安卓返回键
- */
- const defaultStateAction = AppNavigator.router.getStateForAction;
- AppNavigator.router.getStateForAction = (action,state) => {
- if(state && action.type === NavigationActions.BACK && state.routes.length === 1) {
- if (lastBackPressed + 2000 < Date.now()) {
- ToastAndroid.show(Constant.hint_exit,ToastAndroid.SHORT);
- lastBackPressed = Date.now();
- const routes = [...state.routes];
- return {
- ...state,...state.routes,index: routes.length - 1,};
- }
- }
- return defaultStateAction(action,state);
- };
(5)实现Android中界面跳转左右切换动画
react-navigation在Android中默认的界面切换动画是上下。如何实现左右切换呢?很简单的配置即可:
然后在StackNavigator的配置下添加如下代码:
- import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
- transitionConfig:()=>({
- screenInterpolator: CardStackStyleInterpolator.forHorizontal,})
当我们快速点击跳转时,会开启多个重复的界面,如何解决呢。其实在官方git中也有提示,解决这个问题需要修改react-navigation源码:
找到src文件夹中的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,};
- }
react-navigation默认不支持根据路由名返回指定界面,官方只提供了根据Key来做goBack的指定返回。解决这个问题同样需要修改react-navigation源码,在Navigation.goBack条件下添加对路由名的支持。找到/node_modules/react-navigation/src/routers/StackRouter.js,全局搜索backRoute,将条件判断语句替换为如下代码:
- if (action.type === NavigationActions.BACK) {
- const key = action.key;
- let backRouteIndex = null;
- if (key) {
- const backRoute = null;
- if(key.indexOf('id') >= 0) {
- backRoute = state.routes.find((route: *) => route.key === action.key);
- } else {
- backRoute = state.routes.find(route => route.routeName === action.key);
- }
- backRouteIndex = state.routes.indexOf(backRoute);
- }
- if (backRouteIndex == null) {
- return StateUtils.pop(state);
- }
- if (backRouteIndex > 0) {
- return {
- ...state,routes: state.routes.slice(0,backRouteIndex),index: backRouteIndex - 1,};
- }
- }
(8)自定义Tab
- import React,{ Component } from 'react';
- import {
- AppRegistry,Platform,StyleSheet,Text,View,TouchableOpacity,NativeModules,ImageBackground,DeviceEventEmitter
- } from 'react-native';
- export default class Tab extends Component {
- renderItem = (route,index) => {
- const {
- navigation,jumpToIndex,} = this.props;
- const focused = index === navigation.state.index;
- const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
- let TabScene = {
- focused:focused,route:route,tintColor:color
- };
- if(index==1){
- return (<View style={[styles.tabItem,{backgroundColor:'transparent'}]}>
- </View>
- );
- }
- return (
- <TouchableOpacity
- key={route.key}
- style={styles.tabItem}
- onPress={() => jumpToIndex(index)}
- >
- <View
- style={styles.tabItem}>
- {this.props.renderIcon(TabScene)}
- <Text style={{ ...styles.tabText,marginTop:SCALE(10),color }}>{this.props.getLabel(TabScene)}</Text>
- </View>
- </TouchableOpacity>
- );
- };
- render(){
- const {navigation,jumpToIndex} = this.props;
- const {routes,} = navigation.state;
- const focused = 1 === navigation.state.index;
- const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
- let TabScene = {
- focused:focused,route:routes[1],tintColor:color
- };
- return (
- <View style={{width:WIDTH}}>
- <View style={styles.tab}>
- {routes && routes.map((route,index) => this.renderItem(route,index))}
- </View>
- <TouchableOpacity
- key={"centerView"}
- style={[styles.tabItem,{position:'absolute',bottom:0,left:(WIDTH-SCALE(100))/2,right:WIDTH-SCALE(100),height:SCALE(120)}]}
- onPress={() => jumpToIndex(1)}
- >
- <View
- style={styles.tabItem}>
- {this.props.renderIcon(TabScene)}
- <Text style={{ ...styles.tabText,color }}>{this.props.getLabel(TabScene)}</Text>
- </View>
- </TouchableOpacity>
- </View>
- );
- }
- }
- const styles = {
- tab:{
- width:WIDTH,backgroundColor:'transparent',flexDirection:'row',justifyContent:'space-around',alignItems:'flex-end'
- },tabItem:{
- height:SCALE(80),width:SCALE(100),alignItems:'center',justifyContent:'center'
- },tabText:{
- marginTop:SCALE(13),fontSize:FONT(10),color:Color.C7b7b7b
- },tabTextChoose:{
- color:Color.f3474b
- },tabImage:{
- width:SCALE(42),height:SCALE(42),},}
- componentDidMount () {
- /**
- * 将单击回调函数作为参数传递
- */
- this.props.navigation.setParams({
- switch: () => this.switchView()
- });
- }
- /**
- * 切换视图
- */
- switchView() {
- alert('切换')
- }
- static navigationOptions = ({navigation,headerStyle: CommonStyles.headerStyle
- });
(9)如何在屏幕控件之外的模块获取当前界面及navigation实例
很多情况下,我们都需要处理登录token失效的情况。例如:在当前设备登录后不退出,此时在另一台设备登录,导致第一个设备用户登录状态失效,此时在第一台设备操作网络请求时,需要提醒用户登录失效,跳转登录界面,并重新登录。
这种需求很常见,关于网络请求我们一般会封装为一个HttpUtil。然后在Component中去调用。此时如果需要处理登录失效的跳转逻辑,需要写在HttpUtil,那么在HttpUtil中就没办法获取navigation来做跳转,那么如何解决呢?下面提供一种方案,很实用:
定义一个Component的基类,包含当前显示的Component实例:screen,以及导航函数。
- import React,{Component} from 'react';
- export default class Base extends Component {
- static screen;
- constructor(props) {
- super(props);
- Base.screen = this;
- }
- nav() {
- return this.props.navigation;
- }
- }
在其他组件/模块中,我可以调用它来导航到不同的屏幕:
- Base.screen.nav().navigate(...);
这样不管在哪个屏幕上,并且可以随时获取导航对象以在需要时重定向用户。
(10)react-navigation高级用法:实现自定义Tab切换效果。
react-navigation 库中提供了实现自定义Router切换的方式,需要用到的组件如下:
- TabRouter,createNavigator,createNavigationContainer
1. TabRouter用来自定义路由栈
2.createNavigator用来创建导航组件
3.createNavigationContainer作为导航组件的容器组件
自定义Router切换的流程大致如下:
1. 创建StackNavigator
2. 创建TabRouter
3. 定义导航样式
4. 定义整体路由切换组件
5. 创建Navigator
来看核心代码:
- // 界面组件
- import FirstPage from './scene/FirstPage';
- import SecondPage from './scene/SecondPage';
- import ThirdPage from './scene/ThirdPage';
- import DetailPage from './scene/DetailPage';
- // 引入 react-navigation 核心组件
- import {
- TabRouter,StackNavigator,addNavigationHelpers,createNavigationContainer,} from 'react-navigation';
- // 创建 3个 StackNavigator
- const FirstScreen = StackNavigator(
- {
- First: {
- screen: FirstPage
- },Detail: {
- screen: DetailPage
- }
- }
- );
- const SecondScreen = StackNavigator(
- {
- Second: {
- screen: SecondPage
- }
- }
- );
- const ThirdScreen = StackNavigator(
- {
- Third: {
- screen: ThirdPage
- }
- }
- );
- // 定义 TabRouter
- const FirstScene = ({ navigation }) => (
- <FirstScreen />
- );
- const SecondScene = ({ navigation }) => (
- <SecondScreen />
- );
- const ThirdScene = ({ navigation }) => (
- <ThirdScreen />
- );
- const CustomTabRouter = TabRouter(
- {
- First: {
- screen: FirstScene,path: 'firstScene'
- },Second: {
- screen: SecondScene,path: 'secondScene'
- },Third: {
- screen: ThirdScene,path: 'thirdScene'
- },{
- initialRouteName: 'First'
- }
- );
- // 定义TabBar
- const CustomTabBar = ({ navigation,activeRouteName }) => {
- const { routes } = navigation.state;
- return (
- <View style={ styles.tabContainer }>
- <ScrollView>
- {
- routes.map((route,index)=>(
- <TouchableOpacity
- key={ index }
- onPress={() => navigation.navigate(route.routeName)}>
- <Text style={[
- styles.tabbarText,activeRouteName === route.routeName ? styles.active : styles.inactive
- ]}>
- { route.routeName }
- </Text>
- </TouchableOpacity>
- ))
- }
- </ScrollView>
- </View>
- )
- }
- // 定义TabView
- const CustomTabView = ({ router,navigation }) => {
- const { routes,index } = navigation.state;
- const activeRouteName = routes[index].routeName;
- const ActiveScreen = router.getComponentForRouteName(activeRouteName);
- return(
- <View style={ styles.container }>
- <CustomTabBar
- navigation={ navigation }
- activeRouteName={ activeRouteName } />
- <ActiveScreen
- navigation={
- addNavigationHelpers(
- {
- dispath: navigation.dispatch,state: routes[index]
- }
- )
- }
- />
- </View>
- )
- }
- // 创建Navigator
- const CustomTabs = createNavigationContainer(
- createNavigator(CustomTabRouter)(CustomTabView)
- )
- export default CustomTabs;
通过上述代码,我们就可以创建出类似于饿了么App中商品分类的模块切换效果。
- // Style 样式
- const styles = StyleSheet.create({
- tabContainer: {
- width: 86,zIndex: 888,flexDirection:'column',justifyContent:'center',backgroundColor: '#e7e7e7',borderRightWidth:1,borderColor: '#e0e0e0'
- },tabbarText: {
- fontSize: 18,fontWeight: 'bold',marginTop: 20,marginBottom: 20,color: 'black'
- },active: {
- color: 'red',inactive: {
- color: 'black',container: {
- flexDirection:'row',flex: 1,}
- });
(11)定义某个界面的切换动画效果
有时候产品会存在某个界面的切换动画和其他不同,那么如何实现呢?很简单,只需要在StackNavigator中配置参数下声明以下代码:
- transitionConfig:()=>({
- screenInterpolator:
- (props)=> {
- const { scene } = props
- if (scene.route.routeName === 'VIPDetailPage') {
- return CardStackStyleInterpolator.forFade
- } else {
- return CardStackStyleInterpolator.forHorizontal(props)
- }
- }
- })
效果图
自定义TabRouter: