ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

HarmonyOS时间管理应用:ArkUI三层布局与Canvas环形图实战解析

HarmonyOS时间管理应用:ArkUI三层布局与Canvas环形图实战解析 做HarmonyOS应用开发的人应该都有体会系统版本迭代快UI范式一年一个样但真正难的从来不是学新API而是把一个抽象概念落地成用户愿意天天打开的页面。最近我在做一款基于HarmonyOS 6的时间管理APP核心页面叫时光重塑目标就一个把一个人每天的时间去向摊开来看再用一个简单的操作把溜走的碎片时间重塑回该去的地方。这篇文章把我从原型到真机调优的完整布局设计思路、组件选型逻辑和踩坑记录都拆出来给正在做鸿蒙原生应用的开发者一个可以直接参考的案例。1. 从需求到布局为什么时光重塑页面必须是这三层结构1.1 传统时间统计页面的通病信息堆砌不等于有效管理我团队早期做过一版时间记录类的原型设计稿参考了不少主流效率工具打开页面全是统计面板柱状图、折线图、饼图、打卡日历、连续天数、成就徽章……数据维度拉满看起来非常专业。但真正放上真机用了一周之后我自己的感受是完成记录这个动作之后我再也没有欲望打开那个页面了。因为那一整屏图表回答不了我真正关心的问题——我今天的时间到底花哪儿去了哪些时间是可以改变的大量的时间管理APP死在同一个地方它们擅长记录却不擅长推动改变。用户关掉页面之后该刷手机还是刷手机碎片时间还是碎片时间。所谓管理的动作没有在界面上形成一个明确的入口和反馈机制。所以在做HarmonyOS 6这版重构的时候我给自己定了一个非常朴素的产品原则时光重塑页面不追求展示所有的数据只专注回答三个问题——今天的时间流向了哪里、哪些是被浪费的碎片、怎么把它们变回有价值的时间块。1.2 时光重塑的设计目标让用户三秒内看懂时间去向这三句话翻译成设计语言就是三个动作总览、定位、行动。总览要求用户打开页面后三秒内就能感知今天的时间质量不需要逐个数字去读定位要求每个时间段、每个时间块的含义足够直观用户能迅速找到哦这里有两段刷短视频的空白时间行动要求页面上存在直达的操作入口让重塑这个动作可以一键完成而不是需要跳到二级页面去处理。于是我把整个页面压缩成了一个纵向的信息流从上到下依次是势能区、明细区、重塑区。这个三层结构不是拍脑袋定的它对应的是人处理时间信息时的认知路径先看到全貌再找到具体对象最后产生动作。任何一个环节如果被其他信息挡住了用户的使用意愿都会断崖式下跌。1.3 三区域布局框架势能区、明细区、重塑区的分工布局上顶部势能区是一个环形时间分布图把一天的24小时按时间段聚合成几个扇区高效时间块用冷色调表示碎片时间块用暖灰色表示。用户扫一眼就能看出今天成块的时间和被切碎的时间之间的比例关系。中部明细区沿用时间轴的形式从上到下按时间顺序排列今天的所有时间块每个块都由一个卡片承载卡片上有开始时间、时长、所属项目、操作入口。之所以这里要回归时间轴是因为时间管理本质上是对连续性的管理时间轴是最不容易产生理解偏差的形式。底部重塑区是一个悬浮的活动区域专门放那些还没有归属的碎片时间块。它相当于一个临时的回收站所有被标记为碎片的时间都汇聚在这里用户可以长按其中任意一块把它重塑到某个目标项目里。这个布局完成之后整个页面就形成了一条完整的行动链路看分布 → 找碎片 → 重塑。2. ArkUI组件选型用声明式语法搭出时间轴的基础骨架2.1 页面骨架与安全区处理Scroll、Column、LayoutWeight的取舍第一版原型里我最自然的想法是整个页面用一个Scroll包一个ColumnColumn里放上中下三个区域。跑起来之后很快就发现问题了明细区的时间块数量一旦变多整个页面的滚动就和顶部环形图的动画互相争抢性能低端真机上能明显感觉到滑动时的卡顿。后来我调整了层级关系页面根节点用Column顶部势能区固定高度不滚动中部明细区用List来承载底部重塑区悬浮在页面底部固定位置。List自带懒加载和缓存复用机制几十个时间块的场景下性能非常稳定。我的经验是ArkUI里Column/Row适合做静态布局骨架一旦遇到需要滚动和复用的内容区交给List是更稳妥的方案。沉浸式布局是另一个容易被忽略的细节。HarmonyOS 5之后的应用普遍做沉浸式状态栏我在HarmonyOS 6项目里也不例外。为了让背景色延伸到状态栏和导航条区域我在根容器上加了expandSafeArea只扩展顶部和底部两个安全区左右保持系统默认的边界避免横屏或折叠屏展开时内容被圆角裁切。Entry Component struct TimeRemodelPage { build() { Column() { // 顶部势能区 TimeRingChart() // 中部明细区 TimeBlockList() // 底部重塑区 RemodelZone() } .expandSafeArea( [SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM] ) .width(100%) .height(100%) .backgroundColor(#0E0F13) } }这段结构看起来简单但它把所有滚动和布局压力都约束在了明晰的区域里环形图不会因为列表滚动而反复重绘底部重塑区也不会跟随内容一起滚出屏幕。这是整个页面布局的基石。2.2 时间环形分布图Canvas自绘是比Ring组件更灵活的选择顶部环形时间分布图我考虑过两种方案直接用Progress组件的环形类型做多环叠加或者用Canvas自绘。Progress的Ring形态用起来确实简单两三行代码就能画出一个圆环。但问题在于它做不了多段弧线一天的时间分布要按不同项目段上不同的颜色用多个Progress叠在一起虽然能实现但偏移角度、分段占比的逻辑都不可控扩展性很差。所以最后我选择了Canvas自绘。自绘的核心逻辑其实不复杂先归一化计算出每个时间块占总时长的比例再乘以2π得到对应的弧度从12点钟方向开始用arc方法逐段绘制。为了防止不同分段在交界处产生重叠毛边每一段的结束角度减去了一个很小的值相当于在每段之间留一道细缝。private drawRing(ctx: CanvasRenderingContext2D, segments: SegmentData[]) { const width this.ringSize; const height this.ringSize; const centerX width / 2; const centerY height / 2; const radius width / 2 - 24; const lineWidth 18; ctx.clearRect(0, 0, width, height); let startAngle -Math.PI / 2; const totalMinutes segments.reduce((sum, seg) sum seg.minutes, 0); for (const seg of segments) { const sweepAngle (seg.minutes / totalMinutes) * 2 * Math.PI; ctx.beginPath(); ctx.strokeStyle seg.color; ctx.lineWidth lineWidth; ctx.lineCap round; ctx.arc(centerX, centerY, radius, startAngle, startAngle sweepAngle - 0.04); ctx.stroke(); startAngle sweepAngle; } }Canvas方案的收益不只是画图它让点击某个扇区联动中部明细区滚动到对应位置这种交互变成了可能。我在Canvas上注册了点击事件通过判断点击坐标与圆心的相对位置计算角度再反查对应的分段索引驱动明细区滚动到对应的时间块位置。这种联动效果用现成的Ring组件做起来极其痛苦自绘反而一劳永逸。2.3 时间块卡片列表数据量不大时ForEach比List更省心中部明细区我一开始也是用List ListItem做的后来发现当天的时间块一般不超过30个这个数量级下List的复用机制反而成了负担滚动行为的预测性和Column ForEach相比没有优势。于是我把明细区改成了Scroll包Column配合ForEach渲染所有时间块卡片。这里有一个关键的操作ForEach的第三个参数必须传item生成器也就是给每条数据一个稳定的id这样只有数据内容真正变化时卡片才会重建而不是整个列表整体刷新。每条时间块卡片我用Builder封装成一个独立函数卡片内部包含项目色条、标题、项目名、开始时间和时长、右侧的操作按钮。Builder的好处是它把卡片的UI描述和列表的数据逻辑分开代码可读性高很多。卡片本身的布局是一个Row左侧色条固定4vp宽度中间文本区域用layoutWeight(1)占满剩余空间右侧操作区固定宽度。这种布局在折叠屏展开时也能自动撑开中间区域不需要额外写适配逻辑。2.4 重塑交互面板长按弹层比拖拽重构更可靠重塑操作我最终选择了长按时间块卡片 → 底部弹层选择目标项目作为主交互而不是拖拽。原因很现实拖拽重构在手机这种小屏幕上是体验上限高但稳定性偏低的操作。手机屏幕空间有限拖拽目标区域小误触率和高学习成本会让大多数用户望而却步。而长按弹出底部面板是移动端用户最熟悉的交互范式几乎零学习成本。底部弹层我用的bindSheet从卡片所在位置呼出默认高度约40%屏幕。面板上半部分显示当前碎片时间块的摘要信息时长、开始和结束时间、被标记为碎片的原因。面板下半部分是目标项目的选择按钮按最近使用频率排序点击项目后弹层收起顶部环形图和明细区同步更新。整个交互闭环不到三秒就能完成。这个选择也反过来影响了布局设计因为不需要为拖拽腾出额外的空白区域明细区的卡片可以做得更紧凑每屏展示更多时间块底部重塑区也只需要一个简洁的碎片池聚合视图不需要支持跨区域拖放。3. 视觉层级与细节规范让时光这个主题渗透到每个像素3.1 色彩与质感琥珀色主调搭配毛玻璃的克制配色时光重塑这个语义自带一种温润、有质感的调性。我在配色上定了一个非常克制的方案背景用近黑的深色#0E0F13主强调色是琥珀金#C9A963代表经过重塑、有价值的时间高效专注的时间块用青绿色#3DD6BF碎片时间块用暖灰色#8A8F98文字层级上主标题用高亮白辅助描述用次级灰。整个页面用到了毛玻璃效果。具体的做法是在顶部势能区的环形图下方垫了一层背景使用backgroundBlurStyle(BlurStyle.Thin)让环形图看起来像悬浮在一层半透明介质之上而不是生硬地贴在背景上。底部重塑区也用同样的处理保证悬浮层和列表内容之间产生前后关系。毛玻璃在这个页面里的价值不只是好看它承担了重要的视觉层级功能势能区和重塑区都是浮在时间轴列表上方的抽象层用模糊背景隔开用户能自然感知到这两个区域是可交互的控件区域不是普通内容。我这里要提醒一句BlurStyle级别的选取要慎重Thin以上档位的模糊半径在低端设备上会带来明显的耗电和渲染压力如果没有多设备测试条件建议只用Thin。3.2 间距与圆角基于8vp栅格的统一节奏页面布局的很多细节问题都出在间距不统一上。我定了一套基础的栅格规则页面左右边距统一16vp卡片与卡片之间间距12vp卡片内部内边距16vp顶部势能区底部留24vp的呼吸空间底部重塑区距明细区保持12vp的悬浮关系。圆角也做了统一中部的卡片圆角12vp底部弹层圆角24vp符合从底部上滑的面板形态按钮圆角8vp环形图中心如有文字注释则不加背景。这套数字看起来琐碎但它的本质是让用户在同一信息层级里只看到一种曲线、一种间隔视觉上的规整感会直接转化为对APP专业度的信任。HarmonyOS 6项目里我不建议在组件代码里到处写魔法数字。ArkUI支持通过resources定义资源维度把间距、圆角、字号都抽成分层参数。我在项目里建了一套DimensionRes把常用的spacing和radius都集中管理这样后续做平板适配或者深色模式微调时改一处全局生效而不是翻遍所有文件找数字。3.3 深色模式适配自定义组件最容易翻车的几个地方这个APP主色就是深色所以对深色模式的适配反而有了更高的要求不是简单地把背景压暗而是所有用到的色彩都要有在深色介质上的正确呈现。最容易翻车的是自定义组件。Canvas绘制环形图时我最初直接在drawRing里写死了一段色值数组结果在系统和应用都切到深色主题时有几段颜色看起来发灰像是蒙了一层雾。排查了半天才发现问题不在Canvas而是我在代码里硬编码的颜色值没有跟随资源目录切换。后来我改成从resources的color.json里读取颜色值在组件初始化时通过ResourceManager拿一次之后缓存起来使用。这样深色模式下的色值可以在资源文件里单独定义。文本方面我也遵循了HarmonyOS的层级命名习惯主文本用一级亮度辅助文本用二级亮度时间戳、项目等次要信息用三级亮度。这样用户在不同亮度环境下都能快速抓住页面的信息主次。4. 多设备与卡片服务布局从手机延伸到折叠屏和桌面的策略4.1 响应式断点设计手机竖屏、折叠屏开合、平板横屏HarmonyOS强调一次开发多端部署布局上最直接的体现就是断点设计。我在项目里用mediaquery监听当前窗口宽度把设备粗粒度分成三个档位窄屏小于600vp对应手机竖屏、中屏600840vp对应折叠屏展开态或者小平板横屏、宽屏大于840vp对应大平板或者智慧屏类场景。每个档位不只是列表宽度变化而是真正的布局结构变化。窄屏就是前面讲的三层纵向结构中屏把顶部环形图挪到左侧固定约280vp宽的容器里右侧是时间轴明细和底部重塑区变成双栏宽屏则把环形图扩大到整个左侧主视图右侧不仅有时间轴还可以增加一列当日统计摘要。断点切换的时候要注意状态保持。用户在窄屏滚动到底部展开折叠屏后变成双栏明细区不应该跳回顶部。我在实现时用了一个简单的记忆方案明细区Scroll组件维护一个scrollPosition在媒体查询回调里先记录当前位置再在布局切换完成的onAreaChange回调里恢复滚动位置。4.2 折叠屏双栏布局环形图与明细区的空间再分配折叠屏场景里用户展开屏幕的瞬间往往带着看得更多的预期。双栏布局直接把总览和明细两件事分屏处理左边环形图始终可见右边时间轴自由滚动用户不需要像手机端那样滚回去看总览。这个双栏布局在架子层面前面已经处理好了根节点Column无法直接分栏所以我在中屏和宽屏档位下换成了Row内部放两个子容器。两个子容器之间通过一个可以拖动的分隔条调整宽度比例默认是4:6左边环形图略窄。分隔条用一条1vp的Divider实现配合PanGesture拖动拖动范围限制在总宽度的30%到50%之间。真机测试时发现一个问题折叠屏开合动画进行的过程中宽度会连续变化mediaquery断点触发频率非常高如果每次都直接重建布局会出现肉眼可见的闪烁。我的处理办法是在断点回调里加一层防抖只有当宽度在同一个断点区间内连续稳定超过150毫秒才真正执行布局切换。4.3 服务卡片把一键重塑放到桌面上HarmonyOS的服务卡片是我非常喜欢的一个能力它把时光重塑里最高频的动作搬到了桌面用户不需要解锁手机打开APP就能完成一部分重塑操作。卡片我用的是FormExtensionAbility尺寸做了两个版本2×4的摘要卡展示今日注意到的碎片数、已重塑数和一个环形缩略图4×4的完整卡则多一个目标项目的快捷按钮列表点击某个按钮直接把当前选中的碎片时间块重塑到该项目全程不离开桌面。卡片布局有个限制卡片只能使用基础组件构建不支持自定义组件、动画和复杂手势。所以卡片设计得非常克制一个标题文本、两行数据文本、一排按钮全部用Column和Row搭出来。缩略的环形图是用多个Progress的环叠加画的因为卡片场景不需要点击交互这种静态分段展示足够用了。开发卡片最容易踩的坑是刷新机制。卡片的UI是通过formProvider刷新数据驱动的不是实时响应式的。我在每次用户完成重塑动作后主动调formProvider.updateForm去更新桌面上对应卡片的UI避免用户看到过期数据。这个坑不踩一次很难想到等到用户截图反馈卡片上显示的数字和APP里不一样时你就知道有多被动了。5. 真机调试中遇到的五个布局坑与解决方案5.1 沉浸式页面底部被手势条区域遮挡第一次在真机上跑沉浸式布局时底部重塑区的按钮被全面屏的返回手势条区域盖住了一半点按区域明显变小。原因是expandSafeArea只是让背景色延伸到安全区之外并不意味着底部内容会自动避让手势条。解决办法是在底部重塑区容器上通过onAreaChange拿到底部安全区的高度然后给容器加paddingBottom。注意不能用固定的数值不同机型的底部安全区高度不一样折叠屏展开之后也会变化。我把这个安全区高度放在自定义组件的一个成员变量里容器重新布局时动态更新。5.2 Scroll区域随着环形图绘制而抖动第一次实现中我把环形图放在了页面的头部和明细区在同一个Scroll里。结果每次环形图数据更新触发重绘时整个头部区域的高度都要重新计算明细区的滚动位置就会出现肉眼可见的跳动尤其在快读完一条长卡片准备点按的时候误触率很高。排查下来根因是环形图所在的容器没有固定高度。环形图尺寸我改成了固定值比如宽度80%的vp值换算成具体像素并且通过aspectRatio锁死宽高比确保任何情况下头部高度不变。这一处修改对滚动稳定性的改善非常明显。5.3 Canvas颜色在暗色场景下发灰这个问题前面提了一下这里说完整的排查经过。现象是深色模式下环形图几段的颜色都发灰像半透明盖在深色背景上。反复检查Canvas绘制代码没有发现问题后来把取色的来源从代码硬编码改成了资源的Resource引用问题立即消失。原因是resources里被系统根据深浅色模式做了动态替换代码硬编码的颜色值不会跟随模式切换而Canvas绘制时如果不显式设置背景透明会基于所在环境的色彩配置做混合于是出现了发灰这种半雾效果。所以自定义绘制的颜色值一律要从resources读取别偷懒写死。5.4 长按弹出弹层和列表滚动手势冲突长按手势默认会和列表滚动手势竞争识别。测试中发现用户在列表上快速滑动时偶尔会触发长按弹层反过来长按某张卡片时间稍长松手时又偶尔带起一次滚动弹层消失得很突兀。解决办法是在卡片的长按手势上加GesturePriority让长按手势在满足判定条件时独占事件流不再向下传递给Scroll。具体做法是用TapGesture配合LongPressGesture做联合判断只有长按达到400毫秒阈值且手指位移小于一定范围时才触发弹层否则全部让位给滚动。5.5 页面冷启动首帧白屏布局再精致用户打开页面第一眼如果看到白屏印象分就直接打折扣。我排查发现白屏问题的原因为页面入口的aboutToAppear里做了太多同步工作读取本地数据库的时间块数据、计算环形图分段、恢复上次滚动位置。解决思路是拆首帧页面先渲染一个静态骨架骨架的布局结构和真实数据页完全一致但内容用占位色块代替数据查询完成后再一次性替换成真实内容。环形图中的环可以先画成一个完整的灰色圆环等数据到位后再动画过渡到真实分段。这个骨架屏→数据渲染的节奏在用户感知上几乎是无缝的但在性能较差的机器上首帧时间从原来的800毫秒以上降到了300毫秒以内。最后分享一个细节上的小建议时间管理APP的页面布局设计永远不要只盯着视觉效果页面的核心职责是缩短用户从看到问题到解决问题之间的距离。我在时光重塑页面里做的三次布局推翻每一次都是为了减少一个多余的动作。如果你也正在做这类工具型应用不妨在布局定稿前多问自己一句用户在这个页面上完成关键动作真的需要经过我现在设计的这些步骤吗少一步就是赢一步。
返回列表