获取刘海屏信息
检测设备类型
import { Platform, Dimensions, StatusBar } from 'react-native';
import { getStatusBarHeight } from 'react-native-status-bar-height';
import DeviceInfo from 'react-native-device-info';
// 检测是否为刘海屏设备
export const isNotchDevice = () => {
if (Platform.OS === 'ios') {
// iOS 刘海屏判断
const { height, width } = Dimensions.get('window');
return (
Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTV &&
(height === 812 || width === 812 || height === 896 || width === 896)
);
}
// Android 刘海屏判断
if (Platform.OS === 'android') {
return DeviceInfo.hasNotch() || DeviceInfo.hasDynamicIsland();
}
return false;
};
// 获取状态栏高度
export const getStatusBarHeight = () => {
if (Platform.OS === 'ios') {
return isNotchDevice() ? 44 : 20;
}
return StatusBar.currentHeight || 0;
};
// 获取安全区域底部高度
export const getBottomSpace = () => {
if (Platform.OS === 'ios') {
return isNotchDevice() ? 34 : 0;
}
return 0;
};
安全区域适配组件
SafeAreaView 组件
import React from 'react';
import {
View,
StyleSheet,
SafeAreaView as RNSafeAreaView,
Platform,
} from 'react-native';
import { getStatusBarHeight, getBottomSpace } from './device';
interface SafeAreaViewProps {
children: React.ReactNode;
style?: any;
topColor?: string;
bottomColor?: string;
topInset?: boolean;
bottomInset?: boolean;
}
export const SafeAreaView: React.FC<SafeAreaViewProps> = ({
children,
style,
topColor = 'transparent',
bottomColor = 'transparent',
topInset = true,
bottomInset = true,
}) => {
const statusBarHeight = getStatusBarHeight();
const bottomSpace = getBottomSpace();
if (Platform.OS === 'ios') {
return (
<View style={[styles.container, style]}>
{topInset && (
<View style={[styles.topBar, { height: statusBarHeight, backgroundColor: topColor }]} />
)}
<View style={[styles.content, { marginTop: topInset ? 0 : -statusBarHeight }]}>
{children}
</View>
{bottomInset && (
<View style={[styles.bottomBar, { height: bottomSpace, backgroundColor: bottomColor }]} />
)}
</View>
);
}
// Android 使用 RN 的 SafeAreaView
return (
<RNSafeAreaView style={[styles.androidContainer, style]}>
{children}
</RNSafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
topBar: {
width: '100%',
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
content: {
flex: 1,
},
bottomBar: {
width: '100%',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
},
androidContainer: {
flex: 1,
},
});
带安全区域的头部导航栏
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
StatusBar,
} from 'react-native';
import { getStatusBarHeight } from './device';
interface HeaderProps { string;
leftText?: string;
onLeftPress?: () => void;
rightText?: string;
onRightPress?: () => void;
}
export const Header: React.FC<HeaderProps> = ({
leftText,
onLeftPress,
rightText,
onRightPress,
}) => {
const statusBarHeight = getStatusBarHeight();
return (
<View style={[styles.container, { paddingTop: statusBarHeight }]}>
<StatusBar
translucent
backgroundColor="transparent"
barStyle="dark-content"
/>
<View style={styles.header}>
{leftText && (
<TouchableOpacity style={styles.leftButton} onPress={onLeftPress}>
<Text style={styles.buttonText}>{leftText}</Text>
</TouchableOpacity>
)}
<View style={styles.titleContainer}>
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
</View>
{rightText && (
<TouchableOpacity style={styles.rightButton} onPress={onRightPress}>
<Text style={styles.buttonText}>{rightText}</Text>
</TouchableOpacity>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#e0e0e0',
},
header: {
height: 44,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 12,
},
leftButton: {
minWidth: 60,
justifyContent: 'center',
},
rightButton: {
minWidth: 60,
alignItems: 'flex-end',
justifyContent: 'center',
},
buttonText: {
fontSize: 16,
color: '#007AFF',
},Container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}, {
fontSize: 17,
fontWeight: '600',
color: '#000000',
},
});
页面布局适配
基础页面布局
import React from 'react';
import {
View,
StyleSheet,
ScrollView,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import { SafeAreaView } from './SafeAreaView';
import { getBottomSpace } from './device';
interface PageProps {
children: React.ReactNode;
safeAreaTop?: boolean;
safeAreaBottom?: boolean;
scrollable?: boolean;
style?: any;
}
export const Page: React.FC<PageProps> = ({
children,
safeAreaTop = true,
safeAreaBottom = true,
scrollable = false,
style,
}) => {
const Container = scrollable ? ScrollView : View;
return (
<SafeAreaView
topInset={safeAreaTop}
bottomInset={safeAreaBottom}
style={[styles.container, style]}
>
<KeyboardAvoidingView
style={styles.keyboardView}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<Container
style={[
styles.content,
scrollable && styles.scrollContent,
{ paddingBottom: safeAreaBottom ? getBottomSpace() : 0 }
]}
showsVerticalScrollIndicator={false}
>
{children}
</Container>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
keyboardView: {
flex: 1,
},
content: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
},
});
底部TabBar适配
import React from 'react';
import {
View,
TouchableOpacity,
Text,
StyleSheet,
Dimensions,
} from 'react-native';
import { getBottomSpace } from './device';
const { width } = Dimensions.get('window');
interface TabBarItem {
label: string;
icon: React.ReactNode;
onPress: () => void;
active: boolean;
}
interface TabBarProps {
items: TabBarItem[];
}
export const TabBar: React.FC<TabBarProps> = ({ items }) => {
const bottomSpace = getBottomSpace();
const tabWidth = width / items.length;
return (
<View style={[styles.container, { paddingBottom: bottomSpace }]}>
<View style={styles.tabBar}>
{items.map((item, index) => (
<TouchableOpacity
key={index}
style={[styles.tabItem, { width: tabWidth }]}
onPress={item.onPress}
activeOpacity={0.8}
>
<View style={styles.iconContainer}>
{item.icon}
</View>
<Text style={[
styles.label,
item.active && styles.activeLabel
]}>
{item.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: '#e0e0e0',
},
tabBar: {
flexDirection: 'row',
height: 49,
},
tabItem: {
alignItems: 'center',
justifyContent: 'center',
},
iconContainer: {
marginBottom: 2,
},
label: {
fontSize: 10,
color: '#8e8e93',
},
activeLabel: {
color: '#007AFF',
},
});
工具函数
设备信息工具
// deviceUtils.ts
import { Dimensions, Platform } from 'react-native';
import DeviceInfo from 'react-native-device-info';
export const DeviceUtils = {
// 获取屏幕尺寸
getScreenSize() {
const { width, height } = Dimensions.get('window');
return { width, height };
},
// 判断是否为刘海屏
isNotchScreen() {
const { height, width } = Dimensions.get('window');
if (Platform.OS === 'ios') {
// iPhone X, Xs, 11 Pro, 12 mini, 13 mini
if (height === 812 || width === 812) return true;
// iPhone XR, Xs Max, 11, 11 Pro Max, 12, 12 Pro, 13, 13 Pro, 14
if (height === 896 || width === 896) return true;
// iPhone 12 Pro Max, 13 Pro Max, 14 Plus
if (height === 926 || width === 926) return true;
// iPhone 14 Pro
if (height === 852 || width === 852) return true;
// iPhone 14 Pro Max
if (height === 932 || width === 932) return true;
return false;
}
if (Platform.OS === 'android') {
return DeviceInfo.hasNotch();
}
return false;
},
// 获取安全区域
getSafeAreaInsets() {
const isNotch = this.isNotchScreen();
if (Platform.OS === 'ios') {
return {
top: isNotch ? 44 : 20,
bottom: isNotch ? 34 : 0,
left: 0,
right: 0,
};
}
if (Platform.OS === 'android') {
const statusBarHeight = Platform.Version >= 21 ? 24 : 0;
return {
top: statusBarHeight,
bottom: isNotch ? 16 : 0,
left: 0,
right: 0,
};
}
return { top: 0, bottom: 0, left: 0, right: 0 };
},
// 是否为全面屏
isFullScreen() {
const screenSize = this.getScreenSize();
const aspectRatio = screenSize.height / screenSize.width;
return aspectRatio > 1.8;
},
};
样式适配
全局样式设置
// styles.ts
import { StyleSheet } from 'react-native';
import { DeviceUtils } from './deviceUtils';
const { top, bottom } = DeviceUtils.getSafeAreaInsets();
export const GlobalStyles = StyleSheet.create({
// 安全区域容器
safeContainer: {
flex: 1,
paddingTop: top,
paddingBottom: bottom,
},
// 页面容器
pageContainer: {
flex: 1,
backgroundColor: '#FFFFFF',
},
// 头部安全间距
headerSafeTop: {
height: top,
backgroundColor: '#FFFFFF',
},
// 底部安全间距
bottomSafeArea: {
height: bottom,
backgroundColor: '#FFFFFF',
},
// 列表底部间距
listFooter: {
height: bottom,
},
// 输入框底部间距(用于键盘弹出时)
inputBottomMargin: {
marginBottom: Platform.OS === 'ios' ? bottom + 10 : 10,
},
// 按钮底部固定
fixedButtonContainer: {
position: 'absolute',
left: 16,
right: 16,
bottom: bottom + 16,
},
});
使用示例
import React from 'react';
import { Page } from './Page';
import { Header } from './Header';
import { TabBar } from './TabBar';
const HomeScreen: React.FC = () => {
return (
<Page safeAreaTop safeAreaBottom scrollable={false}>
<Header title="首页" />
{/* 页面内容 */}
<View style={{ flex: 1 }}>
{/* 你的页面内容 */}
</View>
{/* 底部TabBar */}
<TabBar
items={[
{
label: '首页',
icon: <HomeIcon />,
onPress: () => {},
active: true,
},
// ... 其他tab项
]}
/>
</Page>
);
};
注意事项
- 测试不同设备:在多种刘海屏设备上测试
- 横屏适配:考虑横屏时的安全区域
- 键盘处理:键盘弹出时需要调整布局
- 动态岛适配:对于iPhone 14 Pro及以上机型,需要考虑动态岛的交互
- Android多样:不同Android厂商的刘海实现不同,需要兼容处理
这个适配方案涵盖了刘海屏适配的主要场景,可以根据具体需求进行调整和扩展。

版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。