ArkUI Canvas:3DGS质检直方图零样本清屏与批次重绘【鸿蒙心迹】

发布时间:2026/10/14 1:22:53
ArkUI Canvas:3DGS质检直方图零样本清屏与批次重绘【鸿蒙心迹】 示例工程CloudHistogram页面HistogramPage任务CH-1707批次版本v3分桶[2, 4, 1, 0, 3]样本总数10。正文数据、代码和所有图片均为教学演示图片并非真实 DevEco Studio 截图或真机运行实测。一、图没错错在把上一批的像素留了下来我在整理一套 3DGS 候选帧质检工作台时碰到一个容易被前端忽视的小问题。右侧列表换了批次左侧质量分布图却还保留着上一批的高柱子。用户以为新一批的质量提升了实际上只是画布没有清干净。Canvas 和普通声明式组件不太一样修改一份数组不等于画布上旧的矩形会自动消失。画布更像一块画板本次绘制只负责把新东西涂上去没画到的区域仍有机会保持原来的内容。所以这次我不把篇幅花在点云重建算法上也不把五个统计桶包装成模型能力。我只做一个可复现的演示页面CloudHistogram 的 HistogramPage 固定显示任务 CH-1707批次版本为 v3五个质量区间的数量依次是 2、4、1、0、3总样本数 10。它们是手工准备的样本数据不是设备采集、算法推理或 AppGallery 审核结果。工程真正要检验的是批次变化、空数据和迟到事件进入后图形能否诚实地对应当前状态。页面采用 320×180 的逻辑绘制区域五根柱子之间保留固定间隔。空桶也要占位置因为第 4 桶为 0 并不意味着第 5 桶应该向左挪一个格子。若图形只根据非空桶构造横坐标视觉上便改变了统计区间的含义。我的约定是无论数据怎样变化横轴总有五个位置数据缺失或计数为零时画空位不偷偷调整分类顺序。这里还要分清两个“空”一个桶为零意味着这个质量段暂时没有样本五个桶总数全为零才意味着当前批次没有有效样本。它们在 UI 上不该都显示“暂无数据”。前者保留完整图表和空柱后者清屏后显示明确的空状态。把这两个条件混起来质检人员就无法判断是质量分布偏移还是整批压根没载入。二、先把统计输入变成可靠的快照画图之前我先处理数据的形状。业务层接过来的数组并不天然可信它可能长度错误、夹带负数或把小数当作计数。为了不让渲染代码每次都猜一遍第一段函数直接生成不可含糊的五桶快照不符合约定就返回错误由页面展示数据错误态绝不强行补齐凑数。这样做看着严格但对于质检报表而言误画往往比不画更难发现。interface HistogramSnapshot { taskId: string; version: number; bins: number[]; total: number; } function createSnapshot(taskId: string, version: number, input: number[]): HistogramSnapshot { if (taskId.length 0 || !Number.isInteger(version) || version 0) { throw new Error(invalid task/version); } if (input.length ! 5) { throw new Error(five bins required); } let total 0; input.forEach((value: number) { if (!Number.isSafeInteger(value) || value 0) { throw new Error(invalid bin count); } total value; }); if (!Number.isSafeInteger(total)) { throw new Error(total overflow); } return { taskId, version, bins: input.slice(), total }; }这里特意用了slice()复制数组。否则异步回调持有同一个可变数组页面在绘图中途就可能读到半新半旧的数据。total只从五个桶算出来而不是信任外部又传来的一个总数字段。对演示数据[2,4,1,0,3]函数应给出总数 10将输入换成[0,0,0,0,0]得到的总数才是 0。复制的是小型数字数组不涉及大规模点云真实项目如果处理百万级结构需要单独考虑内存成本。从状态设计上讲我保留了taskId和version因为“是不是同一批次”必须先于“画成什么样”。仅比较时间戳可能会被终端时钟变化干扰仅比较数组是否相等则会误把内容相同的新批次当作旧批次。这里用单调增加的业务版本号只是 Demo 的前置协议约束不宣称它是系统自动生成的版本信息。数据提供方如果做不到单调递增页面就应该更换可验证的业务批次标识。三、Canvas 的责任是清屏、坐标、再绘制Canvas 是命令式绘图接口。官方示例使用RenderingContextSettings、CanvasRenderingContext2D和Canvas(...).onReady获取可绘制时机。这里沿用这套基础接口不引入不存在的“自动 diff 画布”能力。第二段代码展示页面关键部分首次onReady才绘制每次接受新快照时都先对整块逻辑区域做clearRect再画五根柱子。关键不是哪一种颜色而是所有分支都走同一个“整帧重建”的入口。Entry Component struct HistogramPage { private settings: RenderingContextSettings new RenderingContextSettings(true); private ctx: CanvasRenderingContext2D new CanvasRenderingContext2D(this.settings); private ready: boolean false; private current: HistogramSnapshot createSnapshot(CH-1707, 3, [2, 4, 1, 0, 3]); State private statusText: string v3 · 10 个样本; private paint(s: HistogramSnapshot): void { if (!this.ready) { return; } this.ctx.clearRect(0, 0, 320, 180); if (s.total 0) { this.ctx.fillStyle #60788A; this.ctx.fillText(暂无有效样本, 108, 92); return; } const maxCount Math.max(1, ...s.bins); s.bins.forEach((count: number, index: number) { const height Math.round(count / maxCount * 112); this.ctx.fillStyle #0D978F; if (height 0) { this.ctx.fillRect(34 57 * index, 148 - height, 30, height); } }); } build() { Column({ space: 14 }) { Text(CloudHistogram · CH-1707).fontSize(20) Text(this.statusText).fontSize(15) Canvas(this.ctx).width(320).height(180) .backgroundColor(#F3F8FA) .onReady(() { this.ready true; this.paint(this.current); }) }.padding(16).width(100%) } }示例把画布坐标与组件尺寸都设为 320×180避免只扩大组件外框、绘图坐标却仍按旧尺寸计算。真实项目还需要根据布局实测结果重新映射绘图区域尤其折叠屏展开、分屏或字体缩放后不能直接照搬固定数值。我没有在这里声称clearRect会释放所有图形资源它只清除指定区域的绘制内容。示例不创建ImageBitmap也没有额外图片解码句柄需要释放一旦加入离屏画布或外部像素对象生命周期还得另行管理。比例尺我用当前桶的最大计数归一化而不是用总数。这样五个桶中最大值 4 的柱高固定为 112其余高度由比例决定。这个选择适合观察“本批内部结构”却不适合比较不同批次的绝对规模。所以在产品页面上必须同时展示总数 10不能只让高柱看起来一样高。真正跨批对比需要固定刻度上界或并排对比方案这次不做以免模糊这篇的边界。四、迟到版本不能重画旧统计只把画布清干净还不够。假如 v4 已经进入页面v3 的异步统计回调晚一点才回来即便它也执行clearRect仍会把新图洗成旧图。所以第三段代码补的是事件门禁验证同一个任务版本号必须严格更大然后才更新快照、文案和绘图。截图展示的是 v3 的静态示意态下面的 v4 空样本与旧版回执只是验收输入不代表我真的接过远端消息。// 以下方法放入 HistogramPage 中与上段的 current/paint 配合。 private accept(next: HistogramSnapshot): void { if (next.taskId ! this.current.taskId) { return; } if (next.version this.current.version) { return; } this.current createSnapshot(next.taskId, next.version, next.bins); this.statusText v${this.current.version} · ${this.current.total} 个样本; this.paint(this.current); } // 可复现的人工输入 // accept(createSnapshot(CH-1707, 4, [0, 0, 0, 0, 0])); // accept(createSnapshot(CH-1707, 3, [2, 4, 1, 0, 3])); // 被拒绝这里的资源边界也有一个小细节onReady只处理画布准备好以后首次绘制如果数据比画布先到则accept可以先更新内存快照待onReady再绘制最新一份。反过来画布已就绪后数据到达则立即清屏重画。这样避免了为了等待 Canvas 而额外引入计时器轮询也不用担心忘记清除轮询任务。不过如果页面退出后仍有业务订阅回调就必须在上层解除订阅或者使回调失效本文没有伪造某个具体订阅 API。异常路径同样需要明说。如果输入分桶长度不是 5createSnapshot会抛错。生产应用应在接入层捕获异常记录批次标识和错误类别展示“统计数据不可用”而不是继续把半截图留在页面。若希望错误态也立即清空旧图应提供统一的renderError清屏入口不能简单return。这种分支可以在后续工程扩展但此处选择把非法数据拒绝在入画布之前便于读者分清数据校验与 Canvas 自身的职责。五、用验收清单而不是一张漂亮图收尾我给这套示例列了四条可操作检查。第一v3 数据显示五个固定槽位第四槽为零但第五槽仍在最右第二把 v4 数据全部改成零应先清除旧柱再显示空态不能残留任何 v3 的高度第三随后送进迟到的 v3不应改变状态第四把画布挂载前送来的有效新批次暂存起来挂载后只绘制最新快照。四条各自检查不同层面的缺陷单看初始页面是发现不了的。这些是建议的验收步骤不是已经完成的设备测试记录。本素材包中的白色 DevEco 开发界面、右侧模拟器和底部 HiLog 都是示意排版日志是为讲解状态路径而编排的演示文字纯手机图也不是设备截屏。代码展示了可以移植到 ArkUI 工程的核心思路但并未声称在特定 HarmonyOS SDK 版本上编译通过。涉及布局单位、画布重建时机和状态驱动的细节仍应在目标设备和 API 级别下验证。对我来说这个小改动最有价值的地方不是画出了五根柱子而是把业务快照、绘图动作和批次门禁拆开。统计数据由业务层负责可信Canvas 只负责把一帧画对异步回执要先过版本检查。将来即使把五个桶换成更多质量等级或者把柱形图替换成折线真正应该沿用的也是这三个边界而不是某一行fillRect的颜色参数。官方核对资料华为 Canvas 组件参考华为 CanvasGradient 与 Canvas 绘制示例。资料用于核对组件、上下文和绘制方法本例的批次版本、桶算法与错误状态均是应用自定义逻辑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询