查看: 199|回复: 3

ArkUI UI重设计实践:从600行规范到代码落地

[复制链接]
发表于 昨天 12:00 | 显示全部楼层 |阅读模式
大多数技术文章讲的是“怎么实现一个功能”,但本文换个角度:如何把一份600行的设计规范文档,转化成可运行的ArkUI代码。项目“本地扫描仪”包含文件扫描、证件照、OCR三个模块。最初版本在UI上问题明显——证件照页面同时铺了8到10个交互元素,字号从11sp到22sp出现了8种,按钮主次不分。于是我们编写了完整的设计规范文档,逐页重构,并记录下这个过程:设计规范怎么写、代码怎么落地、踩了哪些坑。

一、问题诊断:先量化再动手
重构前,我们先做了两件事:统计每个页面的交互元素数量,以及统计代码中所有fontSize取值。证件照页面最严重——背景色选择、尺寸选择、自定义开关、RGB输入框、导出按钮全部展开在同一屏内。字号上,同级元素(区块标题)同时出现了13、15、16三种取值,典型的“边做边加”导致的不一致。

二、建立字体系统:常量统一管理
解决字号混乱的第一步是建立统一的Typography Scale。我们定义了7级字体层级:H1 Display 24sp Bold(页面唯一大标题)、H2 Section 18sp SemiBold(功能区块标题)、H3 Card 16sp Medium(卡片内标题)、Body Large 15sp Regular(按钮文字/重要正文)、Body 14sp Regular(副标题/说明文字)、Caption 12sp Regular(辅助提示)、Tab Label 10sp Regular(底部Tab文字)。比原来的8种取值少一种,但覆盖所有场景。关键变更:区块标题统一为18sp(取代13/15/16混用),极小提示从11sp提升到12sp。

代码落地使用常量统一管理。创建AppTypography.ets文件,定义Typography对象:
  1. export const Typography = {
  2.   H1: { size: 24, weight: FontWeight.Bold },
  3.   H2: { size: 18, weight: FontWeight.SemiBold },
  4.   H3: { size: 16, weight: FontWeight.Medium },
  5.   BodyLg: { size: 15, weight: FontWeight.Normal },
  6.   Body: { size: 14, weight: FontWeight.Normal },
  7.   Caption: { size: 12, weight: FontWeight.Normal },
  8.   Tab: { size: 10, weight: FontWeight.Normal }
  9. };
复制代码
使用时直接引用常量,不再手写数字:
  1. Text('文件扫描')
  2.   .fontSize(Typography.H1.size)
  3.   .fontWeight(Typography.H1.weight)
复制代码
好处是改字号只需改一处常量,坏处是初期迁移工作量大,每个页面的Text组件都要改。

三、证件照页面重构:渐进式披露
证件照页面是重构重点,8到10个交互元素压缩到3个。核心思路是渐进式披露:默认只展示最常用选项,高级选项折叠收起。阶段划分:阶段1(无图片时)只展示选图入口;阶段2(有图片后)展示预览+背景色+尺寸+导出;阶段3(自定义展开)用户主动展开高级选项。

背景色选择器从3个文字chip改为圆形色块选择器:
  1. Row({ space: 12 }) {
  2.   ForEach(BgColors, (c: BgColorItem) => {
  3.     Column() {
  4.       Circle()
  5.         .width(44).height(44)
  6.         .fill(c.color)
  7.         .stroke(this.selectedBg === c.id ? '#007DFF' : 'transparent')
  8.         .strokeWidth(3)
  9.         .scale({ x: this.selectedBg === c.id ? 1.08 : 1.0 })
  10.       Text(c.label)
  11.         .fontSize(Typography.Caption.size)
  12.         .fontColor('#999')
  13.     }
  14.   })
  15. }
复制代码
尺寸选择器从4个平铺chip改为下拉选择器:
  1. Column() {
  2.   Row() {
  3.     Text(`当前:${this.selectedSize.label}`)
  4.       .fontSize(Typography.Body.size)
  5.     Image($r('app.media.ic_arrow_down'))
  6.       .rotate({ angle: this.showSizeDropdown ? 180 : 0 })
  7.   }
  8.   if (this.showSizeDropdown) {
  9.     ForEach(SizeOptions, (s: SizeOption) => {
  10.       Text(`${s.label} ${s.width}×${s.height}mm`)
  11.         .onClick(() => {
  12.           this.selectedSize = s;
  13.           this.showSizeDropdown = false;
  14.         })
  15.     })
  16.   }
  17. }
复制代码

四、扫描结果页重构:按钮层级
扫描结果页原图/增强切换、导出PDF、导出图片、分享四个操作平铺,视觉权重相同。解决方案是分层+ActionSheet合并。模式切换采用Segmented Control样式:
  1. Row() {
  2.   ForEach(['原图', '增强'], (mode: string) => {
  3.     Text(mode)
  4.       .fontSize(14)
  5.       .fontColor(this.currentMode === mode ? '#fff' : '#999')
  6.       .backgroundColor(this.currentMode === mode ? '#007DFF' : 'transparent')
  7.       .borderRadius(18)
  8.       .padding({ left: 20, right: 20, top: 8, bottom: 8 })
  9.       .onClick(() => { this.currentMode = mode; })
  10.   })
  11. }
复制代码
导出按钮用主操作+ActionSheet:
  1. Button('导出', { type: ButtonType.Capsule })
  2.   .height(52)
  3.   .width('100%')
  4.   .backgroundColor('#007DFF')
  5.   .onClick(() => {
  6.     ActionSheet.show({
  7.       items: [
  8.         { title: '导出为 PDF' },
  9.         { title: '导出高清图片' },
  10.         { title: '分享给好友' }
  11.       ]
  12.     })
  13.   })
复制代码
主操作按钮用实心填充52vp高度,次要操作用描边样式44vp。三种导出方式收进一个弹窗,首页只保留1个导出按钮。

五、响应式布局:断点系统
我们定义了四个断点:SM ≤600vp(手机竖屏,底部Tab)、MD 600~840vp(手机横屏,底部Tab)、LG 840~1200vp(平板竖屏,左侧Tab)、XL ≥1200vp(平板横屏,左侧Tab)。关键组件在不同断点下布局差异:
  1. if (this.breakpoint >= Breakpoint.LG) {
  2.   // 平板:左右分栏
  3.   Row() {
  4.     Column() { this.SwiperPreview() }.layoutWeight(3)
  5.     Column() { this.OperationPanel() }.layoutWeight(2)
  6.   }
  7. } else {
  8.   // 手机:上下堆叠
  9.   Scroll() {
  10.     Column() {
  11.       this.SwiperPreview()
  12.       this.OperationPanel()
  13.     }
  14.   }
  15. }
复制代码
断点检测使用系统API:
  1. import { display } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct Index {
  5.   @StorageProp('breakpoint') breakpoint: string = 'sm';
  6.   aboutToAppear() {
  7.     const w = display.getDefaultDisplaySync().width;
  8.     this.updateBreakpoint(w);
  9.     display.on('displayChange', () => {
  10.       const nw = display.getDefaultDisplaySync().width;
  11.       this.updateBreakpoint(nw);
  12.     });
  13.   }
  14. }
复制代码

六、动画规范
全局动画参数:证件照选图成功后的进场动画是预览图淡入+上滑:
  1. Column() {
  2.   Image(this.selectedImage)
  3.     .opacity(this.showPreview ? 1 : 0)
  4.     .translate({ y: this.showPreview ? 0 : 10 })
  5.     .animation({ duration: 300, curve: Curve.EaseOut })
  6. }
复制代码
Tab切换的弹性缩放:
  1. Image(tab.icon)
  2.   .scale({
  3.     x: this.activeTab === tab.id ? 1.08 : 1.0,
  4.     y: this.activeTab === tab.id ? 1.08 : 1.0
  5.   })
  6.   .animation({
  7.     duration: 200,
  8.     curve: springMotion(0.5, { damping: 0.7 })
  9.   })
复制代码
springMotion参数response 0.5秒,damping 0.7,产生轻微弹跳后迅速收敛的效果。

七、踩坑记录
坑1:fontSize常量的类型问题。ArkUI的fontSize接受number|string|Resource。如果常量定义为{size:24},类型推断为number,但某些组件(如Button)的fontSize需要string或Resource。修复:常量定义为number,使用时转string或定义为Resource:.fontSize(Typography.H1.size.toString())或H1: { size: $r('app.integer.font_h1') }。Resource方式更符合规范,但需要额外资源文件。

坑2:ForEach的keyGenerator。证件照背景色选择器用ForEach渲染色块,如果不提供keyGenerator,列表项身份识别不准确,切换选中态时动画会闪烁。必须提供keyGenerator:ForEach(BgColors, (c) => {...}, (c) => c.id)。

坑3:ActionSheet在不同设备上的表现。手机上ActionSheet从底部滑入,高度自适应;平板上ActionSheet宽度会撑满屏幕,因为平板默认使用Dialog样式。修复:平板端改用自定义Popup或DropdownMenu。

坑4:动画时长与帧率的关系。100ms微交互在60fps设备上约6帧,在120fps上约12帧,更平滑但可能感觉“太慢”。HarmonyOS的动画曲线在不同帧率下会自动插值,无需手动处理。

坑5:设计规范的执行一致性。最难不是写规范,而是保证每个开发者都遵守。代码审查时发现有人在新页面里又手写fontSize(16)而不是引用Typography.H3。修复:ESLint规则禁止直接使用数字字号:"harmonyos/no-hardcoded-font-size": "error"。

八、性能数据
代码行数反而减少了约15%,因为消除了大量重复的字号、颜色、间距硬编码。

九、核心思想
回顾整个过程,最有价值的三个决策:先量化问题再动手,不是“我觉得字号乱”而是“统计出8种取值”,数字比直觉更有说服力;渐进式披露解决信息过载,证件照页面从10个元素压缩到3个,不是删除功能而是折叠;常量化消除不一致,Typography常量+ESLint规则从源头杜绝字号混乱。核心差异:传统重构靠开发者的审美直觉,本方案靠600行设计文档+常量体系,设计文档是代码的上游输入,代码是设计文档的下游实现。
回复

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: ArkUI UI重设计实践:从600行规范到代码落地

很干货的一篇实践总结,从量化问题到建立设计规范,再到ArkUI代码落地的每一步都交代得很清楚。特别是“渐进式披露”的思路和ActionSheet合并导出操作,对减少页面信息密度很有启发。请问在重构过程中,对比重构前后用户反馈或操作效率有没有量化数据?比如证件照页面完成时间或点击次数变化?
回复 支持 反对

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: ArkUI UI重设计实践:从600行规范到代码落地

楼主的分享很扎实,从问题量化到常量定义再到分层重构,每一步都有理有据。特别喜欢“渐进式披露”的思路——证件照页面从8~10个元素压缩到3个,这不仅仅是界面清爽,更是在帮用户做决策减负。另外字体常量的做法确实能根治“边做边加”导致的字号混乱,后期改主题或适配不同屏幕也会省很多事。想问下,在迁移600行规范到代码的过程中,团队内部有没有遇到阻力?比如设计师和开发之间对某些层级定义的理解偏差,或者旧页面批量修改时有没有自动化工具辅助?
回复 支持 反对

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: ArkUI UI重设计实践:从600行规范到代码落地

这篇文章非常接地气,从实际痛点出发,用数据说话,把设计规范和代码落地的过程讲得很清晰。特别是“渐进式披露”和“按钮层级”的思路,对UI工程师很有参考价值。想请问一下,600行的设计规范文档除了字体系统,还覆盖了哪些维度(比如间距、色彩、动效)?在迁移到常量管理时,团队有没有遇到组件库与自定义样式冲突的情况?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-21 08:15 , Processed in 0.030711 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部