在React Native(RN)跨平台开发里,阴影效果通常靠Shadow Props那套属性来实现。文档上写得很简单,无非就是shadowColor、shadowOffset、shadowOpacity、shadowRadius四个属性,看上去几分钟就能搞定。但换到鸿蒙设备上跑一遍,就会发现事情没那么简单——这四个属性在鸿蒙上完全不生效,因为它们本质上是iOS专有属性,并没有被RNOH(React Native on OpenHarmony)兼容层映射到鸿蒙的渲染引擎上。
一、问题复现:Shadow Props在鸿蒙上集体失效
先看一个典型的iOS阴影写法:
- <View
- style={{
- width: 100,
- height: 100,
- backgroundColor: '#fff',
- shadowColor: '#000000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.2,
- shadowRadius: 4,
- }}
- >
- <Text>带阴影的视图</Text>
- </View>
复制代码
这段代码在iOS上表现正常,但到了鸿蒙上,阴影直接消失,界面上只剩一个纯色View。逐个验证四个属性,结果一致:
- shadowColor:设置阴影颜色,鸿蒙上无效。
- shadowOffset:控制阴影偏移方向,鸿蒙上无效。
- shadowOpacity:控制阴影透明度,鸿蒙上无效。
- shadowRadius:控制阴影模糊半径,鸿蒙上无效。
也就是说,只要用了Shadow Props,在鸿蒙上就约等于没写阴影。这个坑在联调时很容易让人误以为是样式层级或颜色值的问题,排查半天才发现是平台兼容性问题。
二、最直接的替换:用elevation属性
elevation是Android和鸿蒙平台共有的属性,在RN中可以直接使用。它通过一个数值来控制阴影的“高度感”,数值越大,阴影越深、越明显。
- <View
- style={{
- width: 100,
- height: 100,
- backgroundColor: '#fff',
- elevation: 4, // 阴影等级,数值越大阴影越深
- }}
- >
- <Text>鸿蒙阴影效果</Text>
- </View>
复制代码
需要注意的是,elevation在鸿蒙上的渲染表现和Android并不完全一致。实测下来,相同数值下鸿蒙的阴影颜色、阴影形状以及渲染性能都可能存在细微差异。因此如果项目需要同时支持Android和鸿蒙,建议在真机上分别做一轮视觉走查,而不是只依赖Android上的效果图。
三、跨平台写法:Platform.select按平台分发
如果项目要同时覆盖iOS、Android和鸿蒙,最稳妥的方案是用Platform.select区分平台,分别指定阴影样式:
- import { Platform } from 'react-native';
- const shadowStyle = Platform.select({
- ios: {
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.2,
- shadowRadius: 4,
- },
- android: {
- elevation: 4,
- },
- harmony: {
- elevation: 4, // 鸿蒙使用与Android相同的属性
- },
- });
复制代码
在此基础上,可以封装一个跨平台阴影组件,避免业务代码里到处写条件判断:
- import React from 'react';
- import { View, StyleSheet, Platform } from 'react-native';
- type ShadowViewProps = {
- children: React.ReactNode;
- elevationLevel?: number;
- shadowColor?: string;
- shadowOffset?: { width: number; height: number };
- shadowOpacity?: number;
- shadowRadius?: number;
- style?: any;
- };
- export function ShadowView({
- children,
- elevationLevel = 4,
- shadowColor = '#000',
- shadowOffset = { width: 0, height: 2 },
- shadowOpacity = 0.2,
- shadowRadius = 4,
- style
- }: ShadowViewProps) {
- const platformShadowStyle = Platform.select({
- ios: { shadowColor, shadowOffset, shadowOpacity, shadowRadius },
- android: { elevation: elevationLevel },
- harmony: { elevation: elevationLevel },
- });
- return <View style={[styles.container, platformShadowStyle, style]}>{children}</View>;
- }
- const styles = StyleSheet.create({
- container: {
- backgroundColor: '#fff',
- borderRadius: 8,
- },
- });
复制代码
这样上层组件不用关心运行在哪个平台,阴影表现交给ShadowView统一处理。
四、其他可选的阴影实现方式
除了elevation,实际开发中还有一些备选方案,各有适用场景:
1. 边框模拟阴影:给View加一个半透明边框,视觉上起到阴影作用,适合轻量卡片场景。
- <View
- style={{
- width: 100,
- height: 100,
- backgroundColor: '#fff',
- borderWidth: 1,
- borderColor: 'rgba(0,0,0,0.1)',
- }}
- />
复制代码
2. View嵌套:底层放一个偏移的深色半透明圆角View,上层放内容层,通过绝对定位模拟出阴影偏移。
- <View style={{ position: 'relative' }}>
- {/* 阴影层 */}
- <View
- style={{
- position: 'absolute',
- top: 2,
- left: 2,
- width: 100,
- height: 100,
- backgroundColor: 'rgba(0,0,0,0.1)',
- borderRadius: 8,
- }}
- />
- {/* 内容层 */}
- <View
- style={{
- width: 100,
- height: 100,
- backgroundColor: '#fff',
- borderRadius: 8,
- position: 'relative',
- zIndex: 1,
- }}
- >
- <Text>带阴影的内容</Text>
- </View>
- </View>
复制代码
3. 第三方阴影库:比如react-native-shadow-2,基于Shadow组件封装,可配置distance、startColor、endColor等参数,适合需要更精细阴影控制的场景。
- import { Shadow } from 'react-native-shadow-2';
- <Shadow distance={4} startColor={'#00000020'} endColor={'#00000010'}>
- <View style={{ padding: 20 }}>
- <Text>带阴影的内容</Text>
- </View>
- </Shadow>
复制代码
4. SVG滤镜:用react-native-svg的FeDropShadow绘制阴影,适合特殊形状的阴影需求,但实现相对复杂,性能上也要额外评估。
5. 原生模块:通过自封装原生方法创建阴影,灵活性最高,但维护成本也最高,一般项目不太需要走到这一步。
总的来看,elevation属性是鸿蒙上最直接的实现方式;如果只做鸿蒙单平台,甚至可以直接用ArkTS原生的.shadow()方法,功能与iOS的Shadow Props相当。
五、鸿蒙原生ArkTS的阴影写法
如果项目不是RN而是原生鸿蒙应用,ArkTS的阴影代码会更简洁:
- Column() {
- Text('带阴影的文本')
- .shadow({
- radius: 4,
- color: Color.Black,
- offsetX: 2,
- offsetY: 2
- })
- }
复制代码
原生shadow属性与iOS的shadowColor、shadowOffset等属性在功能上是对应的。RN的跨平台阴影系统优势在于一套代码多端复用;但如果业务只服务鸿蒙用户,直接用原生方案反而更省事。
六、性能优化建议
在RN + 鸿蒙项目里使用阴影,性能同样不能忽视,尤其是列表场景。
1. 避免在大列表的每个列表项里都加阴影。FlatList的renderItem里如果每个Item都套一层ShadowView,滚动时会产生大量阴影计算,明显影响帧率。建议只在必要的地方加阴影,比如卡片头部或操作按钮,其余列表项用边框或纯色背景代替。
2. elevation数值不要一味调大。elevation设到20会让阴影非常浓重,既不好看也增加渲染负担。卡片类阴影一般用2到4,按钮用1到2,弹窗需要明显层级感时用8到12。
3. 条件渲染阴影。根据业务状态动态决定是否启用阴影,比如卡片在选中或按压时才增加阴影,避免静态状态下所有元素都带阴影。
- const Card = ({ showShadow }) => {
- const shadowStyle = showShadow ? { elevation: 4 } : {};
- return (
- <View style={[styles.card, shadowStyle]}>
- <Text>卡片内容</Text>
- </View>
- );
- };
复制代码
4. 多平台实测。不同平台对阴影的渲染差异可能导致某个阴影效果在鸿蒙上过重或过淡,建议把iOS、Android、鸿蒙三端的截图放到一起对比,按平台微调elevation数值。
七、踩坑总结
这次在鸿蒙上实践Shadow Props,最深的感受是:React Native的样式属性不能默认认为所有平台都通用。尤其是iOS专有属性,在鸿蒙上失效后不会有任何报错,只会静默地不渲染,排查成本很高。
建议后来者记住这几点:
- Shadow Props(shadowColor/shadowOffset/shadowOpacity/shadowRadius)是iOS专有,鸿蒙上不生效。
- 鸿蒙上优先使用elevation,数值含义与Android一致,但渲染细节需要单独验证。
- 跨平台项目用Platform.select统一分发阴影样式,封装成公共组件。
- 避免在滚动列表中大量使用阴影,控制elevation数值,必要时用条件渲染动态开关。
本文代码基于React Native 0.84 + RNOH 0.84.1编写,鸿蒙设备为HarmonyOS 6.0。RNOH迭代很快,不同版本的兼容表现可能会有变化,建议以真机实测为准。 |