React Navigation 4.x
近期将项目中的 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
版本移除了各类导航器,需要手动安装,这里安装一下 StackNavigator
和 BottomTabNavigator
。
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,
},
})
然后通过 BottomTabNavigator
的 defaultNavigationOptions
属性设置不同页面的图标:
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
事件。