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 绑住。
必要条件|故障前置排查
| 前置故障 | 检查位置 | 处理动作 |
|---|---|---|
| SDK/API缺失 | DevEco Studio SDK Manager、build-profile.json5 |
安装 API 24 并重新同步工程 |
| Kit未引入或版本不匹配 | 当前页面 import 和编译错误 | 按 API 24 文档修正对应 Kit/API |
| 页面或模块未配置 | main_pages.json、module.json5 |
登记页面并补齐 Stage/权限配置 |
| 权限被拒绝 | 系统应用权限、abilityAccessCtrl 返回值 |
重新授权;拒绝时保持未运行状态 |
| 系统能力不可用 | Camera、MapKit、SpeechKit、VisionKit 或文件运行状态 | 切换支持设备或走页面声明的降级路径 |
排查图片固定覆盖 SDK 配置、设备授权和运行版本;复杂技术再增加权限、能力和回调状态图。01要能看到 API 24/构建工具,02要能看到真实授权状态,03要能看到设备版本和能力状态。
SDK/API 故障排查:


权限故障排查后:

MapKit文章在地图服务故障排查后补充 AppGallery Connect 检查:项目、包名、签名、MapKit 服务开通状态和应用服务凭据/授权配置必须一致;不要用含密钥的控制台截图。 


更多推荐

所有评论(0)