全面屏适配的核心问题
1 主要挑战
- 刘海屏/水滴屏:屏幕顶部的异形区域
- 手势导航栏:底部的 Home 指示条
- 高宽比变化:18:9、19.5:9、20:9 等比例
- 状态栏高度变化:不同厂商定制
2 需要适配的区域
┌─────────────────────────────────────┐
│ 状态栏 │
│ (可能包含刘海/水滴) │
├─────────────────────────────────────┤
│ │
│ 安全内容区域 │
│ │
├─────────────────────────────────────┤
│ 手势导航区/虚拟按键栏 │
└─────────────────────────────────────┘
OpenClaw 全面屏适配方案
1 获取安全区域信息
// 在 OpenClaw 中获取设备信息
import { Device } from '@openclaw/core';
class ScreenAdapter {
// 获取安全区域
static getSafeAreaInsets(): {
top: number; // 状态栏高度
bottom: number; // 底部安全距离
left: number; // 左侧安全距离
right: number; // 右侧安全距离
} {
if (Device.isIOS()) {
return this.getIOSSafeArea();
} else if (Device.isAndroid()) {
return this.getAndroidSafeArea();
}
return { top: 0, bottom: 0, left: 0, right: 0 };
}
// iOS 安全区域
private static getIOSSafeArea() {
// 使用 react-native-safe-area-context 或 Capacitor 等
const { getInsets } = require('react-native-safe-area-context');
return getInsets();
}
// Android 安全区域
private static getAndroidSafeArea() {
const { StatusBar, Dimensions } = require('react-native');
// 获取状态栏高度
const statusBarHeight = StatusBar.currentHeight || 0;
// 检查是否有底部导航栏
const windowHeight = Dimensions.get('window').height;
const screenHeight = Dimensions.get('screen').height;
const bottomNavHeight = screenHeight - windowHeight - statusBarHeight;
return {
top: statusBarHeight,
bottom: Math.max(bottomNavHeight, 0),
left: 0,
right: 0
};
}
}
2 全面屏适配组件
// SafeAreaView 组件
import React from 'react';
import { View, StyleSheet, Platform } from 'react-native';
import { ScreenAdapter } from './ScreenAdapter';
interface SafeAreaViewProps {
children: React.ReactNode;
top?: boolean; // 是否避开顶部
bottom?: boolean; // 是否避开底部
left?: boolean; // 是否避开左侧
right?: boolean; // 是否避开右侧
style?: any;
}
export const SafeAreaView: React.FC<SafeAreaViewProps> = ({
children,
top = true,
bottom = true,
left = false,
right = false,
style
}) => {
const insets = ScreenAdapter.getSafeAreaInsets();
const paddingStyles = {
paddingTop: top ? insets.top : 0,
paddingBottom: bottom ? insets.bottom : 0,
paddingLeft: left ? insets.left : 0,
paddingRight: right ? insets.right : 0,
};
return (
<View style={[styles.container, paddingStyles, style]}>
{children}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
}
});
3 页面布局适配
// 页面容器组件
import React from 'react';
import { StyleSheet, StatusBar } from 'react-native';
import { SafeAreaView } from './SafeAreaView';
export const PageContainer: React.FC<{
children: React.ReactNode;
safeArea?: boolean;
statusBarStyle?: 'light-content' | 'dark-content';
}> = ({
children,
safeArea = true,
statusBarStyle = 'dark-content'
}) => {
React.useEffect(() => {
// 设置状态栏样式
StatusBar.setBarStyle(statusBarStyle);
// Android 沉浸式状态栏
if (Platform.OS === 'android') {
StatusBar.setTranslucent(true);
StatusBar.setBackgroundColor('transparent');
}
}, [statusBarStyle]);
if (safeArea) {
return (
<SafeAreaView top bottom>
{children}
</SafeAreaView>
);
}
return <>{children}</>;
};
// 使用示例
const HomePage = () => {
return (
<PageContainer statusBarStyle="light-content">
{/* 页面内容 */}
</PageContainer>
);
};
刘海屏特殊处理
1 iOS 刘海屏适配
// iOS 刘海屏检测和适配
import { Platform, Dimensions } from 'react-native';
class NotchAdapter {
// 判断是否为 iPhone 刘海屏
static isIPhoneWithNotch(): boolean {
if (Platform.OS !== 'ios') return false;
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
// iPhone 刘海屏型号判断
const isIPhoneX =
Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTVOS &&
(height === 812 || width === 812 || // iPhone X/XS/11 Pro
height === 896 || width === 896 || // iPhone XS Max/XR/11/11 Pro Max
height === 844 || width === 844 || // iPhone 12/13 Pro, 12/13
height === 926 || width === 926 || // iPhone 12/13 Pro Max
height === 852 || width === 852 || // iPhone 14/15
height === 932 || width === 932); // iPhone 14/15 Pro Max
return isIPhoneX;
}
// 获取刘海屏顶部偏移
static getNotchTopOffset(): number {
if (!this.isIPhoneWithNotch()) {
return Platform.OS === 'ios' ? 20 : 0; // 普通 iOS 状态栏高度
}
return 44; // 刘海屏状态栏高度
}
}
2 Android 刘海屏适配
// Android 刘海屏适配
import { NativeModules, Platform } from 'react-native';
class AndroidNotchAdapter {
// 检查设备是否支持刘海屏
static hasDisplayCutout(): Promise<boolean> {
if (Platform.OS !== 'android') return Promise.resolve(false);
// 使用 Android API Level 28+ 的 DisplayCutout
return new Promise((resolve) => {
if (NativeModules.DeviceInfo) {
NativeModules.DeviceInfo.hasNotch()
.then(resolve)
.catch(() => resolve(false));
} else {
resolve(false);
}
});
}
// 获取刘海屏信息
static getCutoutInfo(): Promise<{
top: number;
bottom: number;
left: number;
right: number;
}> {
return new Promise((resolve) => {
const info = {
top: 0,
bottom: 0,
left: 0,
right: 0
};
// 实际实现需要原生模块支持
resolve(info);
});
}
}
手势导航适配
1 底部手势导航区适配
// 底部手势导航适配
import { Dimensions, Platform, StyleSheet } from 'react-native';
export const NavigationBarAdapter = {
// 获取底部手势导航栏高度
getGestureBarHeight(): number {
if (Platform.OS === 'ios') {
// iOS 手势条高度
const { height } = Dimensions.get('window');
if (height >= 812) return 34; // iPhone X 及以上
return 0;
}
if (Platform.OS === 'android') {
// Android 手势导航条高度
const windowHeight = Dimensions.get('window').height;
const screenHeight = Dimensions.get('screen').height;
const gestureBarHeight = screenHeight - windowHeight;
// 通常为 0 或 24-48px
return gestureBarHeight > 100 ? 0 : gestureBarHeight;
}
return 0;
},
// 底部固定组件适配
createBottomFixedStyle(extraPadding: number = 0) {
const gestureHeight = this.getGestureBarHeight();
return {
paddingBottom: gestureHeight + extraPadding
};
}
};
// 使用示例:底部标签栏
const BottomTabBar: React.FC = () => {
return (
<View style={[
styles.tabBar,
NavigationBarAdapter.createBottomFixedStyle(20)
]}>
{/* 标签内容 */}
</View>
);
};
const styles = StyleSheet.create({
tabBar: {
height: 60,
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#eee',
}
});
响应式布局适配
1 响应式设计工具
// 响应式布局工具
import { Dimensions, ScaledSize } from 'react-native';
class ResponsiveDesign {
// 设计稿基准尺寸
private static readonly DESIGN_WIDTH = 375;
private static readonly DESIGN_HEIGHT = 812;
// 当前窗口尺寸
static window: ScaledSize = Dimensions.get('window');
// 监听尺寸变化
static listenToOrientationChanges(callback: (window: ScaledSize) => void) {
Dimensions.addEventListener('change', ({ window }) => {
this.window = window;
callback(window);
});
}
// 横向百分比布局
static wp(percentage: number): number {
const value = (percentage * this.window.width) / 100;
return Math.round(value);
}
// 纵向百分比布局
static hp(percentage: number): number {
const value = (percentage * this.window.height) / 100;
return Math.round(value);
}
// 字体大小适配
static fontSize(size: number): number {
const scale = this.window.width / this.DESIGN_WIDTH;
const newSize = size * scale;
// 根据屏幕尺寸调整
if (this.window.width < 375) {
return newSize * 0.95;
} else if (this.window.width > 414) {
return newSize * 1.05;
}
return newSize;
}
// 判断是否为全面屏
static isFullScreen(): boolean {
const { width, height } = this.window;
const aspectRatio = height / width;
return aspectRatio > 1.8; // 长宽比大于 1.8 认为是全面屏
}
}
2 网格布局系统
// 网格布局组件
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { ResponsiveDesign } from './ResponsiveDesign';
interface GridProps {
children: React.ReactNode;
columns?: number;
spacing?: number;
}
export const Grid: React.FC<GridProps> = ({
children,
columns = 2,
spacing = ResponsiveDesign.wp(2)
}) => {
const childrenArray = React.Children.toArray(children);
return (
<View style={[styles.container, { marginHorizontal: -spacing / 2 }]}>
{childrenArray.map((child, index) => (
<View
key={index}
style={[
styles.item,
{
width: `${100 / columns}%`,
padding: spacing / 2
}
]}
>
{child}
</View>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
flexWrap: 'wrap',
},
item: {
// 宽度通过 style 动态设置
}
});
配置和优化
1 App 启动配置
// iOS Info.plist 配置
{
"UIViewControllerBasedStatusBarAppearance": true,
"UIStatusBarStyle": "UIStatusBarStyleDefault",
// 刘海屏适配
"UIStatusBarHidden": false,
"UILaunchStoryboardName": "LaunchScreen",
// 安全区域配置
"UIRequiresFullScreen": false,
// iPhone X 系列适配
"UIWindowScene": {
"UISceneConfigurations": {
"UIWindowSceneSessionRoleApplication": [{
"UISceneConfigurationName": "Default Configuration",
"UISceneDelegateClassName": "$(PRODUCT_MODULE_NAME).SceneDelegate"
}]
}
}
}
<!-- Android styles.xml -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- 沉浸式状态栏 -->
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<!-- 刘海屏适配 -->
<item name="android:windowLayoutInDisplayCutoutMode">
shortEdges
</item>
</style>
2 性能优化建议
// 全面屏适配性能优化
class PerformanceOptimizer {
// 防抖处理尺寸变化
static debounceResize(callback: Function, delay: number = 250) {
let timeoutId: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callback(...args);
}, delay);
};
}
// 使用 FlatList 优化长列表
static optimizeFlatListConfig() {
return {
windowSize: 21, // 渲染区域大小
initialNumToRender: 10, // 初始渲染数量
maxToRenderPerBatch: 10, // 每批渲染数量
updateCellsBatchingPeriod: 50, // 批处理间隔
removeClippedSubviews: true, // 移除屏幕外视图
getItemLayout: (data: any[], index: number) => ({
length: 100, // 固定高度提升性能
offset: 100 * index,
index
})
};
}
}
测试和验证
// 全面屏适配测试工具
export class ScreenAdapterTest {
// 测试所有屏幕类型
static runAllTests() {
const testCases = [
{
name: 'iPhone 12 Pro',
width: 390,
height: 844,
expectedSafeTop: 47,
expectedSafeBottom: 34
},
{
name: 'Android 普通屏',
width: 360,
height: 640,
expectedSafeTop: 24,
expectedSafeBottom: 0
},
// 添加更多测试用例...
];
testCases.forEach(testCase => {
console.log(`测试: ${testCase.name}`);
// 模拟测试逻辑
});
}
// 视觉回归测试
static visualRegressionTest(screenshotPath: string) {
// 使用 Appium 或 Detox 进行自动化测试
// 对比不同屏幕尺寸的截图
}
}
OpenClaw 全面屏适配的关键点:

- 安全区域处理:使用 SafeAreaView 包裹内容
- 动态布局:基于屏幕尺寸和比例进行响应式设计
- 平台差异:分别处理 iOS 和 Android 的适配逻辑
- 手势支持:适配底部手势导航区
- 性能考虑:避免因适配导致的性能问题
- 测试覆盖:确保在各种全面屏设备上表现一致
建议在实际项目中结合具体业务需求,选择适合的适配方案,并建立完善的测试机制确保兼容性。
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。