美文网首页
React Navigation 4.x

React Navigation 4.x

作者: 皱巴巴 | 来源:发表于2020-01-13 17:05 被阅读0次

    近期将项目中的 react native 升级到了 0.60.x 版本,同步的也将 React Navigation 升级到了 4.x。这篇博客记录了 4.x 版本的一些基本用法以及在实现项目中一些常见功能的实现,其中 rn 基于 0.60 版本。

    安装

    4.x 版本从 react-navigation 中移除了各类导航器,同时还依赖了一些其他的包需要手动安装。

    npm install react-navigation react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context
    

    rn 0.60 版本之后,安装完成之后会自动 link,低版本安装过程见官网说明

    Android 端需要手动进行一些修改,编辑 android/app/build.gradle,在 dependencies 中添加如下内容:

    implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
    
    WX20200113-140721.png

    编辑 Android 中的 MainActivity.java,添加如下内容:

    package com.reactnavigation.example;
    
    import com.facebook.react.ReactActivity;
    + import com.facebook.react.ReactActivityDelegate;
    + import com.facebook.react.ReactRootView;
    + import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
    
    public class MainActivity extends ReactActivity {
    
      @Override
      protected String getMainComponentName() {
        return "Example";
      }
    
    +  @Override
    +  protected ReactActivityDelegate createReactActivityDelegate() {
    +    return new ReactActivityDelegate(this, getMainComponentName()) {
    +      @Override
    +      protected ReactRootView createRootView() {
    +        return new RNGestureHandlerEnabledRootView(MainActivity.this);
    +      }
    +    };
    +  }
    }
    

    最后在 index.js 或者 app.js 中导入 react-native-gesture-handler 依赖即可。

    import 'react-native-gesture-handler';
    

    基本使用

    4.x 版本移除了各类导航器,需要手动安装,这里安装一下 StackNavigatorBottomTabNavigator

    npm install react-navigation-stack @react-native-community/masked-view react-navigation-tabs
    

    添加三个页面组件:

    const Home = props => {
      const { navigation } = props
      return (
        <View>
          <TouchableOpacity onPress={() => navigation.push('Second')}>
            <View>
              <Text>Second page</Text>
            </View>
          </TouchableOpacity>
        </View>
      )
    }
    
    const My = () => {
      return (
        <View>
          <Text>My</Text>
        </View>
      )
    }
    
    const Second = () => {
      return (
        <View>
          <Text>Second</Text>
        </View>
      )
    }
    

    创建导航器,这一部分写法和之前一样:

    const MainTab = createBottomTabNavigator({
      Home,
      My,
    })
    
    const AppStack = createStackNavigator({
      Main: {
        screen: MainTab,
      },
      Second,
    })
    
    export default function App() {
      const AppContainer = createAppContainer(AppStack)
    
      return <AppContainer />
    }
    
    screenshot-1.gif

    调整 TabNavigator Header 标题

    从上图可以看到,Home 和 My 页面顶部标题都是现实的 Main,因为这两个页面都在 BottomTabNavigator 中,共用了一个 Header。我们可以通过 Main 页面的 navigationOptions 来动态修改标题:

    Main: {
      screen: MainTab,
      navigationOptions: ({ navigation }) => {
        const { routeName } = navigation.state.routes[navigation.state.index]
        // You can do whatever you like here to pick the title based on the route name
        const headerTitle = routeName
    
        return {
          headerTitle,
        }
      }
    }
    

    这里直接使用了 routeName 作为标题,也可以根据 routeName 匹配其他的标题文字。效果如下:


    tab_title.gif

    添加 Tabbar 图标

    通过 BottomTabNavigator navigationOptions 中的 tabBarIcon 属性可以设置 Tabbar 的图标。

    首先实现一个 TabbarIcon 组件,根据 routeName 返回相应的图片,同时图片会读取 tintColor 属性设置颜色:

    const IMAGES = {
      Home: require('./assets/icons/home.png'),
      My: require('./assets/icons/my.png'),
    }
    
    const TabbarIcon = ({ routeName, tintColor }) => {
      return (
        <Image
          source={IMAGES[routeName]}
          style={[styles.image, { tintColor: tintColor }]}
          resizeMode="contain"
        />
      )
    }
    
    const styles = StyleSheet.create({
      image: {
        height: 24,
      },
    })
    

    然后通过 BottomTabNavigatordefaultNavigationOptions 属性设置不同页面的图标:

    defaultNavigationOptions: ({ navigation }) => {
      const { routeName } = navigation.state
      return {
        tabBarIcon: props => <TabbarIcon {...props} routeName={routeName} />,
      }
    }
    

    还可以通过 tabBarOptions 设置激活和未激活的颜色 :

    tabBarOptions: {
      inactiveTintColor: 'rgba(0,0,0,0.45)',
      activeTintColor: '#722ed1',
    }
    
    tab_icon.gif

    统一路由风格

    StackNavigator 在 Android 中的表现和 iOS 中存在一些差异,通过暴露的一些配置项我们可以统一风格,这里统一使用 iOS 的风格。

    首先实现一个 HeaderBackImage 组件,统一返回图标:

    const IS_IOS = Platform.OS === 'ios'
    
    export default props => {
      return (
        <View style={styles.imgContainer}>
          <Image
            source={require('./assets/icons/icon_back.png')}
            style={[styles.image, { tintColor: props.tintColor }]}
            {...props}
          />
        </View>
      )
    }
    
    const styles = StyleSheet.create({
      imgContainer: {
        paddingRight: IS_IOS ? 6 : 15,
        paddingLeft: IS_IOS ? 15 : 0,
      },
      image: {
        backgroundColor: 'transparent',
        height: 16,
        width: 10,
        resizeMode: 'contain',
      },
    })
    

    修改 StackNavigator 配置:

    defaultNavigationOptions: {
      headerStyle: {
        elevation: 0, // 移除 Android Header 阴影
        shadowOpacity: 0, // 移除 iOS Header 阴影
      },
      headerBackImage: HeaderBackImage,
      headerTitleAlign: 'center', // Android 标题居中
      headerBackTitleVisible: false, // 隐藏 iOS 返回按钮标题
      headerPressColorAndroid: 'transparent', // 移除 Android 点击返回按钮效果
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, // 切换路由时水平动画
      headerStyleInterpolator: HeaderStyleInterpolators.forUIKit, // 切换路时 Header 动画
    },
    headerMode: 'float', // 页面共用一个 Header,切换时应用动画
    
    tab_a_android.gif

    沉浸式状态栏

    之前我写过一篇关于实现沉浸式状态栏的博客,在 4.x 版本中实现该功能变得更加方便。

    纯色

    首先我们给 Header 添加一个背景色看看默认是什么效果。

    headerStyle: {
      backgroundColor: '#722ed1',
    },
    headerTintColor: '#fff',
    
    statusbar.png

    可以看到 Android 端状态栏依旧是灰色。我们可以设置 StatusBar 的属性来实现沉浸式:

    <>
      <StatusBar backgroundColor="transparent" translucent />
      <AppContainer />
    </>
    
    status_android.png

    可以看到设置了 translucent 之后,内容并没有往上移动到状态,4.x 版本默认处理了这种情况。

    背景图

    有时页面顶部有一张背景图或者整个页面有个全屏背景,这时我们就需要 Header 透明并且内容能衍生到状态栏底部。

    我们先修改 Second 页面,添加一个背景图片:

    const Second = () => {
      return (
        <ImageBackground
          style={{ flex: 1 }}
          source={require('../../assets/img/bg.jpg')}>
          <Text>I have a full screen background image</Text>
        </ImageBackground>
      )
    }
    
    WX20200113-161531.png

    可以看到图片并没有撑满整个屏幕,设置 headerTransparent 属性为 true 可以使 Header 透明并浮在页面上,这样内容就会撑满。

    Second.navigationOptions = {
      headerTransparent: true,
    }
    
    full.png

    此时虽然全屏了,但是内容却跑到 Header 底部除了,我们需要添加上边距预留出 Header 的位置,通过 useHeaderHeight 可以获取到 Header 的高度。

    import { useHeaderHeight } from 'react-navigation-stack'
    
    const headerHeight = useHeaderHeight()
    
    full_margin.png

    处理状态栏文字

    RN 中切换到不同的页面,可能需要显示不能颜色的文字(深色、浅色),在之前的博客中介绍了使用高阶组件的方法,监听 navigation 的 willFocus 事件切换。4.x 版本中仍需要使用此方案,不过这次通过 hook 来实现。

    // useStatusBar.js
    
    import { useEffect } from 'react'
    import { StatusBar } from 'react-native'
    
    const useStatusBar = (navigation, barStyle) => {
      useEffect(() => {
        const onWillFocus = () => {
          StatusBar.setBarStyle(barStyle)
        }
    
        StatusBar.setBarStyle(barStyle)
    
        const listener = navigation.addListener('willFocus', onWillFocus)
    
        return () => listener.remove()
      }, [])
    }
    
    export default useStatusBar
    

    当页面挂载之后设置状态栏的风格,后续当从其他页面返回时会触发 willFocus 事件。

    相关文章

      网友评论

          本文标题:React Navigation 4.x

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