HarmonyKit | 鸿蒙新特性实践:ToolCard 统一卡片布局设计迭代
HarmonyKit | 鸿蒙新特性实践ToolCard 统一卡片布局设计迭代卡片的困境工具卡片看起来是最简单的 UI 组件——一个图标、一个标题、一行描述。但当你的网格里有 10 张卡片每张卡片的描述文字长度从 8 个字到 21 个字不等时“简单就变成了棘手”。HarmonyKit 的卡片设计经历了两轮迭代。第一轮是先让它能跑——每个卡片自适应内容高度结果 2 列网格中同行两张卡片高度不齐参差感严重。第二轮是让它整齐——引入了固定高度、统一间距和主题色体系10 张卡片的视觉效果终于统一。项目仓库https://atomgit.com/VON-/harmony-kit迭代一宽度自适应 高度自适应最初的设计很简单——卡片宽度设为44%相对于 GridItem高度不指定靠内容自然撑开Column(){Text(icon).fontSize(32)Text(name).fontSize(14)Text(description).fontSize(11).maxLines(1)}.width(44%).padding(20)这个方案在工具只有 5 个时看起来还凑合。但当工具扩展到 10 个描述文字的差异性暴露了出来“文本与 Base64 互相转换”10 个汉字“二进制、八进制、十进制、十六进制互转”17 个汉字在 11px 的字体下17 个汉字在 44% 屏幕宽度的卡片中会换行。而 10 个汉字的不会。结果就是换行的卡片比不换行的卡片高出一行的高度。在 2 列网格中同一行的两张卡片高度不一致底部参差不齐。迭代二固定高度 柔和阴影 主题色第二版设计做了三个核心改动1. 固定高度 158vp.height(158).justifyContent(FlexAlign.Center)158vp 的选择是计算出来的top padding: 20vp图标圆形: 48vp图标底边距: 12vp名称文字行高: ~20vp名称底边距: 4vp描述文字最多两行: 11px × 1.5 行距 × 2 行 ≈ 33vpbottom padding: 20vp合计: 约 157vp → 取整 158vpjustifyContent(FlexAlign.Center)让短内容的卡片垂直居中而不是贴顶。这样即使内容只有一半高度卡片视觉上也平衡。2. 图标设计文字胜于图形HarmonyKit 的图标不是 PNG 文件而是一个带半透明底色圆的等宽文本Stack(){Circle().width(48).height(48).fill(this.tool.color18);Text(this.tool.icon).fontSize(18).fontWeight(FontWeight.Bold).fontColor(this.tool.color).fontFamily(monospace);}this.tool.color 18这个技巧值得展开。color是十六进制颜色字符串如#007aff。追加18变成了#007aff18——在十六进制中最后两位是 alpha 通道00-FF。18约等于 9% 的不透明度。这个半透明底色圆圈为卡片提供了柔和的视觉锚点比纯色填充精致得多。使用等宽字体文本作为图标好处是零资源文件——不需要 PNG/SVG 文件不增加 APK 体积随字号缩放——字体缩放时图标自动跟随动态换色——每个工具用自己的主题色不需要为每种颜色准备一份图标资源等宽字体保障了对齐——{}和/虽然宽度不同但monospace保证了每个字符占据相同宽度3. 主题色体系10 个工具各有独立的主题色JSON 格式化 #007aff 蓝色 — Apple 的系统蓝 Base64 编解码 #34c759 绿色 — 编解码安全绿色 时间戳转换 #ff9500 橙色 — 时间温暖橙色调 URL 编解码 #5856d6 紫色 — URL网页链接紫色 哈希计算 #ff3b30 红色 — 哈希指纹独一无二红色 UUID 生成器 #30b0c7 青色 — UUIDID冷静青色 颜色转换 #af52de 粉紫 — 颜色光谱粉紫 进制转换 #ff6482 粉红 — 进制数学活泼粉红 正则测试器 #30d158 翠绿 — 正则模式精确翠绿 文本统计 #ff9f0a 琥珀 — 统计数字琥珀配色原则相邻工具的颜色在色环上拉开至少 60 度防止混淆冷色调蓝/青/绿/紫和暖色调橙/红/粉红/琥珀交错排列每个颜色在白色背景上的对比度 4.5:1保证可读性卡片的阴影设计.shadow({radius:10,color:#0d000000,offsetX:0,offsetY:2})阴影参数经过了多次微调radius: 10足够大的模糊半径让阴影看起来柔和不像是硬边框color: #0d000000纯黑色 5% 不透明度。比默认的#1a00000010%更轻在白色背景上若有若无offsetY: 2轻微偏下模拟顶光照下物体的自然投影这个阴影组合在浅灰色背景#f5f5f5上创造了微妙的浮起感——卡片似乎在背景上方 1-2mm 处悬浮。但如果背景也是白色阴影效果会被弱化。这就是为什么主页背景必须是浅灰而非纯白。名称文字的单行截断Text(this.tool.name).fontSize(14).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})工具名称都是精心控制在 10 个字符以内的短文本。“JSON 格式化”“Base64 编解码”“时间戳转换”——最长的是时间戳转换5 个字在 14px 字体下约 70px 宽。卡片宽度约 170px足够容纳。但为了防御未知的长名称仍然设置了maxLines(1) 省略号截断。描述文字的两行限制Text(this.tool.description).fontSize(11).maxLines(2).lineHeight(16).textOverflow({overflow:TextOverflow.Ellipsis}).textAlign(TextAlign.Center)maxLines(2)配合lineHeight(16)让描述区域最多占据 32vp 的高度。超过两行的文字被截断为省略号。居中对齐让描述在卡片宽度内水平居中视觉上更平衡。为什么是 2 行而不是 1 行因为有的描述比较长“二进制、八进制、十进制、十六进制互转需要两行才能完整展示。如果限制为 1 行用户只能看到被截断的二进制、八进制、十进制…”——丢失了关键信息。卡片的交互反馈HarmonyKit 的卡片在点击时提供路由跳转.onClick((){this.getUIContext().getRouter().pushUrl({url:this.tool.routerPath});})使用this.getUIContext().getRouter()而非全局router.pushUrl()——这是鸿蒙路由 API 演进后的推荐写法。UI 上下文绑定的路由实例在多窗口场景下行为正确而全局 router 可能路由到错误的窗口。Grid 布局的配合ToolCard 的width(100%)配合 Grid 的columnsTemplate(1fr 1fr)Grid(){ForEach(TOOL_LIST,(tool:ToolItem){GridItem(){ToolCard({tool:tool})}})}.columnsTemplate(1fr 1fr).columnsGap(12).rowsGap(12)Grid 平均分配两列每列宽度 (screenWidth - 左右padding - gap) / 2。卡片的width(100%)填充 GridItem 的全部可用宽度。columnsGap和rowsGap定义卡片之间的间距——12vp 是一个经验值既不显得拥挤也不浪费空间。项目仓库https://atomgit.com/VON-/harmony-kit