Single-page hand-drawn educational infographic in

一个界面"好不好看",往往不是靠复杂的动效或华丽的插图,而是靠无数个视觉细节的叠加。阴影、圆角、颜色层次、文字排版——这些东西每一个单独拿出来都不起眼,组合在一起却能让界面看起来"精致"。

HarmonyOS6 ArkUI 在视觉样式方面提供了相当完整的支持,从 shadow 投影到 borderRadius 圆角,从 TextDecorationType 删除线到 Blank() 弹性间距,几乎所有常见的视觉需求都有对应 API。

Professional hand-drawn sketchnote comparison on p

在 HarmonyOS PC 端开发中,视觉细节的重要性被进一步放大。PC 端屏幕更大、可视内容更多,用户离屏幕的距离更近,细微的视觉瑕疵都会被放大。一个在手机上看起来还不错的界面,放到 PC 端可能就显得粗糙了。

本文以商品卡片为具体案例,把这些视觉细节逐一拆解,聊聊背后的设计思路。

卡片阴影:给界面加一点"深度"

.shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 2 })

Single-page hand-drawn educational infographic in

shadow 接收四个参数:

  • radius:模糊半径,值越大阴影越扩散、越柔和
  • color:阴影颜色,支持带透明度的 ARGB 格式
  • offsetX / offsetY:阴影偏移量,offsetY: 2 表示阴影略微往下偏移

颜色 #1A000000 的含义是:#1A = 十进制 26,对应透明度约 10% 的纯黑。这种极淡的阴影,肉眼感受不到明显的"黑色",只能感受到卡片跟背景之间有一道微弱的分隔——这正是"Material Design"式卡片效果的精髓,若有若无,恰到好处。

阴影参数的设计原则:

场景 radius 透明度 offsetY 适用场景
轻微层次 4-8 10-15% 1-2 卡片、按钮
中等层次 8-16 15-25% 2-4 弹窗、浮层
强烈层次 16-32 25-40% 4-8 对话框、菜单

在 HarmonyOS PC 端,阴影的使用更加讲究——PC 端屏幕更大,用户离屏幕更近,过重的阴影会显得廉价。通常建议使用 10%-15% 透明度的阴影,radius 控制在 8-12 之间。

如果把 color 改成 #66000000(透明度约 40%),阴影就会变得非常重,看起来像是卡片浮得很高,反而不够自然。移动端界面通常选 10%-20% 透明度的阴影,既有层次感,又不会干扰主要内容的阅读。

圆角:让界面"柔和"起来

.borderRadius(14)  // 卡片整体
.borderRadius(10)  // 左侧图片区
.borderRadius(18)  // 右侧按钮
.borderRadius(4)   // 标签

同一个界面里,不同层级、不同功能的元素用了不同大小的圆角:

  • 卡片整体 14vp,大圆角传递"友好、现代"的视觉感受
  • 图片区 10vp,稍小一点,与卡片形成视觉层次
  • 按钮 18vp(宽高都是 36vp,所以实际上是完整的圆形),圆形按钮在小尺寸下看起来更精致
  • 标签 4vp,小圆角表示这是一个"徽章"而非卡片

圆角的设计原则:

元素类型 推荐圆角 视觉感受
大卡片/面板 12-16vp 友好、现代
小卡片/条目 8-12vp 精致、清晰
按钮 4-8vp(矩形)/ 50%(圆形) 可点击、亲和
标签/徽章 2-6vp 紧凑、紧凑
图片 4-10vp 柔和、不刺眼

同一个界面里,圆角大小应该有呼应,不要出现"有些地方是直角、有些地方是大圆角"的割裂感。

在 HarmonyOS PC 端,圆角的设计需要更加谨慎——PC 端界面元素更多、更大,圆角的不一致会更加明显。建议在设计初期就确定一套圆角规范,并在整个应用中统一使用。

颜色语义:颜色不只是装饰

// 现价:红色,紧迫感、促销感
Text(item.price)
  .fontColor('#FF4D4D')

// 原价:灰色删除线,"这是打折前的价格"
Text(item.originalPrice)
  .fontColor('#BBBBBB')
  .decoration({ type: TextDecorationType.LineThrough })

// 标签背景:与标签颜色对应的浅色,视觉上柔和
Text(item.tag)
  .fontColor(item.tagColor)
  .backgroundColor(item.bgColor)

红色的价格是电商界面的行业惯例,传递出"优惠、促销、紧迫"的心理暗示。灰色配删除线的原价,告诉用户"这是划掉的旧价格",字面和视觉双重确认。

标签的配色方案也很讲究:tagColor 是主色(如绿色 #07C160),bgColor 是对应的极浅底色(如 #F0FFF4)。这种"深色文字 + 浅色背景"的标签设计,颜色醒目但不刺眼,常见于各类 App 的状态标签、分类标签。

颜色语义的设计原则:

颜色 语义 适用场景
红色 (#FF4D4D) 危险、促销、重要 价格、错误提示、删除按钮
橙色 (#FF8C00) 警告、推荐 限时优惠、热门推荐
绿色 (#07C160) 成功、安全、新增 成功提示、新增标签、通过状态
蓝色 (#007DFF) 信息、链接、选中 链接、选中态、主按钮
灰色 (#999999) 次要、禁用、辅助 辅助文字、禁用状态、占位符

在 HarmonyOS PC 端,颜色的使用还需要考虑无障碍设计——确保颜色对比度符合 WCAG 2.1 AA 标准(至少 4.5:1),让色弱用户也能正常阅读内容。

Blank():最简单的弹性间距

Row() {
  Text('商品列表')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .fontColor('#1A1A1A')
  Blank()
  Text('查看全部 >')
    .fontSize(14)
    .fontColor('#007DFF')
}
.width('100%')
.padding({ left: 16, right: 16, top: 20, bottom: 12 })

Blank() 在 ArkUI 里是一个特殊的弹性组件,它会自动占满容器内的剩余空间。在这里,"商品列表"靠左,"查看全部 >"靠右,中间的间距由 Blank() 自动撑开。屏幕宽度变了,间距跟着变,永远保持两端对齐。

这比手动设置 margin 或者 padding 要灵活得多——你不需要知道屏幕宽度是多少,只要告诉系统"中间这块儿是弹性空间"就行了。

在 HarmonyOS PC 端Blank() 的作用更加突出——PC 端窗口可以自由调整大小,用户可能把窗口拉得很宽,也可能缩得很窄。用 Blank() 实现的弹性间距,能确保界面在任何窗口大小下都保持正确的布局。

文字层次:字号与字重的搭配

// 商品名:16号,中等字重
Text(item.name)
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .fontColor('#1A1A1A')

// 标签:11号,正常字重
Text(item.tag)
  .fontSize(11)

// 现价:18号,粗体
Text(item.price)
  .fontSize(18)
  .fontWeight(FontWeight.Bold)

// 原价:13号,正常字重,灰色
Text(item.originalPrice)
  .fontSize(13)
  .fontColor('#BBBBBB')

整张卡片里,价格数字字号最大(18vp)、最粗(Bold),是视觉焦点。商品名次之(16vp,Medium),是主要信息。标签和原价字号最小,是辅助信息。

文字层次的设计原则:

信息级别 字号 字重 颜色 适用场景
一级(焦点) 18-24vp Bold 主色/深色 价格、标题
二级(主要) 14-18vp Medium 深灰 商品名、正文
三级(辅助) 11-14vp Regular 中灰 标签、描述
四级(次要) 10-12vp Regular 浅灰 时间、版权信息

这种"字号越大越重要,字色越深越重要"的排版原则,叫做视觉层次。用户扫一眼卡片,视线会自然地按"价格 → 商品名 → 标签 → 原价"的顺序流动,符合电商场景"先看价格"的浏览习惯。

在 HarmonyOS PC 端,文字层次的设计同样适用,但需要注意:PC 端屏幕更大,用户可以看得更远,所以字号可以适当放大 10%-20%。建议一级信息用 20-26vp,二级信息用 16-20vp。

间距系统:让界面"透气"

// 卡片内部间距
.padding(14)

// 卡片之间的间距
Column({ space: 12 })

// 卡片阴影内容的左边距
.margin({ left: 14 })

// 标签内边距
.padding({ left: 6, right: 6, top: 2, bottom: 2 })

注意一个规律:主要间距用 12 或 14,细节间距用 6,标签内边距上下 2 左右 6。这并不是随意取的数字,而是基于 4的倍数 规则(4、8、12、16、20…)的间距体系——绝大多数设计系统都采用这套规则,它能让界面的各处留白看起来整齐、和谐。

间距系统的设计原则:

间距类型 推荐值 适用场景
大间距 20-24vp 模块之间、页面边距
中间距 12-16vp 卡片之间、组之间
小间距 6-8vp 元素之间、行间距
极小间距 2-4vp 图标与文字、标签内边距

在 HarmonyOS PC 端,间距可以适当放大——PC 端屏幕更大,用户离屏幕更远,较大的间距能让界面看起来更舒展、更透气。建议中间距用 16-20vp,大间距用 24-32vp。

完整案例

下面是完整的视觉设计示例代码,可以直接复制到 DevEco Studio 中运行:

/**
 * 视觉设计细节完整示例
 * 演示阴影、圆角、颜色层次在 HarmonyOS PC 端的应用
 * 
 * 文件路径:entry/src/main/ets/components/VisualDesignDemo.ets
 * 运行环境:DevEco Studio 5.0 + HarmonyOS6 SDK
 */

interface GoodsInfo {
  id: number
  name: string
  price: string
  originalPrice: string
  tag: string
  tagColor: string
  bgColor: string
}

@Entry
@Component
struct VisualDesignDemo {
  @State selectedId: number = -1

  private goodsList: GoodsInfo[] = [
    {
      id: 1,
      name: '无线蓝牙耳机',
      price: '¥299',
      originalPrice: '¥599',
      tag: '限时折扣',
      tagColor: '#FF4D4D',
      bgColor: '#FFF5F5'
    },
    {
      id: 2,
      name: '智能运动手表',
      price: '¥899',
      originalPrice: '¥1299',
      tag: '爆款推荐',
      tagColor: '#FF8C00',
      bgColor: '#FFF8F0'
    },
    {
      id: 3,
      name: '便携充电宝',
      price: '¥129',
      originalPrice: '¥199',
      tag: '新品上架',
      tagColor: '#07C160',
      bgColor: '#F0FFF4'
    },
    {
      id: 4,
      name: '机械键盘',
      price: '¥459',
      originalPrice: '¥699',
      tag: '热销榜一',
      tagColor: '#007DFF',
      bgColor: '#F0F7FF'
    }
  ]

  build() {
    Column({ space: 16 }) {
      // 顶部标题栏
      this.HeaderBar()

      // 商品卡片列表
      Column({ space: 12 }) {
        ForEach(this.goodsList, (item: GoodsInfo) => {
          this.GoodsCard(item)
        })
      }
      .padding(16)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F6FA')
  }

  @Builder
  HeaderBar() {
    Row() {
      Text('商品列表')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1A1A1A')

      Blank()

      Text('查看全部 >')
        .fontSize(14)
        .fontColor('#007DFF')
    }
    .width('100%')
    .padding({ left: 16, right: 16, top: 20, bottom: 12 })
  }

  @Builder
  GoodsCard(item: GoodsInfo) {
    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
      // 商品图片
      Column() {
        Text(item.name.substring(0, 1))
          .fontSize(28)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Bold)
      }
      .width(80)
      .height(80)
      .backgroundColor(item.tagColor)
      .borderRadius(10)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)

      // 商品信息
      Column({ space: 6 }) {
        Text(item.name)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1A1A1A')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Text(item.tag)
          .fontSize(11)
          .fontColor(item.tagColor)
          .backgroundColor(item.bgColor)
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .borderRadius(4)

        Row({ space: 8 }) {
          Text(item.price)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FF4D4D')

          Text(item.originalPrice)
            .fontSize(13)
            .fontColor('#BBBBBB')
            .decoration({ type: TextDecorationType.LineThrough })
        }
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      .margin({ left: 14 })

      // 加购按钮
      Button('+')
        .width(36)
        .height(36)
        .fontSize(22)
        .fontColor('#FFFFFF')
        .backgroundColor(this.selectedId === item.id ? '#FF4D4D' : '#007DFF')
        .borderRadius(18)
        .margin({ left: 14 })
        .onClick(() => {
          this.selectedId = item.id
        })
    }
    .width('100%')
    .padding(14)
    .backgroundColor('#FFFFFF')
    .borderRadius(14)
    .shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 2 })
    .border({
      width: this.selectedId === item.id ? 2 : 0,
      color: '#007DFF'
    })
    .onClick(() => {
      this.selectedId = item.id
    })
  }
}

常见问题与解决方案

1. 阴影在低性能设备上卡顿

问题:大量卡片同时显示阴影,导致滚动卡顿。

解决方案

  • 使用 cache(false) 禁用缓存,让框架动态计算阴影
  • 或者使用 border 模拟阴影,降低渲染开销
  • 考虑使用 LazyForEach 实现虚拟列表,只渲染可见区域

2. 圆角导致内容溢出

问题:设置了 borderRadius,但子元素超出了圆角边界。

解决方案

.borderRadius(14)
.clip(true)  // 裁剪超出圆角边界的子内容

3. 颜色对比度不足

问题:文字颜色与背景色对比度太低,影响阅读。

解决方案:使用 WCAG 颜色对比度检测工具,确保对比度至少达到 4.5:1(AA 标准)或 7:1(AAA 标准)。

写在最后

视觉细节是界面品质的分水岭,而 HarmonyOS6 ArkUI 已经把大量细节控制能力暴露出来了,阴影透明度、圆角大小、字号层级、弹性间距……每个属性都能精确控制。

真正的设计功夫不在于"用了多少 API",而在于对每个参数的取值有清晰的意图:这个阴影为什么是 10% 透明度而不是 30%?这个圆角为什么是 14 而不是 8?这个字号为什么是 16 而不是 18?当你能回答这些问题,界面自然就做得好看了。

在 HarmonyOS PC 端开发中,视觉细节的重要性被进一步放大。PC 端屏幕更大、用户离屏幕更近,细微的视觉瑕疵都会被放大。建议在开发初期就建立一套完整的设计规范,包括颜色体系、圆角体系、字号体系、间距体系,并在整个应用中统一使用。

最后一点,颜色要有系统性——主色、辅助色、背景色、文字色、危险色,在数据结构层面就确定好,通过数据驱动样式,而不是在每个组件里各自硬编码一套颜色。这样日后修改主题、调整配色,改一个地方就够了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐