请添加图片描述

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony

好的界面设计,往往藏在细节里。同样是一个卡片,有的看起来就是贴在背景上的,有的看起来像是浮在上面的。区别在哪?

答案是:边框、圆角、还有那一点点若有若无的层次感。

我们的卡片长什么样

先看 ArticleCard 的样式:

const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    borderWidth: 1,
    marginBottom: 12,
    overflow: 'hidden'
  },
});

四个属性,四个作用

borderRadius: 12 设置圆角。12 像素是一个比较柔和的圆角,不会太方正也不会太圆润。现代 UI 设计普遍使用圆角,因为圆角看起来更友好、更现代。

borderWidth: 1 设置边框宽度。1 像素的边框非常细,在高清屏上几乎是一条线。它的作用不是装饰,而是在深色背景上勾勒出卡片的边界。

marginBottom: 12 设置卡片之间的间距。12 像素让卡片之间有呼吸感,不会挤在一起。

overflow: 'hidden' 隐藏超出边界的内容。这个属性配合 borderRadius 使用,让卡片内的图片也有圆角效果。

边框颜色的动态设置

<TouchableOpacity
  style={[styles.card, {backgroundColor: theme.card, borderColor: theme.border}]}
  onPress={() => openLink(item.link)}
  activeOpacity={0.7}
>

为什么边框颜色要动态设置

borderColor: theme.border 让边框颜色跟随主题变化。

在深色模式下,theme.border#2a2a4a,一种深紫灰色。这个颜色比卡片背景(#1a1a2e)稍浅,能看到边界但不会太突兀。

在浅色模式下,theme.border#e0e0e0,一种浅灰色。比卡片背景(白色)稍深,同样能勾勒边界。

如果用固定的边框颜色,比如纯黑或纯白,在某一种主题下会很突兀。动态颜色让边框在两种主题下都自然。

backgroundColor 的作用

backgroundColor: theme.card 设置卡片的背景色。深色模式是深蓝灰色,浅色模式是白色。

卡片背景色和页面背景色不同,这种色差本身就能产生层次感。卡片"浮"在页面上的感觉,很大程度来自这个色差。

为什么不用阴影

React Native 支持阴影效果,但我们没有用:

// 我们没有用这些属性
shadowColor: '#000',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,  // Android 专用

阴影的问题

第一,跨平台不一致。iOS 用 shadow* 属性,Android 用 elevation。两个平台的阴影效果不一样,很难调到一致。

第二,性能开销。阴影需要额外的渲染计算,尤其是在列表里有很多卡片时,可能影响滚动流畅度。

第三,深色模式下效果不好。阴影是通过在元素下方画一个模糊的深色区域实现的。在深色背景上,深色阴影几乎看不见。

我们的替代方案

用边框代替阴影。1 像素的边框在视觉上能起到类似的作用:勾勒出卡片的边界,让它和背景区分开。

边框的优点是:跨平台一致、性能好、深色浅色模式都能用。

overflow: hidden 的妙用

card: {
  borderRadius: 12,
  overflow: 'hidden'
},
image: {
  width: '100%',
  height: 150,
  resizeMode: 'cover'
},

问题:图片的角是方的

卡片有 12 像素的圆角,但图片是矩形的。如果不做处理,图片的四个角会超出卡片的圆角边界,看起来很奇怪。

解决:overflow: hidden

overflow: 'hidden' 告诉 React Native:超出这个 View 边界的内容,不要显示。

卡片有圆角,图片超出圆角的部分会被裁掉,图片看起来也有了圆角。

这是一个常用技巧:父元素设置圆角和 overflow: 'hidden',子元素自动被裁成圆角。

只对顶部图片生效

我们的卡片结构是:图片在上,内容在下。图片的上两个角需要圆角(和卡片顶部对齐),下两个角不需要(和内容区域连接)。

overflow: 'hidden' 刚好实现了这个效果:图片上两个角被卡片的圆角裁掉,下两个角因为在卡片内部,不会被裁。

卡片内容区域的样式

content: {padding: 12},

为什么只有一个 padding

padding: 12 是简写,等于 paddingTop: 12, paddingRight: 12, paddingBottom: 12, paddingLeft: 12

12 像素的内边距让文字不会贴着卡片边缘,有呼吸感。这个值和 borderRadius: 12 一致,视觉上比较协调。

为什么内容区域没有背景色

内容区域没有单独设置背景色,它会继承卡片的背景色。这样整个卡片是一个统一的颜色,图片区域和内容区域自然过渡。

卡片之间的间距

card: {
  marginBottom: 12,
},
list: {
  paddingBottom: 100,
},

marginBottom 的作用

marginBottom: 12 让每个卡片下方有 12 像素的空白。卡片之间不会紧贴,有间隔才能看出是独立的个体。

为什么是 12 而不是其他值

12 像素是一个"黄金值",不大不小。太小(比如 4 像素)卡片会显得拥挤,太大(比如 24 像素)会浪费空间,一屏能看到的卡片数量减少。

而且 12 和卡片的圆角、内边距一致,整个界面的间距保持统一,看起来更整齐。

paddingBottom: 100 的作用

列表底部留 100 像素的空白,是为了给底部 Tab 栏让位。Tab 栏是悬浮在列表上方的,如果不留空白,最后一个卡片会被 Tab 栏遮挡。

点击反馈

<TouchableOpacity
  style={[styles.card, ...]}
  onPress={() => openLink(item.link)}
  activeOpacity={0.7}
>

activeOpacity 的作用

activeOpacity={0.7} 设置点击时的透明度。用户按下卡片时,卡片会变成 70% 透明度,松开后恢复。

这个视觉反馈告诉用户"我点到了",是交互设计的基本要求。没有反馈的按钮会让用户困惑:我点了吗?点成功了吗?

为什么是 0.7

0.7 是一个比较明显但不夸张的值。太高(比如 0.9)变化不明显,用户可能注意不到。太低(比如 0.3)变化太大,看起来像是闪烁。

默认值是 0.2,我们觉得太透明了,改成 0.7 更自然。

不同页面的卡片样式

我们的 App 有多种卡片:文章卡片、用户信息卡片、设置卡片、收藏卡片、关于卡片。它们的样式略有不同,但遵循统一的设计语言。

文章卡片

card: {borderRadius: 12, borderWidth: 1, marginBottom: 12, overflow: 'hidden'},

有图片,需要 overflow: 'hidden'

用户信息卡片

userCard: {borderRadius: 16, borderWidth: 1, padding: 20, marginBottom: 16, flexDirection: 'row', alignItems: 'center'},

圆角更大(16),内边距更大(20),因为这是页面的主要卡片,需要更突出。

设置卡片

settingCard: {borderRadius: 16, borderWidth: 1, padding: 16, marginBottom: 16},

和用户信息卡片保持一致的圆角,但内边距稍小。

统一的设计语言

所有卡片都用:

  • 圆角(12 或 16)
  • 1 像素边框
  • 动态的背景色和边框色
  • 适当的内边距和外边距

这种统一让整个 App 看起来是一个整体,而不是拼凑的。

完整的卡片样式代码

// ArticleCard 的样式
const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    borderWidth: 1,
    marginBottom: 12,
    overflow: 'hidden'
  },
  image: {
    width: '100%',
    height: 150,
    resizeMode: 'cover'
  },
  content: {
    padding: 12
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    lineHeight: 22,
    marginBottom: 6
  },
  desc: {
    fontSize: 13,
    lineHeight: 18,
    marginBottom: 8
  },
  meta: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8,
    flexWrap: 'wrap',
    gap: 8
  },
  footer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  },
});

// 使用
<TouchableOpacity
  style={[styles.card, {backgroundColor: theme.card, borderColor: theme.border}]}
  onPress={() => openLink(item.link)}
  activeOpacity={0.7}
>
  {showImage && item.envelopePic ? (
    <Image source={{uri: item.envelopePic}} style={styles.image} />
  ) : null}
  <View style={styles.content}>
    {/* 标题、描述、元信息、底部 */}
  </View>
</TouchableOpacity>

设计原则总结

一致性

所有卡片用相似的圆角、边框、间距。用户看到一个卡片,就知道其他卡片大概长什么样。

层次感

通过背景色差异和边框,让卡片和页面背景区分开。不需要阴影也能有"浮起来"的感觉。

呼吸感

适当的内边距和外边距,让内容不拥挤。留白也是设计的一部分。

反馈感

点击有透明度变化,让用户知道操作被响应了。

这些细节单独看都很小,但加在一起,就是"好看"和"一般"的区别。


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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐