HarmonyOS6.1.1-Canvas:全屏签字尺寸变化后-如何避免把固定画布尺寸当成布局依据
Canvas 页面在开发机上看着顺眼,不代表上线后一直顺眼。第一次把演示页放到不同窗口尺寸上,我遇到的不是崩溃,而是一种更尴尬的现象:顶部面板被压扁,右侧比较区挤出边缘,底部放大格被挡住。最初代码用了 1280 和 720 作为初始宽高,本意是给首次渲染一个可用值;后来我才意识到,初始值不是布局依据。只要页面发生旋转、分屏、窗口拖拽或容器重新测量,Canvas 里的绝对坐标就需要重新计算。
这一篇不讨论抗锯齿效果,也不复述“状态变了要重画”的问题。我关注的是页面已经能画之后,面积变化如何不把已有布局画乱。Canvas 不像普通声明式组件那样自动替我排版;我在 fillRect、fillText 里写进去的每一个坐标都要面对新的宽高。因此上线后维护的第一件事不是问“横屏和竖屏支不支持”,而是确认新的 Area 是否已经成为这一次绘制的真实输入。
问题不是窗口变了,而是旧尺寸还在指导绘制
我曾经在一个较窄窗口里复测。视觉上 Canvas 已经铺满容器,可代码内部仍按上一轮较宽尺寸算双栏,面板宽度加上左右边距后超过实际画布。由于 Canvas 不会替我裁掉逻辑错误,结果就是边框一部分不见、文字位置显得奇怪。看上去像字体问题,根因却是布局计算的输入没有更新。
现有页面的思路是先把 onAreaChange 给出的宽高落到 canvasWidth、canvasHeight,然后再调用 renderCanvas()。有两个细节我会保留。其一,只接受大于零的宽高,避免测量暂态把合法尺寸写成零;其二,即使这次面积没有通过条件,也会执行绘制入口,让页面按当前已知有效尺寸保持一致。这个取舍不是说零尺寸有意义,而是避免把重绘逻辑分叉得太复杂。
Canvas(this.context)
.width('100%')
.height('100%')
.onAreaChange((_oldArea: Area, newArea: Area) => {
const width = Math.floor(Number(newArea.width));
const height = Math.floor(Number(newArea.height));
if (width > 0 && height > 0) {
this.canvasWidth = width;
this.canvasHeight = height;
}
this.renderCanvas();
})
.onReady(() => { this.canvasReady = true; this.renderCanvas(); });
这里 onReady 和 onAreaChange 的职责不一样。前者保证绘制上下文准备好以后至少有一次画面;后者处理后续面积变动。把它们混成“哪个先来就画哪个”,很容易在页面初始阶段拿未就绪上下文绘制,或者在尺寸变化后没有刷新。维护时我会检查这两个入口,而不是只看某一台设备是否碰巧画出了首屏。

场景解说:这是[全屏电子签名]的初始场景,保留当前输入与默认状态,便于与后续操作对照。
面积进入绘制后,布局要全部从它推导
真正避免错位的关键,不是多写几个 if,而是不要在绘制函数里偷藏旧常量。现有 renderCanvas() 先从新的高度减去安全区,再以宽度比例算边距、间距和双栏面板宽度。它还给内容高度和边距设置下限,避免小窗口把数值压到不可读的程度。这样每轮布局都来自同一组尺寸输入,面板、下方放大区和边框会一起移动。
private renderCanvas(): void {
if (!this.canvasReady) {
return;
}
const safeTop = 104;
const safeBottom = 128;
const contentHeight = Math.max(360, this.canvasHeight - safeTop - safeBottom);
const margin = Math.max(36, Math.floor(this.canvasWidth * 0.035));
const gap = Math.max(16, Math.floor(this.canvasWidth * 0.014));
const topPanelHeight = Math.floor(contentHeight * 0.58);
const panelWidth = Math.floor((this.canvasWidth - margin * 2 - gap) / 2);
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 后续面板和放大区均使用这一轮计算出的坐标。
}
我以前会只盯着 panelWidth,后来发现下方区域也同样重要。lowerY、lowerHeight 必须从同一轮 contentHeight 推导,不然顶部压缩了,放大区还停在旧 y 坐标,就会发生重叠。Canvas 的布局错乱常常不是一个元素算错,而是一组相互关联的坐标来自不同时间的尺寸。
这张流程图里,clearRect 不是装饰。若只按新尺寸再画一次,旧窗口更大时留下的像素可能仍在画布边缘;用户会把残影误以为布局错误。清空范围也必须使用当前画布尺寸。这里没有宣称系统会在任意窗口形态下给出一样的排版,它只是保证这个 Demo 按当前可用面积重新计算并绘制。

场景解说:执行[清空]后,页面展示当前交互与状态反馈;该图对应本节讨论的操作边界。
我上线后采用的尺寸复测方式
我会先把测试分成三组:首次进入、变大、变小。首次进入验证 onReady 和首个有效面积协作;变大验证原先的元素不会漂在左上角;变小验证文本、双栏和放大区不会互相盖住。每一组都保留同一组样本和参数,避免字号变化掩盖布局变化。
对于变小场景,我不把“所有内容都完整展示”写成不切实际的目标。当前代码用最小内容高度与最小边距维持基础可读性,但极端窄小面积下仍需要产品决定是缩放、滚动、折叠比较区,还是限制使用形态。现有页面只是一个实验页,维护记录必须如实写下这个边界。
复测时我还会看两个容易漏掉的动作。第一个是连续拖拽窗口,不只测一次跳变。这样可以观察面积事件快速到来时,页面是否始终使用最近尺寸。第二个是先切换参数、再改尺寸,确认快照面板和当前面板都跟随新的布局规则;不能只让“当前输出”适配,而把上次快照留在旧坐标。这个页面两块面板的比较价值很高,任何一块错位都会让维护判断失真。

场景解说:继续执行[确认]后的实际结果,保留页面返回、异常或人工处理状态,不把演示状态解释为外部能力已完成。
给后来维护者的检查清单
我会在交接记录里写四个问题。每一次绘制是否都只从 canvasWidth、canvasHeight 推导坐标?安全区常量是否与外层覆盖控件的实际高度保持同步?发生面积变化时是否完整清空旧画面?极小面积的产品策略是否被明确,而不是依赖偶然的裁剪?这些问题听着像排版细节,但它们决定了同一个功能在运行环境变化后还能不能被人正常使用。
也别把 Math.floor 当成无关紧要的处理。Canvas 坐标带小数时,边框和文字在不同缩放下可能出现难解释的轻微模糊。现有页面将面积和若干尺寸取整,是为了让计算路径更稳定。它不保证所有图形都绝对锐利,却减少了“同一个尺寸计算出半像素坐标”的不确定性。若后续需要高精度绘图,应单独评估设备像素比和渲染策略,不应在这里凭感觉加补丁。
下一版可以优化什么
以下内容是设计设想,不是现有页面能力。下一版可以把双栏布局抽成明确的宽度阈值策略:宽度不足时改为上下排列,并在页面上提示当前比较模式;还可以记录最近一次有效面积,方便维护人员复现问题窗口。若要支持更复杂的分屏场景,也可以增加专门的尺寸测试面板,把安全区、实际画布尺寸和每个区域的计算值显示出来。它们需要新的产品决定和实现工作,不能当作当前代码已经提供的保证。
我现在复盘这次问题,会把“画布铺满了容器”与“内容按容器正确重排”分开看。前者只是组件尺寸,后者才是绘制逻辑。只要所有坐标都回到当前面积重新计算,Canvas 的维护就不会被第一次调好的 1280×720 绑住。
附录
A. 开发环境要求

| 项目 | 要求 |
|---|---|
| 开发工具 | 安装能够使用 HarmonyOS 6.1.1 SDK 的 DevEco Studio |
| SDK | HarmonyOS 6.1.1,API 24 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI,Stage 模型 |
| 构建工具 | 项目自带 hvigorw.bat;本项目使用过 DevEco Studio Hvigor 6.24.4 |
| 操作系统 | 当前工程可在 Windows 环境中使用 PowerShell 或 DevEco Studio 构建 |
| 依赖 | 以项目根目录和 entry 模块的 oh-package.json5 为准;当前工程未声明第三方依赖 |
项目的 SDK 配置位于根目录 build-profile.json5,三个版本字段保持一致:
{
"compileSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "6.1.1(24)",
"targetSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS"
}
若本机没有安装 API 24 SDK,HarmonyOS 6.1.1 新增接口可能在类型检查或编译阶段不可用。此时应先通过 DevEco Studio 的 SDK Manager 安装对应 SDK,再重新同步工程。
B. 工程配置要求
页面源码位于:
entry/src/main/ets/pages/batch05/InventoryLabelCanvasPage.ets
页面需要登记在 entry/src/main/resources/base/profile/main_pages.json 中:
"pages/batch05/InventoryLabelCanvasPage"
entry 模块使用 Stage 模型,目标设备类型包含 phone、tablet 和 2in1。运行具体页面前,应根据源码涉及的系统能力核对权限、服务配置和输入资源;纯 UI 页面不需要额外申请与功能无关的权限。
在 sourceproject 目录执行以下命令可以构建 HAP:
.\hvigorw.bat --mode module -p module=entry@default -p product=default assembleHap --no-daemon
预期结果是 Hvigor 输出 BUILD SUCCESSFUL。若使用 DevEco Studio,也可以选择 entry 模块和 default product 后直接构建或运行。连接真机时还需要配置可用的调试签名;仅执行本地未签名构建时,不等同于已经可以安装到真实设备。
C. 测试环境要求

逻辑测试建议先使用 HarmonyOS 6.1.1 API 24 模拟器。测试前固定输入样本、初始状态和操作顺序,每轮只改变一个核心变量;涉及外部服务或硬件能力时,还需准备对应权限、认证、网络或测试资源。
测试时建议固定以下基线:
| 参数 | 基线值 |
|---|---|
| 系统镜像 | HarmonyOS 6.1.1 API 24 |
| 初始状态 | 页面默认状态,操作前重新进入或执行重置 |
| 测试输入 | 使用文章约定的固定样本和固定参数 |
| 主操作 | 每轮只执行文章描述的一项核心操作 |
| 观察内容 | 输入、当前状态、结果区域、异常提示及必要回调 |
基线流程至少重复两轮,确认页面能够从相同起点得到稳定状态。尺寸适配测试至少覆盖横屏、竖屏或一次窗口缩放,检查主要内容、状态区域和操作控件没有互相遮挡。
D. 运行环境要求
模拟器用于检查页面启动、基础交互、状态更新、异常分支和布局适配。运行镜像应为 HarmonyOS 6.1.1 API 24,并确保目标页面已注册且能够从应用入口正常打开。
真机用于检查真实触摸、屏幕显示、性能以及模拟器无法完整提供的硬件或系统能力。设备系统需要满足工程的 compatibleSdkVersion,即 HarmonyOS 6.1.1 API 24;同时开启开发者模式和调试连接,并使用有效签名安装应用。
若页面涉及相机、麦克风、地图、网络服务、媒体文件或授权样本,应在运行前准备相应权限、认证信息和合法测试数据;不涉及外部能力的页面可直接在模拟器中完成基础交互测试。模拟器与真机使用相同的固定输入和操作顺序,便于比较两类环境中的差异。
固定输入和操作顺序,便于比较两类环境中的差异。
更多推荐



所有评论(0)