RN for OpenHarmony 实战 TodoList 项目:任务卡片阴影效果

案例开源地址:https://atomgit.com/lqjmac/rn_openharmony_todolist

扁平还是立体

设计界有过一场"扁平化 vs 拟物化"的争论。扁平化追求简洁,去掉阴影、渐变、纹理。拟物化追求真实,模拟现实世界的光影效果。

现在的主流是"半扁平"------整体简洁,但在需要的地方加一点阴影来表示层次。卡片浮在背景上方,按钮浮在卡片上方,阴影告诉用户"这个元素在上面"。

我们的任务卡片就有阴影。不是很重的阴影,只是淡淡的一层,让卡片看起来有一点点"浮起来"的感觉。这种微妙的立体感能让界面更有质感。


任务卡片的阴影代码

tsx 复制代码
<Animated.View style={[styles.taskCard, {
  backgroundColor: theme.card, 
  borderColor: theme.border, 
  opacity: itemAnim,
  transform: [{translateX: itemAnim.interpolate({inputRange: [0, 1], outputRange: [-50, 0]})}],
  shadowColor: '#000', 
  shadowOffset: {width: 0, height: 2}, 
  shadowOpacity: darkMode ? 0.3 : 0.1, 
  shadowRadius: 4, 
  elevation: 3
}]}>

阴影相关的属性有五个:shadowColorshadowOffsetshadowOpacityshadowRadiuselevation。前四个是 iOS 的,最后一个是 Android 的。


iOS 的阴影属性

iOS 用四个属性控制阴影:

shadowColor

tsx 复制代码
shadowColor: '#000'

阴影的颜色。大多数情况下用黑色 #000,因为现实世界的阴影就是黑色的。

也可以用彩色阴影。比如我们的 FAB 用紫色阴影 shadowColor: '#6c5ce7',让阴影带一点品牌色,更有设计感。

shadowOffset

tsx 复制代码
shadowOffset: {width: 0, height: 2}

阴影的偏移量。width 是水平偏移,height 是垂直偏移。

{width: 0, height: 2} 表示阴影向下偏移 2 像素,水平不偏移。这模拟了光从正上方照射的效果,阴影在元素下方。

如果设置 {width: 2, height: 2},阴影会向右下方偏移,模拟光从左上方照射。

大多数情况下,width 设为 0,只调整 height。因为我们习惯光从上方来,阴影在下方。

shadowOpacity

tsx 复制代码
shadowOpacity: darkMode ? 0.3 : 0.1

阴影的透明度,0 到 1 之间。0 是完全透明(看不见),1 是完全不透明(纯黑)。

我们根据主题调整透明度。深色模式下用 0.3,阴影更明显。浅色模式下用 0.1,阴影更淡。

为什么深色模式阴影要更明显?因为深色背景本身就暗,淡阴影会看不见。浅色背景上,淡阴影就够了,太重会显得脏。

shadowRadius

tsx 复制代码
shadowRadius: 4

阴影的模糊半径。数值越大,阴影越模糊、越扩散。数值越小,阴影越清晰、越集中。

shadowRadius: 4 是一个适中的值,阴影有一定的模糊但不会太散。

如果 shadowRadius: 0,阴影会是一个清晰的边缘,像元素的复制品。这种效果很少用。


Android 的阴影属性

Android 不支持 iOS 的 shadow* 属性,用 elevation 代替:

tsx 复制代码
elevation: 3

elevation 是"海拔高度"的意思。数值越大,元素越"高",阴影越明显。

Material Design 定义了不同层级的 elevation

  • 0:在背景上,没有阴影
  • 1-2:轻微浮起,如卡片
  • 3-4:中等浮起,如按钮
  • 6-8:明显浮起,如 FAB、弹窗
  • 12-24:很高,如对话框、抽屉

我们的任务卡片用 elevation: 3,是轻微到中等的浮起效果。

elevation 的局限

elevation 只能控制阴影的"强度",不能控制颜色、偏移、模糊等细节。Android 的阴影是系统自动计算的,基于 Material Design 的规范。

这意味着 iOS 和 Android 的阴影效果可能不完全一致。iOS 可以精细控制,Android 只能大致控制。对于大多数应用来说,这种差异可以接受。


跨平台阴影的写法

为了在两个平台都有阴影,我们同时写 iOS 和 Android 的属性:

tsx 复制代码
{
  // iOS
  shadowColor: '#000',
  shadowOffset: {width: 0, height: 2},
  shadowOpacity: 0.1,
  shadowRadius: 4,
  // Android
  elevation: 3,
}

iOS 会忽略 elevation,Android 会忽略 shadow*。两个平台各取所需。

Platform 判断

如果想针对不同平台设置不同的值,可以用 Platform

tsx 复制代码
import {Platform} from 'react-native';

const shadow = Platform.select({
  ios: {
    shadowColor: '#000',
    shadowOffset: {width: 0, height: 2},
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  android: {
    elevation: 3,
  },
});

不过对于阴影来说,同时写两套属性更简单,不需要 Platform.select


阴影和圆角

任务卡片有圆角:

tsx 复制代码
taskCard: {
  ...
  borderRadius: 12,
  overflow: 'hidden',
}

在 iOS 上,阴影会自动适应圆角。圆角的卡片,阴影也是圆角的。

但如果设置了 overflow: 'hidden',阴影可能会被裁掉。因为阴影在元素外面,overflow: 'hidden' 会隐藏元素外面的内容。

解决方案是把阴影和内容分开:

tsx 复制代码
// 外层负责阴影
<View style={{shadowColor: '#000', shadowOffset: {width: 0, height: 2}, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3}}>
  // 内层负责圆角和裁剪
  <View style={{borderRadius: 12, overflow: 'hidden'}}>
    {/* 内容 */}
  </View>
</View>

我们的任务卡片没有这个问题,因为内容没有超出卡片边界,不需要 overflow: 'hidden'


阴影的性能

阴影会影响渲染性能吗?

在 iOS 上,shadow* 属性会触发离屏渲染,有一定的性能开销。如果列表里有很多带阴影的卡片,滚动时可能会卡顿。

优化方法:

shouldRasterizeIOS

tsx 复制代码
<View style={shadowStyle} shouldRasterizeIOS={true}>

shouldRasterizeIOS 会把元素光栅化成位图缓存,避免每帧都重新计算阴影。但如果元素内容经常变化,光栅化反而会更慢。

减少阴影元素

不是每个元素都需要阴影。只给重要的元素加阴影,减少阴影元素的数量。

简化阴影

减小 shadowRadius,阴影计算会更快。或者干脆用边框代替阴影,边框没有性能问题。

我们的任务列表通常只有几十个卡片,阴影的性能影响可以忽略。如果列表有几百上千个卡片,可能需要考虑优化。


阴影的层次感

阴影不只是装饰,它传达了层次信息。

背景层

背景没有阴影,是最底层。

卡片层

任务卡片有轻微阴影 elevation: 3,浮在背景上方。

按钮层

FAB 有明显阴影 elevation: 8,浮在卡片上方。

弹窗层

Modal 弹窗有最强的阴影(如果有的话),浮在所有内容上方。

这种层次关系让用户直觉地理解界面结构。阴影越重的元素越"近",越容易被注意到。


深色模式下的阴影

深色模式下,阴影需要调整:

tsx 复制代码
shadowOpacity: darkMode ? 0.3 : 0.1

深色背景上,淡阴影几乎看不见。需要增加透明度让阴影更明显。

但也不能太重。深色模式的目的是减少亮度,太重的阴影会让界面显得"脏"。0.3 是一个平衡点。

有些设计师认为深色模式不需要阴影,用边框或颜色差异来表示层次。这也是一种选择。我们保留了阴影,但调整了透明度。


彩色阴影

FAB 用了彩色阴影:

tsx 复制代码
fab: {
  ...
  shadowColor: '#6c5ce7',
  shadowOffset: {width: 0, height: 4},
  shadowOpacity: 0.3,
  shadowRadius: 8,
  elevation: 8,
}

shadowColor: '#6c5ce7' 是紫色,和 FAB 的背景色一致。

彩色阴影让 FAB 看起来像是在"发光",更有存在感。这是一种设计技巧,用于强调重要的元素。

注意彩色阴影只在 iOS 上有效。Android 的 elevation 阴影永远是灰色的。


内阴影

CSS 有 box-shadow: inset 可以做内阴影,但 React Native 不支持内阴影。

如果需要内阴影效果,可以用渐变或者图片模拟:

tsx 复制代码
// 用 LinearGradient 模拟顶部内阴影
<LinearGradient
  colors={['rgba(0,0,0,0.1)', 'transparent']}
  style={{position: 'absolute', top: 0, left: 0, right: 0, height: 10}}
/>

这种方式比较 hack,效果也有限。大多数情况下,不用内阴影也能做出好看的界面。


阴影的动画

阴影可以动画吗?

iOS 的 shadow* 属性不能用 useNativeDriver: true,动画性能较差。

Android 的 elevation 可以动画,但效果可能不明显。

如果想做"按下时阴影变小"的效果:

tsx 复制代码
const elevationAnim = useRef(new Animated.Value(3)).current;

const onPressIn = () => {
  Animated.timing(elevationAnim, {toValue: 1, duration: 100, useNativeDriver: false}).start();
};

const onPressOut = () => {
  Animated.timing(elevationAnim, {toValue: 3, duration: 100, useNativeDriver: false}).start();
};

<Animated.View style={{elevation: elevationAnim, ...}}>

按下时 elevation 从 3 变成 1,阴影变小,看起来像是按钮被按下去了。松开时恢复。

这种动画在 Android 上效果不错,iOS 上需要同时动画 shadowOpacityshadowRadius


不用阴影的替代方案

如果不想用阴影,有其他方式表示层次:

边框

tsx 复制代码
borderWidth: 1,
borderColor: theme.border,

边框能清晰地划分元素边界,但没有立体感。

背景色差异

卡片背景色比页面背景色浅一点(浅色模式)或深一点(深色模式),形成对比。

我们的设计同时用了边框和阴影。边框在两个平台上效果一致,阴影增加立体感。


小结

阴影让卡片有"浮起来"的感觉,增加界面的层次感。iOS 用 shadowColorshadowOffsetshadowOpacityshadowRadius 四个属性,Android 用 elevation。深色模式下阴影透明度要调高一些。彩色阴影可以强调重要元素,但只在 iOS 上有效。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
Allen_LVyingbo9 小时前
病历生成与质控编码的工程化范式研究:从模型驱动到系统治理的范式转变
前端·javascript·算法·前端框架·知识图谱·健康医疗·easyui
探索宇宙真理.10 小时前
WordPress FS注册密码漏洞 | CVE-2025-15001 复现&研究
经验分享·开源·wordpress·安全漏洞
行者9610 小时前
OpenHarmony Flutter 搜索体验优化实战:打造高性能跨平台搜索组件
flutter·harmonyos·鸿蒙
刘一说10 小时前
腾讯位置服务JavaScript API GL与JavaScript API (V2)全面对比总结
开发语言·javascript·信息可视化·webgis
Aotman_11 小时前
JS 按照数组顺序对对象进行排序
开发语言·前端·javascript·vue.js·ui·ecmascript
AlbertZein18 小时前
HarmonyOS一杯冰美式的时间 -- FullScreenLaunchComponent
harmonyos
Hi_kenyon18 小时前
VUE3套用组件库快速开发(以Element Plus为例)二
开发语言·前端·javascript·vue.js
EndingCoder19 小时前
Any、Unknown 和 Void:特殊类型的用法
前端·javascript·typescript
JosieBook20 小时前
【Vue】09 Vue技术——JavaScript 数据代理的实现与应用
前端·javascript·vue.js