背景色从灰调到紫调,工作区从渐变到白卡——一次让整体风格更协调的调色。

前言

3D 拆解分析 App 经历了多轮 UI 迭代:布局重构、图标升级、多色系统、Tab 架构。每次改动都在解决具体问题,但整体视觉风格一直没有统一——首页是灰色背景,分析页也是灰色背景,紫色只出现在按钮和选中态上。

这次改动的核心是"让紫色贯穿整个应用":背景变成淡紫色,工作区变成白色卡片,网格选择器的底色也带上紫调。改完后,应用从"紫色点缀"变成了"紫色主题"。

背景色:从灰到紫

旧版

const BG: string = '#F8F7FC'  // 浅灰

新版

const BG: string = '#E6E0F0'  // 浅紫

在这里插入图片描述

#F8F7FC 是接近白色的浅灰,#E6E0F0 是带紫调的浅灰。两个颜色的亮度接近,但色相不同——旧版偏中性,新版偏暖紫。

为什么选这个色值?

#E6E0F0 的 RGB 值是 (230, 224, 240)——蓝色通道最高(240),红色次之(230),绿色最低(224)。这种配比让它看起来是"带蓝紫调的浅灰",而不是"纯紫色"。

如果紫色太重(比如 #D8C8F0),会让界面看起来像儿童应用。#E6E0F0 足够淡,既改变了色调,又不喧宾夺主。

工作区:从渐变到白色卡片

旧版工作区

Column() {}
  .width('100%').height('100%').borderRadius(18)
  .linearGradient({ angle: 180,
    colors: [['#F0EEFA', 0], ['#F8F7FC', 0.5], ['#F5F5F9', 1]] })

渐变背景,从浅紫到浅灰,没有明确的边界。

新版工作区

Column() {}
  .width('100%').height('100%').borderRadius(18)
  .backgroundColor('#FFFFFF')
  .border({ width: 1, color: '#D8D2E8' })
  .shadow({ radius: 12, color: 'rgba(0,0,0,0.06)', offsetY: 3 })

在这里插入图片描述

白色背景 + 淡紫色边框 + 轻微阴影。

为什么改成白色卡片?

  1. 边界感:渐变背景和页面背景融合在一起,用户分不清"哪里是工作区,哪里是背景"。白色卡片有明确的边界,用户一眼就知道"这是主要操作区域"。

  2. 对比度:紫色背景上的白色卡片,对比度比灰色背景上的渐变更高。ExplodedView 组件在白色背景上显示效果更好。

  3. 一致性:其他元素(统计卡片、场景卡片)都是白色背景。工作区也用白色,视觉风格统一。

边框色的选择

.border({ width: 1, color: '#D8D2E8' })

边框色 #D8D2E8 是紫色系的淡色——比背景色 #E6E0F0 稍深,但比纯灰色边框更协调。如果用纯灰边框(如 #E0E0E0),在紫色背景上会显得突兀。

点阵图案:从硬编码到动态生成

旧版:手动放置 Circle

Circle({ width: 3, height: 3 }).fill(A + '08').position({ x: '10%', y: '8%' })
Circle({ width: 3, height: 3 }).fill(A + '06').position({ x: '75%', y: '15%' })
Circle({ width: 4, height: 4 }).fill(A + '08').position({ x: '20%', y: '70%' })
Circle({ width: 3, height: 3 }).fill(A + '06').position({ x: '80%', y: '75%' })

8 个圆点,手动设置位置和透明度。位置不规则,覆盖不均匀。

新版:动态生成点阵

// 数据层
const dlist: DotData[] = []
for (let r: number = 0; r < 12; r++) {
  for (let c: number = 0; c < 8; c++) {
    dlist.push({
      x: (c * 12.5 + 6).toString() + '%',
      y: (r * 8.3 + 4).toString() + '%'
    })
  }
}
this.dots = dlist

// 渲染层(在 build 中使用)
ForEach(this.dots, (d: DotData) => {
  Circle({ width: 3, height: 3 })
    .fill(A + '08')
    .position({ x: d.x, y: d.y })
})

在这里插入图片描述

12 行 × 8 列 = 96 个圆点,均匀覆盖工作区。每个圆点的位置用百分比计算:

  • X 轴:c * 12.5 + 6(每 12.5% 一个点,起始偏移 6%)
  • Y 轴:r * 8.3 + 4(每 8.3% 一个点,起始偏移 4%)

为什么用动态生成?

  1. 覆盖均匀:手动放置很难保证 96 个点均匀分布
  2. 可维护:改行数/列数只需要改循环参数,不需要逐个调整位置
  3. 可复用:同样的逻辑可以用于其他需要点阵图案的场景

DotData 接口

interface DotData { x: string; y: string }

只有两个字段——百分比格式的 x 和 y 坐标。足够轻量,不需要更多属性。

Hero Card 的渐变升级

旧版渐变

.linearGradient({ angle: 135, colors: [[A, 0], ['#6C3CE0', 0.5], ['#A78BFA', 1]] })

三段渐变:品牌紫 → 深紫 → 浅紫。

新版渐变

.linearGradient({ angle: 140, colors: [[A, 0], ['#7C3CE0', 0.35], ['#A78BFA', 0.7], ['#C4B5FD', 1]] })

四段渐变:品牌紫 → 深紫(35%)→ 中紫(70%)→ 浅紫(100%)。

多了一个色阶,渐变更平滑。角度从 135° 微调到 140°,视觉上更自然。

图标容器

Row() {
  Text('📸').fontSize(40)
}
.width(64).height(64).borderRadius(20)
.backgroundColor('rgba(255,255,255,0.15)')
.justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
.margin({ bottom: 14 })

在这里插入图片描述

📸 emoji 不再直接放在渐变背景上,而是包裹在一个半透明白色圆角矩形里。这个"容器"让图标和背景之间有了过渡,不会显得突兀。

装饰圆的位置调整

Circle({ width: 160, height: 160 }).fill('rgba(255,255,255,0.04)').position({ x: '58%', y: -40 })
Circle({ width: 90, height: 90 }).fill('rgba(255,255,255,0.06)').position({ x: '-5%', y: 90 })
Circle({ width: 45, height: 45 }).fill('rgba(255,255,255,0.08)').position({ x: '82%', y: 120 })

三个圆的大小递减(160 → 90 → 45),透明度递增(0.04 → 0.06 → 0.08)。位置从右侧主导改成左右分散,视觉更平衡。

网格选择器的色调统一

旧版

.backgroundColor(p.cols === this.cols && p.rows === this.rows ? A : '#F5F5F7')

未选中态用纯灰色 #F5F5F7

新版

.backgroundColor(p.cols === this.cols && p.rows === this.rows ? A : '#ECEBF0')

未选中态用带紫调的灰色 #ECEBF0。和背景色 #E6E0F0 同色系,但稍深一点。

这种"同色系深浅搭配"让网格选择器和背景融为一体,不会像异色元素那样跳出来。

历史 Tab 的更新

旧版

@Builder HistoryTab() {
  Column() {
    Text('分析历史').fontSize(20)...
    Column() {
      Text('📋').fontSize(48)...
      Text('暂无分析记录')...
      Text('切换到首页选择场景开始拆解')...
    }
  }
}

新版

@Builder HistoryTab() {
  Column() {
    Row() {
      Row() {
        SymbolGlyph($r('sys.symbol.chevron_left')).fontSize(20).fontColor([T1])
      }.width(34).height(34).borderRadius(17).backgroundColor('rgba(0,0,0,0.03)')
      .justifyContent(FlexAlign.Center).onClick(() => { this.tab = 0 })
      Blank()
    }.width('100%').padding({ left: 18, right: 18, top: 12, bottom: 8 })

    Text('分析历史').fontSize(20)...
    Column() {
      Text('📋').fontSize(48)...
      Text('暂无分析记录')...
      Text('完成首次拆解后,记录将显示在这里')...
    }
  }
}

在这里插入图片描述

两个变化:

  1. 加了返回按钮(chevron_left),点击回到首页 Tab
  2. 副文案从"切换到首页选择场景开始拆解"改成"完成首次拆解后,记录将显示在这里"——更准确地描述了这个页面的用途

统计卡片的文案优化

旧版

this.Stat('5 种', '基底选择', A)
this.Stat('4 档', '拆解精度', A3)
this.Stat('2~16', '部件数量', A4)
this.Stat('3D', '爆炸展示', A5)

新版

this.Stat('5 种基底', '满足不同口味', A)
this.Stat('4 档精度', '简单到深度', A3)
this.Stat('2~16 部件', '灵活拆解', A4)
this.Stat('3D 爆炸', '直观展示', A5)

值和标签合并了(“5 种” + “基底选择” → “5 种基底”),副文案更口语化(“满足不同口味”“简单到深度”“灵活拆解”“直观展示”)。

这样做的好处:用户扫一眼就能理解每个卡片的含义,不需要在两行文字之间建立关联。

视觉统一的原则

1. 背景色定调子

背景色是整个应用的"底色"——所有其他颜色都在这个底色上面展示。选了紫色背景,就意味着整个应用的色调偏紫。

其他颜色需要和背景色协调:

  • 白色卡片:在紫色背景上更突出
  • 紫色边框:和背景同色系,不突兀
  • 灰色文字:在紫色背景上可读性足够

2. 卡片要有边界

白色卡片 + 边框 + 阴影 = 明确的"这是内容区域"信号。没有边界的卡片(如旧版的渐变工作区)会让用户分不清内容和背景的界限。

边框色不需要和背景色完全一致,但要同色系。#D8D2E8(边框)和 #E6E0F0(背景)都是紫色系,搭配起来很协调。

3. 装饰元素要低调

点阵圆点用 A + '08'(8% 透明度),几乎看不到但能感受到。Hero Card 的装饰圆用 rgba(255,255,255,0.04~0.08),同样很淡。

装饰元素的透明度建议控制在 10% 以下。太高的透明度会抢视觉焦点,干扰主要内容。

踩坑记录

1. 背景色对文字可读性的影响

紫色背景上的黑色文字(#1E1B2E)对比度约 12:1,可读性足够。但灰色文字(#888888)在紫色背景上的对比度会降低——因为紫色和灰色的色相接近,视觉上不容易区分。

解决方案:在紫色背景上,灰色文字用稍深的色值(如 #777777)或加粗字体。

2. 点阵图案的性能

96 个 Circle 组件(ForEach 渲染)在列表滚动时可能影响性能。如果遇到卡顿,可以:

  • 减少点阵密度(比如 6×4 = 24 个点)
  • visibility 控制:只在可见区域渲染
  • 把点阵做成一张静态图片,用 Image 组件显示

3. 白色卡片在紫色背景上的阴影

.shadow({ radius: 12, color: 'rgba(0,0,0,0.06)', offsetY: 3 })

阴影颜色用的是黑色半透明(rgba(0,0,0,0.06)),不是紫色半透明。在紫色背景上,黑色阴影看起来偏灰,和白色卡片的边界很清晰。

如果用紫色阴影(如 A + '10'),阴影会和背景融为一体,卡片的"浮起"感会减弱。所以黑色阴影在浅色背景上更有效。

4. border 的 color 类型

.border({ width: 1, color: '#D8D2E8' })

ArkUI 的 .border() 方法中,color 参数接受 ResourceColor 类型。字符串格式的颜色值可以直接传入,不需要额外转换。

写在最后

这次改动的核心是"色调统一"——从灰色背景变成紫色背景,让紫色从点缀色升级为主题色。白色卡片 + 紫色边框 + 点阵图案的组合,让界面既有层次感又不失协调。

调色是 UI 开发中最需要审美判断的工作。技术上只是改一个颜色值,但视觉效果的差异可能是"普通"和"精致"的区别。建议多参考成熟应用的配色方案,找到适合自己产品的色调。

Logo

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

更多推荐