React Native踩坑记录之——屏幕适配

写在前面

前端开发一定躲不开的一个话题就是屏幕适配,主播最近在写React Native(以下简称RN)的时候感觉写样式很不方便,特别是RN的尺寸是无单位的,也不能使用vw、vh这种视口单位,无法满足在不同屏幕上达到一致的显示效果的需求,话不多说进入正题

解决方案

既然RN中不存在vw、vh,那我们就自己写一个,RN提供了一个Dimension API可以获取到屏幕的宽高

ts 复制代码
const screenWidth = Dimensions.get('window').width;
function vw(size: number) {
    const scale = screenWidth / 100;
    return PixelRatio.roundToNearestPixel(size * scale);
}

// 使用起来也很简单
StyleSheet.create({
  box: {
    width: vw(100),
    height: vw(100),
    borderRadius: vw(50),
  }
});

类似的我们还可以做设计稿尺寸的转换

ts 复制代码
const screenWidth = Dimensions.get('window').width;
const DESIGN_WIDTH = 414;
export function vw(designSize: number) {
    const scale = screenWidth / DESIGN_WIDTH;
    return PixelRatio.roundToNearestPixel(designSize * scale);
}

但是当样式多起来总感觉这样写起来不够简便,于是我决定换个方法,直接在创建样式的时候进行尺寸的转换

ts 复制代码
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };

// 提取样式转换逻辑到单独的函数
function transformStyleValue(style: any) {
    for (const prop in style) {
        const value = style[prop];
        if (typeof value === 'number') {
            style[prop] = vw(value);
        }
        // number + vw 写法支持
        if (typeof value === 'string' && value.includes('vw')) {
            style[prop] = vw(parseFloat(value.replace('vw', '')));
        }
    }
    return style;
}

export const createStyle = <T extends NamedStyles<T> | NamedStyles<any>>(styles: T & NamedStyles<any>, options: CreateStyleOptions = DEFAULT_OPTIONS): T => {
    // ...

    const transformedStyles = { ...styles } as T;
    for (const key in transformedStyles) {
        const style = transformedStyles[key] as any;
        transformedStyles[key] = transformStyleValue(style);
    }

    return StyleSheet.create(transformedStyles);
};

但是这样会有个问题,如果我不希望某个属性响应式,比如字体大小太大或太小会影响视觉体验,我们可以在原函数做出一点点的改变,创建样式表时加入一些配置

ts 复制代码
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };

// 定义 options 参数的类型
type CreateStyleOptions = {
    exclude: string[];
};

const DEFAULT_OPTIONS: CreateStyleOptions = { exclude: ['fontSize'] };

// 提取样式转换逻辑到单独的函数
function transformStyleValue(style: any, options: CreateStyleOptions) {
    const { exclude } = options;
    for (const prop in style) {
        // 属性排除
        if (exclude.includes(prop)) {
            continue;
        }
        const value = style[prop];
        if (typeof value === 'number') {
            style[prop] = vw(value);
        }
        if (typeof value === 'string' && value.includes('vw')) {
            style[prop] = vw(parseFloat(value.replace('vw', '')));
        }
    }
    return style;
}

export const createStyle = <T extends NamedStyles<T> | NamedStyles<any>>(styles: T & NamedStyles<any>, options: CreateStyleOptions = DEFAULT_OPTIONS): T => {
    // ...

    const transformedStyles = { ...styles } as T;
    for (const key in transformedStyles) {
        const style = transformedStyles[key] as any;
        transformedStyles[key] = transformStyleValue(style, options);
    }
    return StyleSheet.create(transformedStyles);
};

总结

通过上述方案,实现了 RN 的屏幕适配,既保证了大部分元素能够根据屏幕尺寸灵活调整,又避免了关键属性因过度转换而影响用户体验。在实际项目中,可根据具体需求调整设计稿宽度和排除属性列表,以达到最佳的适配效果

如何有更好的方案也欢迎提出来

相关推荐
夏幻灵几秒前
Vue / React 列表渲染中的 key:为什么没有 key,Checkbox 可能“对号跑错”?
前端·javascript·vue.js
YWL几秒前
vue-time-axis-plus进阶:双进度条+跨空白续播+播放指针,打造专业级监控回放体验
前端·javascript·vue·时间轴
mmsx1 分钟前
MapLibre 实战 09|Bug 单写着"地图被风刮走了":一个瓦片源工厂,是三个线上事故换来的
android·前端·开源
不羁的木木4 分钟前
给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南
前端·flutter·harmonyos
广州山泉婚姻9 分钟前
微服务时代,前后端分离架构该怎样高效协同?
前端·后端
EatFan13 分钟前
多租户系统到底怎么设计?从 tenant_id 到 JWT 租户隔离的真实实践
前端·后端·全栈·多租户
计算机魔术师20 分钟前
硅谷四大AI巨头联手踩刹车,是真心怕失控还是怕被超越?
前端
岁岁种桃花儿28 分钟前
Vue组件第六篇:Vue单文件组件
开发语言·前端·javascript·vue.js
yivifu31 分钟前
创建支持交叉链接且鼠标悬停显示注释框的HTML文件
前端·javascript·html
GreenTea1 小时前
🔥别再用压缩了,Codex、NVIDIA、DeepSeek、Uber 给出 Agent 上下文管理的新答案
前端·后端·算法