支持安全区域
默认情况下,React Navigation 尝试确保导航器的元素在带有刘海(例如 iPhone X)和可能与应用内容重叠的 UI 元素设备上正确显示。这些元素包括
- 物理刘海
- 状态栏覆盖
- iOS 上的 Home 活动指示器
- Android 上的导航栏
未被这些元素重叠的区域称为“安全区域”。
我们尝试在导航器的 UI 元素上应用适当的内边距,以避免被这些元素重叠。目标是 (a) 最大化屏幕的使用率 (b) 而不隐藏内容或使其难以交互,因为内容会被物理显示屏切口或某些操作系统 UI 遮挡。
虽然 React Navigation 默认处理内置 UI 元素的安全区域,但您自己的内容也可能需要处理它,以确保内容不会被这些元素隐藏。
很想通过将整个应用包裹在一个带有内边距的容器中来解决 (a),以确保所有内容都不会被遮挡。但是这样做,我们会浪费屏幕上大量的空间,如下图左侧的图像所示。我们理想情况下想要的是右侧的图像。
虽然 React Native 导出了 SafeAreaView
组件,但此组件仅支持 iOS 10+,不支持旧版本的 iOS 或 Android。此外,它还有一些问题,例如,如果包含安全区域的屏幕正在动画,则会导致跳跃行为。因此,我们建议使用 react-native-safe-area-context 库中的 useSafeAreaInsets
hook,以更可靠的方式处理安全区域。
react-native-safe-area-context
库也导出了 SafeAreaView
组件。虽然它在 Android 上有效,但在垂直动画方面也存在相同的跳跃行为问题。此外,SafeAreaView
组件和 useSafeAreaInsets
hook 可能会在不同的时间更新,导致一起使用时出现闪烁。因此,我们建议始终使用 useSafeAreaInsets
hook,并避免使用 SafeAreaView
组件以获得一致的行为。
本指南的其余部分提供了有关如何在 React Navigation 中支持安全区域的更多信息。
隐藏/自定义标题栏或标签栏
React Navigation 在默认标题中处理安全区域。但是,如果您正在使用自定义标题,则确保您的 UI 在安全区域内非常重要。
例如,如果我为 header
或 tabBar
渲染任何内容,则不会渲染任何内容
- 静态
- 动态
const MyTabs = createBottomTabNavigator({
initialRouteName: 'Analytics',
tabBar: () => null,
screenOptions: {
headerShown: false,
},
screens: {
Analytics: Demo,
Profile: Demo,
},
});
const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screenOptions: {
headerShown: false,
},
screens: {
Home: MyTabs,
Settings: Demo,
},
});
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
function Demo() {
return (
<View
style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}
>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="Home">
{() => (
<Tab.Navigator
initialRouteName="Analytics"
tabBar={() => null}
screenOptions={{ headerShown: false }}
>
<Tab.Screen name="Analytics" component={Demo} />
<Tab.Screen name="Profile" component={Demo} />
</Tab.Navigator>
)}
</Stack.Screen>
<Stack.Screen name="Settings" component={Demo} />
</Stack.Navigator>
</NavigationContainer>
);
}
要解决此问题,您可以在内容上应用安全区域内边距。这可以使用 react-native-safe-area-context
库中的 useSafeAreaInsets
hook 来实现
- 静态
- 动态
function Demo() {
const insets = useSafeAreaInsets();
return (
<View
style={{
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
}}
>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</View>
);
}
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return (
<SafeAreaProvider>
<Navigation />
</SafeAreaProvider>
);
}
function Demo() {
const insets = useSafeAreaInsets();
return (
<View
style={{
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
}}
>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</View>
);
}
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
{/*(...) */}
</NavigationContainer>
</SafeAreaProvider>
);
}
请务必按照此处的说明将您的应用包裹在 SafeAreaProvider
中。
这将检测应用是否在带有刘海的设备上运行,如果是,则确保内容不会隐藏在任何硬件元素后面。
横向模式
即使您正在使用默认的导航栏和标签栏 - 如果您的应用程序在横向模式下工作,则确保您的内容不会隐藏在传感器簇后面非常重要。
要解决此问题,您可以再次将安全区域内边距应用于您的内容。这不会与纵向模式下导航栏或标签栏的默认行为冲突。
使用 hook 以获得更多控制
在某些情况下,您可能需要更多地控制应用哪些内边距。例如,您可以通过更改 style
对象仅应用顶部和底部内边距
- 静态
- 动态
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
function Demo() {
const insets = useSafeAreaInsets();
return (
<View
style={{
paddingTop: insets.top,
paddingBottom: insets.bottom,
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</View>
);
}
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return (
<SafeAreaProvider>
<Navigation />
</SafeAreaProvider>
);
}
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
function Demo() {
const insets = useSafeAreaInsets();
return (
<View
style={{
paddingTop: insets.top,
paddingBottom: insets.bottom,
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</View>
);
}
类似地,您可以在 FlatList
的 contentContainerStyle
中应用这些内边距,以使内容避开安全区域,但在滚动时仍然在状态栏和导航栏下显示它们。
总结
- 使用
react-native-safe-area-context
中的useSafeAreaInsets
hook 而不是SafeAreaView
组件 - 不要将整个应用包裹在
SafeAreaView
中,而是将样式应用于屏幕内部的内容 - 使用
useSafeAreaInsets
hook 仅应用特定的内边距以获得更多控制