鸿蒙开发-实战:用Canvas画一个仪表盘
本文介绍了如何使用Canvas绘制一个仪表盘,包括背景弧、进度弧、刻度线、指针和数值显示。通过设置基础变量、计算角度和颜色,逐步完成仪表盘的绘制。关键步骤包括:清空画布、绘制背景弧、根据当前值计算进度弧角度和颜色、绘制刻度线和指针、显示数值和单位。最终实现一个可交互的仪表盘组件,适用于速度、温度等数值的可视化展示。
实战:用 Canvas 绘制一个仪表盘
今天来做个实际的东西——用 Canvas 画一个仪表盘。仪表盘在很多 APP 里都能见到:汽车仪表、运动 APP 的速度表、智能家居的温度显示等等。
仪表盘绘制流程
下面是绘制仪表盘的完整流程:
仪表盘长什么样?
一个典型的仪表盘包括:
- 弧形刻度盘——背景弧和进度弧
- 刻度线——长短不一的线段
- 指针——指向当前值
- 中心数字——显示当前数值
我们用 Canvas 来绘制这些元素。
第一步:设置基础变量
import { common } from '@kit.AbilityKit';
import { CanvasRenderingContext2D } from '@kit.ArkGraphics2D';
导入需要的模块。
@Entry
@Component
struct Dashboard {
@State currentValue: number = 65; // 当前值
private maxValue: number = 100; // 最大值
private minValue: number = 0; // 最小值
定义仪表盘的数据:当前值 65,范围 0-100。
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(
new RenderingContextSettings(true)
);
创建 Canvas 渲染上下文。new RenderingContextSettings(true) 开启抗锯齿。
第二步:画背景弧
drawDashboard() {
const ctx = this.context;
const width = 300;
const height = 300;
const centerX = width / 2;
const centerY = height / 2;
const radius = 100;
// 清空画布
ctx.clearRect(0, 0, width, height);
先清空画布,准备重新绘制。
// 画背景弧(灰色)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI * 0.75, Math.PI * 2.25, false);
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.stroke();
arc 的参数是:圆心坐标、半径、起始角度、结束角度、是否逆时针。
- 起始角度
Math.PI * 0.75= 135 度(左下角) - 结束角度
Math.PI * 2.25= 405 度(右下角) - 总共 270 度的弧
lineCap = 'round' 让弧的两端是圆角,看起来更柔和。
第三步:画进度弧
const startAngle = Math.PI * 0.75;
const endAngle = startAngle + (this.currentValue / this.maxValue) * Math.PI * 1.5;
进度弧的结束角度 = 起始角度 + (当前值 / 最大值) × 270 度。Math.PI * 1.5 就是 270 度。
// 根据值选择颜色(绿 -> 黄 -> 红)
let color = '#4CAF50'; // 绿色
if (this.currentValue > 70) {
color = '#f44336'; // 红色
} else if (this.currentValue > 40) {
color = '#ff9800'; // 橙色
}
根据值的大小选择颜色:低于 40 绿色,40-70 橙色,70 以上红色。这样用户一眼就能看出状态。
ctx.beginPath();
ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);
ctx.strokeStyle = color;
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.stroke();
画进度弧,和背景弧一样的参数,只是颜色不同、角度范围不同。
第四步:画刻度线
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(Math.PI * 0.75); // 旋转到起始角度
save 保存当前坐标系状态,translate 把原点移到圆心,rotate 旋转到起始角度。
for (let i = 0; i <= 10; i++) {
ctx.beginPath();
if (i % 5 === 0) {
// 长刻度
ctx.moveTo(radius - 25, 0);
ctx.lineTo(radius - 35, 0);
ctx.strokeStyle = '#333333';
ctx.lineWidth = 2;
} else {
// 短刻度
ctx.moveTo(radius - 25, 0);
ctx.lineTo(radius - 30, 0);
ctx.strokeStyle = '#999999';
ctx.lineWidth = 1;
}
ctx.stroke();
画 11 个刻度(0-10)。每 5 个是长刻度,其他是短刻度。moveTo 和 lineTo 的坐标是相对于旋转后的坐标系。
// 画刻度数值
if (i % 5 === 0) {
ctx.save();
ctx.translate(radius - 45, 0);
ctx.rotate(-Math.PI * 0.75 - i * Math.PI * 0.15); // 反旋转文字
ctx.fillStyle = '#666666';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${i * 10}`, 0, 0);
ctx.restore();
}
在长刻度旁边显示数值。因为整个坐标系已经旋转了,所以文字需要反旋转才能保持水平。
ctx.rotate(Math.PI * 0.15); // 每个刻度间隔 27 度
}
ctx.restore();
每画完一个刻度,旋转 27 度(Math.PI * 0.15)。最后 restore 恢复坐标系。
第五步:画指针
const pointerAngle = startAngle + (this.currentValue / this.maxValue) * Math.PI * 1.5;
const pointerLength = radius - 40;
指针的角度和进度弧一样,长度比半径短 40 像素。
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(pointerAngle);
ctx.beginPath();
ctx.moveTo(0, -3);
ctx.lineTo(pointerLength, 0);
ctx.lineTo(0, 3);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
指针是一个三角形:从中心点向当前角度方向画一个细长的三角形。moveTo(0, -3) 和 lineTo(0, 3) 控制指针的宽度。
// 指针中心圆
ctx.beginPath();
ctx.arc(0, 0, 8, 0, Math.PI * 2);
ctx.fillStyle = '#333333';
ctx.fill();
ctx.restore();
在中心画一个小圆,盖住指针的根部。
第六步:显示数值
// 显示当前数值
ctx.fillStyle = '#333333';
ctx.font = 'bold 36px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${this.currentValue}`, centerX, centerY + 50);
在仪表盘下方显示当前数值,用大号粗体字。
// 单位
ctx.fillStyle = '#999999';
ctx.font = '14px sans-serif';
ctx.fillText('km/h', centerX, centerY + 70);
}
在数值下方显示单位。
第七步:添加交互
build() {
Column() {
Text('速度表')
.fontSize(20)
.margin({ top: 20 })
Canvas(this.context)
.width(300)
.height(300)
.onReady(() => {
this.drawDashboard();
})
Canvas 的 onReady 回调在画布准备好后触发,我们在这里开始绘制。
// 模拟数值变化
Button('加速')
.margin({ top: 20 })
.onClick(() => {
this.currentValue = Math.min(this.currentValue + 10, this.maxValue);
this.drawDashboard();
})
Button('减速')
.margin({ top: 10 })
.onClick(() => {
this.currentValue = Math.max(this.currentValue - 10, this.minValue);
this.drawDashboard();
})
}
.width('100%')
.height('100%')
}
添加两个按钮,点击后修改 currentValue 并重新绘制。
颜色变化逻辑
仪表盘的颜色会根据数值变化:
进阶改进
如果你想做得更精致,可以:
- 加渐变:进度弧用渐变色,从绿色渐变到红色
- 加动画:用
requestAnimationFrame让指针平滑过渡 - 加阴影:给指针和数字加阴影,增加立体感
- 加刻度文字:在长刻度旁边显示数值(0, 50, 100)
进阶改进方向
仪表盘可以通过以下方式进一步优化:
小结
仪表盘的绘制核心:
drawArc画弧形背景和进度save/restore+rotate画刻度线- 三角形路径画指针
drawText显示数值
Canvas 的能力远不止这些,但掌握了 arc、line、save/restore、坐标变换这几个基础,你就能画出大部分自定义 UI 了。
更多推荐


所有评论(0)