在鸿蒙设备上做 React Native(RN)开发,深浅色模式适配是个绕不开的话题。很多开发者习惯在代码里写死 #FFFFFF 或 #000000,但这样在系统切换深色模式时,自定义 View 的颜色不会跟着变化,看起来会非常突兀。实际上,React Native 提供的 PlatformColor 可以直接引用系统原生色值,让颜色自动跟随系统主题切换,省掉一堆手动判断逻辑。不过,在鸿蒙上使用 PlatformColor 有几个坑需要提前知道。
PlatformColor 的用法很直接:传入系统颜色的字符串名称,返回对应平台的原生颜色值,然后可以在 StyleSheet 里直接使用。
- import { PlatformColor } from 'react-native';
- // iOS 系统色
- const labelColor = PlatformColor('label');
- const systemBlue = PlatformColor('systemBlue');
- // Android 系统色
- const primaryText = PlatformColor('?android:attr/textColorPrimary');
- const holoBlue = PlatformColor('@android:color/holo_blue_bright');
复制代码
在 StyleSheet 中直接使用返回的颜色即可:
- const styles = StyleSheet.create({
- text: {
- color: PlatformColor('label'), // iOS 系统标签色
- },
- });
复制代码
PlatformColor 还支持多参数兜底。传入多个颜色名时,第一个匹配的生效,后续的作为备选。这个特性非常适合做跨系统版本兼容。比如 iOS 13 才有 systemBackground,iOS 12 没有,可以用这种写法兜底:
- 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 颜色名可能崩溃。
- // 正确写法:配合 Platform.select
- const textColor = Platform.select({
- ios: PlatformColor('label'),
- android: PlatformColor('?android:attr/textColorPrimary'),
- harmony: PlatformColor('?android:attr/textColorPrimary'),
- default: '#000000',
- });
- const bgColor = Platform.select({
- ios: PlatformColor('systemBackground'),
- android: PlatformColor('@android:color/background_light'),
- harmony: PlatformColor('@android:color/background_light'),
- default: '#FFFFFF',
- });
复制代码
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 兜底:
- Platform.select({
- ios: PlatformColor('systemBlue'),
- android: PlatformColor('@android:color/holo_blue_bright'),
- harmony: PlatformColor('@android:color/holo_blue_bright'), // 可能颜色值不同
- default: '#0A59F7',
- });
复制代码
第三个坑是 ?android:attr 系列在鸿蒙上可能不完整。主题属性颜色依赖系统主题,鸿蒙的主题跟 Android 不完全一样,某些属性可能映射不到。作者遇到过 ?android:attr/colorAccent 在鸿蒙上返回的不是强调色,而是一个奇怪的灰色,排查后发现是鸿蒙主题没定义这个属性。
第四个坑是深色模式自动适配可能不生效。PlatformColor 的核心特性是自动适配深浅色模式,但在鸿蒙上这个自动切换可能失效。这时候需要配合 Appearance API 做兜底:
- import { Appearance, Platform, PlatformColor } from 'react-native';
- const getThemedBackground = () => {
- const colorScheme = Appearance.getColorScheme();
- if (Platform.OS === 'harmony') {
- return colorScheme === 'dark' ? '#1C1C1E' : '#FFFFFF';
- }
- return PlatformColor(
- Platform.OS === 'ios' ? 'systemBackground' : '@android:color/background_light'
- );
- };
复制代码
除了这些坑,使用 PlatformColor 还有一些实战场景可以借鉴。比如做主题感知的自定义卡片组件,背景色和文本色都通过 Platform.select 配合 PlatformColor 获取,切换系统主题后不需要额外的主题管理代码:
- const ThemedCard = ({ title, subtitle, children }) => {
- return (
- <View
- style={{
- backgroundColor: Platform.select({
- ios: PlatformColor('secondarySystemBackground'),
- android: PlatformColor('?android:attr/colorBackground'),
- default: '#F3F4F6',
- }),
- borderRadius: 12,
- padding: 16,
- }}
- >
- <Text
- style={{
- fontSize: 18,
- fontWeight: '600',
- color: Platform.select({
- ios: PlatformColor('label'),
- android: PlatformColor('?android:attr/textColorPrimary'),
- default: '#111827',
- }),
- }}
- >
- {title}
- </Text>
- <Text
- style={{
- fontSize: 14,
- marginTop: 4,
- color: Platform.select({
- ios: PlatformColor('secondaryLabel'),
- android: PlatformColor('?android:attr/textColorSecondary'),
- default: '#6B7280',
- }),
- }}
- >
- {subtitle}
- </Text>
- <View style={{ marginTop: 12 }}>{children}</View>
- </View>
- );
- };
复制代码
分隔线也可以这样处理,用 PixelRatio 保证 1 物理像素的细线,颜色跟随系统主题:
- const Divider = () => {
- return (
- <View
- style={{
- height: 1 / PixelRatio.get(),
- backgroundColor: Platform.select({
- ios: PlatformColor('separatorColor'),
- android: PlatformColor('@android:color/darker_gray'),
- default: '#E5E7EB',
- }),
- }}
- />
- );
- };
复制代码
输入框的占位文字颜色也可以系统化处理:
- <TextInput
- placeholder="请输入..."
- placeholderTextColor={Platform.select({
- ios: PlatformColor('placeholderText'),
- android: PlatformColor('?android:attr/textColorTertiary'),
- default: '#9CA3AF',
- })}
- style={{
- color: Platform.select({
- ios: PlatformColor('label'),
- android: PlatformColor('?android:attr/textColorPrimary'),
- default: '#000',
- }),
- }}
- />
复制代码
按钮颜色建议用系统强调色,让 UI 更贴近系统原生控件:
- const ThemedButton = ({ title, onPress }) => {
- return (
- <Pressable
- onPress={onPress}
- style={({ pressed }) => ({
- backgroundColor: Platform.select({
- ios: PlatformColor('systemBlue'),
- android: PlatformColor('?android:attr/colorAccent'),
- default: '#0A59F7',
- }),
- opacity: pressed ? 0.7 : 1,
- borderRadius: 8,
- paddingVertical: 12,
- paddingHorizontal: 24,
- alignItems: 'center',
- })}
- >
- <Text style={{ color: '#FFFFFF', fontWeight: '600', fontSize: 16 }}>
- {title}
- </Text>
- </Pressable>
- );
- };
复制代码
列表项也是典型的场景,按压态和普通态用不同的系统背景色,分隔线跟随系统:
- const ThemedListItem = ({ title, subtitle, onPress }) => {
- return (
- <Pressable
- onPress={onPress}
- style={({ pressed }) => ({
- backgroundColor: pressed
- ? Platform.select({
- ios: PlatformColor('tertiarySystemBackground'),
- android: PlatformColor('@android:color/background_light'),
- default: '#F3F4F6',
- })
- : Platform.select({
- ios: PlatformColor('systemBackground'),
- android: PlatformColor('@android:color/background_light'),
- default: '#FFFFFF',
- }),
- paddingHorizontal: 16,
- paddingVertical: 12,
- borderBottomWidth: 1 / PixelRatio.get(),
- borderBottomColor: Platform.select({
- ios: PlatformColor('separatorColor'),
- android: PlatformColor('@android:color/darker_gray'),
- default: '#E5E7EB',
- }),
- })}
- >
- <Text
- style={{
- fontSize: 16,
- color: Platform.select({
- ios: PlatformColor('label'),
- android: PlatformColor('?android:attr/textColorPrimary'),
- default: '#111827',
- }),
- }}
- >
- {title}
- </Text>
- {subtitle && (
- <Text
- style={{
- fontSize: 13,
- marginTop: 2,
- color: Platform.select({
- ios: PlatformColor('secondaryLabel'),
- android: PlatformColor('?android:attr/textColorSecondary'),
- default: '#6B7280',
- }),
- }}
- >
- {subtitle}
- </Text>
- )}
- </Pressable>
- );
- };
复制代码
总结一下,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 开发还在快速迭代中,很多问题可能已经有新的解决方案,建议以实际测试结果为准。 |