欢迎光临
我们一直在努力

纹渊 HarmonyOS 7 工程实战(02):Canvas 曲面纹样渲染与重绘控制

一、问题与目标

纹渊的纹样预览不是把一张图机械放到页面上。用户选中纹样和载体后,页面需要尽快给出“它贴到杯子上大概是什么观感”的反馈;如果只是矩形平铺,杯身边缘会显得很硬,图案也缺少随曲面收窄的视觉变化。

Canvas 在这里承担轻量预览层。它不替代后面的 3D 预览,但要在用户切换纹样、切换载体、调整窗口尺寸、回到页面继续编辑时保持稳定。真正难处理的不是画一张图,而是绘制时机和状态版本:旧图片加载慢、窗口尺寸变化快、用户连续点击时,任何一次旧绘制都可能覆盖新状态。

HarmonyOS ArkUI 的 Canvas 组件可以直接承载这种自定义绘制,API 行为可对照 Canvas 组件参考。

运行界面

二、实现思路

实现时把预览拆成四层更稳:状态层保存当前纹样、载体和画布尺寸;资源层负责把纹样图转成可绘制对象;映射层把杯体区域拆成竖向 band;调度层只允许最新状态触发最终绘制。这样页面逻辑不会直接和 Canvas 指令耦合。

曲面效果不需要追求真实物理投影。创作页的即时反馈更看重速度和可恢复性,绘制算法只要能表达杯身中间宽、两侧收窄、边缘略透明,就足够帮助用户判断纹样方向和色块密度。真正精细的材质细节可以交给 3D 预览。

重绘触发点要明确:纹样变化、载体变化、画布尺寸变化、预览模式切换会刷新 Canvas;普通页面 build 或无关状态变化不应该重复创建图片资源。平板和 2in1 上经常发生窗口宽度变化,如果没有尺寸快照,Canvas 容易出现旧尺寸拉伸。

绘制环节处理方式
资源准备 把纹样图片、默认底图和空状态统一成可绘制对象
曲面切片 按横向位置拆分 band,用宽度和透明度模拟杯体弧度
重绘控制 只在纹样、载体或尺寸变化时触发 Canvas 刷新
结果输出 预览图可以继续交给导出、分享或 3D 材质链路
风险点防护方式
图片未解码 先进入 loading 状态,解码成功后再绘制
连续切换纹样 每次绘制携带版本号,只接受最新版本
窗口尺寸变化 保存 canvasWidth/canvasHeight 快照,尺寸变化后重算 band
预览模式切换 Canvas 与 3D 使用同一份纹样结果,不各自维护临时状态

三、关键实现

第一步先把页面状态收敛成绘制请求。Canvas 不直接读取多个 UI 字段,而是接收一个稳定的 PreviewState,这样后续导出、收藏或 3D 贴图也能复用同一份纹样结果。

interface PreviewState {
patternId: string
carrierId: 'cup' | 'box' | 'scarf' | 'badge'
imagePixelMap: PixelMap
canvasWidth: number
canvasHeight: number
version: number
}

function shouldRedraw(prev: PreviewState | undefined, next: PreviewState): boolean {
if (!prev) return true
return prev.patternId !== next.patternId ||
prev.carrierId !== next.carrierId ||
prev.canvasWidth !== next.canvasWidth ||
prev.canvasHeight !== next.canvasHeight ||
prev.version !== next.version
}

第二步才进入曲面映射。下面这个片段把杯身分成多个窄条,每个窄条按横向位置计算压缩比例和透明度,最终形成类似包裹在杯面上的视觉效果。

private drawCurvedBands(ctx: CanvasRenderingContext2D): void {
const bandCount = 21
const bandWidth = this.canvasWidth / bandCount
for (let i = 0; i < bandCount; i++) {
const u = this.cupWarpU(i)
const scaleX = 0.72 + Math.sin(u * Math.PI) * 0.28
const alpha = 0.42 + Math.sin(u * Math.PI) * 0.5
ctx.save()
ctx.globalAlpha = alpha
ctx.translate(i * bandWidth + bandWidth / 2, 0)
ctx.scale(scaleX, 1)
ctx.drawImage(this.patternImage, -bandWidth / 2, 0, bandWidth, this.canvasHeight)
ctx.restore()
}
}

第三步处理异步竞争。图片解码、窗口变化和用户点击可能交错发生,绘制任务必须带上版本号;只有版本仍然匹配当前状态时,才允许写回 Canvas。

private async scheduleDraw(next: PreviewState): Promise<void> {
this.pendingVersion = next.version
const bitmap = await this.decodePattern(next.imagePixelMap)
if (this.pendingVersion !== next.version) {
return
}

const ctx = this.canvasContext
ctx.clearRect(0, 0, next.canvasWidth, next.canvasHeight)
this.drawCarrierBase(ctx, next.carrierId)
this.drawCurvedPattern(ctx, bitmap, next)
this.lastRenderedVersion = next.version
}

最后把尺寸变化接入同一条绘制入口。Canvas 的宽高不应该散落在组件各处更新,统一由 updateCanvasSize 推进版本号,后续所有重绘都能走同一套防抖和版本检查。

private updateCanvasSize(width: number, height: number): void {
if (width <= 0 || height <= 0) {
return
}
if (this.previewState?.canvasWidth === width &&
this.previewState?.canvasHeight === height) {
return
}

this.previewState = {
…this.previewState,
canvasWidth: width,
canvasHeight: height,
version: Date.now()
} as PreviewState
this.requestPreviewRedraw()
}

private requestPreviewRedraw(): void {
if (!this.previewState || this.redrawLocked) {
return
}
this.redrawLocked = true
Promise.resolve()
.then(() => this.scheduleDraw(this.previewState as PreviewState))
.finally(() => {
this.redrawLocked = false
})
}

这套逻辑的好处是计算成本低,不需要加载 3D 场景也能给出接近载体形状的反馈。对于创作页来说,用户最先需要判断的是纹样方向、密度和色块关系是否合适,Canvas 预览足够承担即时反馈。

需要注意的是,Canvas 绘制结果必须和页面状态绑定,而不是和某一次点击绑定。用户连续切换纹样时,旧绘制任务应该被新状态覆盖;否则看似只是慢半拍,实际会让后续导出或 3D 贴图拿到错误纹样。

四、失败场景与取舍

Canvas 预览最容易出现三类问题:资源尚未加载就开始绘制,导致空白;窗口尺寸变化后没有重新计算 band,导致变形;用户快速切换纹样时旧结果覆盖新结果,导致选中态和预览不一致。前两类属于资源和尺寸问题,第三类属于状态版本问题,不能只靠页面刷新解决。

取舍上,创作页不需要在 Canvas 层追求真实物理投影,而要优先保证反馈速度和状态一致。精细材质效果交给 3D 预览,Canvas 负责低成本确认图案方向和整体观感,两层职责分开后,问题会更容易定位。

还有一个容易忽视的点是导出链路。Canvas 预览如果只是临时画面,导出时可能重新生成另一份图,导致用户看到的预览和导出的纹样不一致。更稳的做法是让绘制结果进入统一的预览状态:页面展示、3D 贴图和导出分享都从同一个结果对象读取。

五、验证步骤

  • 选择龙纹和陶瓷杯,观察杯身中间区域是否保持完整纹样,两侧是否有收窄和透明过渡。
  • 连续切换两个纹样,确认最后一次选择才会写入 Canvas,旧图片不会覆盖新结果。
  • 在平板宽屏和手机窄屏之间切换窗口,确认 band 宽度会重新计算,图案不会被横向拉伸。
  • 切换三维预览开关,确认 Canvas 结果仍能作为二维降级预览保留。
  • 执行导出或分享前,确认导出的纹样结果与页面中看到的 Canvas 预览一致。
  • 六、总结

    Canvas 曲面预览的重点是快速、稳定、可恢复。只要资源准备、曲面切片和重绘触发点清楚,创作页就能在不依赖完整 3D 场景的情况下给用户可靠反馈。

    赞(0)
    未经允许不得转载:171主机测评 » 纹渊 HarmonyOS 7 工程实战(02):Canvas 曲面纹样渲染与重绘控制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址