查看: 111|回复: 0

鸿蒙上RN用PlatformColor调系统色,深色模式适配与踩坑

[复制链接]
发表于 半小时前 | 显示全部楼层 |阅读模式
在鸿蒙设备上做 React Native(RN)开发,深浅色模式适配是个绕不开的话题。很多开发者习惯在代码里写死 #FFFFFF 或 #000000,但这样在系统切换深色模式时,自定义 View 的颜色不会跟着变化,看起来会非常突兀。实际上,React Native 提供的 PlatformColor 可以直接引用系统原生色值,让颜色自动跟随系统主题切换,省掉一堆手动判断逻辑。不过,在鸿蒙上使用 PlatformColor 有几个坑需要提前知道。

PlatformColor 的用法很直接:传入系统颜色的字符串名称,返回对应平台的原生颜色值,然后可以在 StyleSheet 里直接使用。
  1. import { PlatformColor } from 'react-native';
  2. // iOS 系统色
  3. const labelColor = PlatformColor('label');
  4. const systemBlue = PlatformColor('systemBlue');
  5. // Android 系统色
  6. const primaryText = PlatformColor('?android:attr/textColorPrimary');
  7. const holoBlue = PlatformColor('@android:color/holo_blue_bright');
复制代码

在 StyleSheet 中直接使用返回的颜色即可:
  1. const styles = StyleSheet.create({
  2.   text: {
  3.     color: PlatformColor('label'), // iOS 系统标签色
  4.   },
  5. });
复制代码

PlatformColor 还支持多参数兜底。传入多个颜色名时,第一个匹配的生效,后续的作为备选。这个特性非常适合做跨系统版本兼容。比如 iOS 13 才有 systemBackground,iOS 12 没有,可以用这种写法兜底:
  1. const color = PlatformColor('systemBackground', 'groupTableViewBackground');
复制代码

需要特别强调的是,PlatformColor 必须配合 Platform.select 使用。iOS 和 Android 的系统颜色命名规则完全不同,iOS 用 label、systemBlue 这种 camelCase,Android 用 ?android:attr/xxx 和 @android:color/xxx 格式。如果不做平台区分,在 iOS 上写 Android 颜色名会直接报运行时错误,反过来在 Android 上写 iOS 颜色名可能崩溃。
  1. // 正确写法:配合 Platform.select
  2. const textColor = Platform.select({
  3.   ios: PlatformColor('label'),
  4.   android: PlatformColor('?android:attr/textColorPrimary'),
  5.   harmony: PlatformColor('?android:attr/textColorPrimary'),
  6.   default: '#000000',
  7. });
  8. const bgColor = Platform.select({
  9.   ios: PlatformColor('systemBackground'),
  10.   android: PlatformColor('@android:color/background_light'),
  11.   harmony: PlatformColor('@android:color/background_light'),
  12.   default: '#FFFFFF',
  13. });
复制代码

iOS 上的系统色分标准色和 UI 元素色两类。标准色包含 systemRed、systemGreen、systemBlue、systemOrange、systemYellow、systemPurple、systemTeal、systemGray 等,systemPink 需要 iOS 15+。UI 元素色则包括 label 系列(主要/次要/三级/四级文本)、systemBackground 系列(主背景/次级背景)、分组背景、分隔线 separatorColor、链接色 linkColor、占位文本 placeholderText 等。这些颜色在浅色/深色模式下自动切换,比如 label 在浅色模式是黑色,深色模式自动变白色。

Android 的系统色分两种格式。主题属性(?android:attr/)会跟随主题变化,比如 ?android:attr/textColorPrimary、?android:attr/colorAccent、?android:attr/colorBackground、?android:attr/windowBackground。资源颜色(@android:color/)是固定的,比如 @android:color/black 永远是黑色,@android:color/holo_blue_bright 是固定的亮蓝色。需要注意,?android:attr 系列依赖当前主题:同一个 colorAccent 在 Material Design 主题下是粉色,在 Holo 主题下是蓝色。如果 App 设置了自定义主题,PlatformColor 返回的颜色会跟着变。

在鸿蒙上使用 PlatformColor,有几个实际问题需要验证。

第一个坑是 RNOH 版本对 PlatformColor 的支持程度。RNOH 0.84 以上的版本支持部分 Android 兼容的系统色,但可能不是全部。作者在鸿蒙设备上跑 PlatformColor('@android:color/holo_blue_bright'),返回的颜色跟预期不一致,查了 RNOH 源码才发现是颜色映射的问题。

第二个坑是颜色实际值可能不同。即使颜色名称存在,鸿蒙上返回的实际颜色值也可能跟 Android 不一样。比如 @android:color/holo_blue_bright 在 Android 上是亮蓝色,在鸿蒙上可能是另一种蓝色。建议在真机上逐个验证所有用到的系统色,并准备好 hex 兜底:
  1. Platform.select({
  2.   ios: PlatformColor('systemBlue'),
  3.   android: PlatformColor('@android:color/holo_blue_bright'),
  4.   harmony: PlatformColor('@android:color/holo_blue_bright'), // 可能颜色值不同
  5.   default: '#0A59F7',
  6. });
复制代码

第三个坑是 ?android:attr 系列在鸿蒙上可能不完整。主题属性颜色依赖系统主题,鸿蒙的主题跟 Android 不完全一样,某些属性可能映射不到。作者遇到过 ?android:attr/colorAccent 在鸿蒙上返回的不是强调色,而是一个奇怪的灰色,排查后发现是鸿蒙主题没定义这个属性。

第四个坑是深色模式自动适配可能不生效。PlatformColor 的核心特性是自动适配深浅色模式,但在鸿蒙上这个自动切换可能失效。这时候需要配合 Appearance API 做兜底:
  1. import { Appearance, Platform, PlatformColor } from 'react-native';
  2. const getThemedBackground = () => {
  3.   const colorScheme = Appearance.getColorScheme();
  4.   if (Platform.OS === 'harmony') {
  5.     return colorScheme === 'dark' ? '#1C1C1E' : '#FFFFFF';
  6.   }
  7.   return PlatformColor(
  8.     Platform.OS === 'ios' ? 'systemBackground' : '@android:color/background_light'
  9.   );
  10. };
复制代码

除了这些坑,使用 PlatformColor 还有一些实战场景可以借鉴。比如做主题感知的自定义卡片组件,背景色和文本色都通过 Platform.select 配合 PlatformColor 获取,切换系统主题后不需要额外的主题管理代码:
  1. const ThemedCard = ({ title, subtitle, children }) => {
  2.   return (
  3.     <View
  4.       style={{
  5.         backgroundColor: Platform.select({
  6.           ios: PlatformColor('secondarySystemBackground'),
  7.           android: PlatformColor('?android:attr/colorBackground'),
  8.           default: '#F3F4F6',
  9.         }),
  10.         borderRadius: 12,
  11.         padding: 16,
  12.       }}
  13.     >
  14.       <Text
  15.         style={{
  16.           fontSize: 18,
  17.           fontWeight: '600',
  18.           color: Platform.select({
  19.             ios: PlatformColor('label'),
  20.             android: PlatformColor('?android:attr/textColorPrimary'),
  21.             default: '#111827',
  22.           }),
  23.         }}
  24.       >
  25.         {title}
  26.       </Text>
  27.       <Text
  28.         style={{
  29.           fontSize: 14,
  30.           marginTop: 4,
  31.           color: Platform.select({
  32.             ios: PlatformColor('secondaryLabel'),
  33.             android: PlatformColor('?android:attr/textColorSecondary'),
  34.             default: '#6B7280',
  35.           }),
  36.         }}
  37.       >
  38.         {subtitle}
  39.       </Text>
  40.       <View style={{ marginTop: 12 }}>{children}</View>
  41.     </View>
  42.   );
  43. };
复制代码

分隔线也可以这样处理,用 PixelRatio 保证 1 物理像素的细线,颜色跟随系统主题:
  1. const Divider = () => {
  2.   return (
  3.     <View
  4.       style={{
  5.         height: 1 / PixelRatio.get(),
  6.         backgroundColor: Platform.select({
  7.           ios: PlatformColor('separatorColor'),
  8.           android: PlatformColor('@android:color/darker_gray'),
  9.           default: '#E5E7EB',
  10.         }),
  11.       }}
  12.     />
  13.   );
  14. };
复制代码

输入框的占位文字颜色也可以系统化处理:
  1. <TextInput
  2.   placeholder="请输入..."
  3.   placeholderTextColor={Platform.select({
  4.     ios: PlatformColor('placeholderText'),
  5.     android: PlatformColor('?android:attr/textColorTertiary'),
  6.     default: '#9CA3AF',
  7.   })}
  8.   style={{
  9.     color: Platform.select({
  10.       ios: PlatformColor('label'),
  11.       android: PlatformColor('?android:attr/textColorPrimary'),
  12.       default: '#000',
  13.     }),
  14.   }}
  15. />
复制代码

按钮颜色建议用系统强调色,让 UI 更贴近系统原生控件:
  1. const ThemedButton = ({ title, onPress }) => {
  2.   return (
  3.     <Pressable
  4.       onPress={onPress}
  5.       style={({ pressed }) => ({
  6.         backgroundColor: Platform.select({
  7.           ios: PlatformColor('systemBlue'),
  8.           android: PlatformColor('?android:attr/colorAccent'),
  9.           default: '#0A59F7',
  10.         }),
  11.         opacity: pressed ? 0.7 : 1,
  12.         borderRadius: 8,
  13.         paddingVertical: 12,
  14.         paddingHorizontal: 24,
  15.         alignItems: 'center',
  16.       })}
  17.     >
  18.       <Text style={{ color: '#FFFFFF', fontWeight: '600', fontSize: 16 }}>
  19.         {title}
  20.       </Text>
  21.     </Pressable>
  22.   );
  23. };
复制代码

列表项也是典型的场景,按压态和普通态用不同的系统背景色,分隔线跟随系统:
  1. const ThemedListItem = ({ title, subtitle, onPress }) => {
  2.   return (
  3.     <Pressable
  4.       onPress={onPress}
  5.       style={({ pressed }) => ({
  6.         backgroundColor: pressed
  7.           ? Platform.select({
  8.               ios: PlatformColor('tertiarySystemBackground'),
  9.               android: PlatformColor('@android:color/background_light'),
  10.               default: '#F3F4F6',
  11.             })
  12.           : Platform.select({
  13.               ios: PlatformColor('systemBackground'),
  14.               android: PlatformColor('@android:color/background_light'),
  15.               default: '#FFFFFF',
  16.             }),
  17.         paddingHorizontal: 16,
  18.         paddingVertical: 12,
  19.         borderBottomWidth: 1 / PixelRatio.get(),
  20.         borderBottomColor: Platform.select({
  21.           ios: PlatformColor('separatorColor'),
  22.           android: PlatformColor('@android:color/darker_gray'),
  23.           default: '#E5E7EB',
  24.         }),
  25.       })}
  26.     >
  27.       <Text
  28.         style={{
  29.           fontSize: 16,
  30.           color: Platform.select({
  31.             ios: PlatformColor('label'),
  32.             android: PlatformColor('?android:attr/textColorPrimary'),
  33.             default: '#111827',
  34.           }),
  35.         }}
  36.       >
  37.         {title}
  38.       </Text>
  39.       {subtitle && (
  40.         <Text
  41.           style={{
  42.             fontSize: 13,
  43.             marginTop: 2,
  44.             color: Platform.select({
  45.               ios: PlatformColor('secondaryLabel'),
  46.               android: PlatformColor('?android:attr/textColorSecondary'),
  47.               default: '#6B7280',
  48.             }),
  49.           }}
  50.         >
  51.           {subtitle}
  52.         </Text>
  53.       )}
  54.     </Pressable>
  55.   );
  56. };
复制代码

总结一下,PlatformColor 最适合的场景是"这颜色应该跟系统保持一致",比如导航栏、列表、分割线、背景、文本颜色这些跟系统控件接近的 UI 元素。用 PlatformColor 能自动适配浅色/深色模式,比手动维护一套颜色变量省心得多。但如果项目需要高度定制的品牌色,直接写 hex 值更合适。

给鸿蒙开发者的几个实用建议:

一、能用 PlatformColor 就用,系统色优先于硬编码,自动适配主题。

二、Platform.select 的 default 键务必写上 hex 值,万一平台不支持也不至于崩。

三、鸿蒙上必须在真机上验证每个用到的系统色。不同 RNOH 版本支持度不同,颜色映射结果也可能有差异。

四、不要只依赖 PlatformColor 做深色模式,鸿蒙上还是需要配合 Appearance API 做兜底。

五、多参数兜底时,第一个参数写精确的颜色名,后面写兼容备选。

六、注意颜色命名风格:iOS 用 camelCase(systemBlue),Android 有些是 snake_case(holo_blue_bright),拼写错误会直接出问题。

如果在鸿蒙上遇到 PlatformColor 相关的问题,先确认 RNOH 版本,再去查源码里的颜色映射表,最后在真机上实测验证。鸿蒙 RN 开发还在快速迭代中,很多问题可能已经有新的解决方案,建议以实际测试结果为准。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-1 12:33 , Processed in 0.025725 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部