鸿蒙Next使用Canvas绘制一个汽车仪表盘
本文通过实现一个汽车仪表盘,介绍使用CanvasRenderingContext2D在Canvas组件上进行绘制图形。 看一下最终演示:
先了解一下CanvasRenderingContext2D都有哪些属性和方法 |属性名| 说明| |-----|-----| |fillStyle| 指定绘制的填充色。| |lineWidth |设置绘制线条的宽度。| |strokeStyle |设置线条的颜色。| |lineCap |指定线端点的样式。| |lineJoin |指定线段间相交的交点样式。| |miterLimit |设置斜接面限制值。| |font |设置文本绘制中的字体样式。| |textAlign| 设置文本绘制中的文本对齐方式。| |textBaseline |设置文本绘制中的水平对齐方式。| |globalAlpha |设置透明度。| |lineDashOffset| 设置画布的虚线偏移量。| |globalCompositeOperation| 设置合成操作的方式。| |shadowBlur| 设置绘制阴影时的模糊级别。| |shadowColor |设置绘制阴影时的阴影颜色。| |shadowOffsetX| 设置绘制阴影时和原有对象的水平偏移值。| |shadowOffsetY |设置绘制阴影时和原有对象的垂直偏移值。| |imageSmoothingEnabled |用于设置绘制图片时是否进行图像平滑度调整。| |height| 组件高度。| |width |组件宽度。| |imageSmoothingQuality |imageSmoothingEnabled 为 true 时,用于设置图像平滑度。| |direction| 用于设置绘制文字时使用的文字方向。| |filter |用于设置图像的滤镜。| |canvas| 获取和 CanvasRenderingContext2D 关联的 Canvas 组件的 FrameNode 实例。| 本文使用的几个方法以放到前面,可以参考说明。
方法名 | 说明 |
---|---|
beginPath | 创建一个新的绘制路径 |
arc | 绘制弧线路径 |
stroke | 根据当前的路径,进行边框绘制操作 |
save | 将当前状态放入栈中,保存canvas的全部状态,通常在需要保存绘制状态时调用 |
translate | 移动当前坐标系的原点 |
rotate | 针对当前坐标轴进行顺时针旋转 |
restore | 对保存的绘图上下文进行恢复 |
fillText | 绘制填充类文本 |
fillRect | 填充一个矩形 |
strokeRect | 绘制具有边框的矩形,矩形内部不填充 |
clearRect | 删除指定区域内的绘制内容 |
strokeText | 绘制描边类文本 |
measureText | 返回一个文本测算的对象 |
moveTo | 路径从当前点移动到指定点 |
lineTo | 从当前点到指定点进行路径连接 |
closePath | 结束当前路径形成一个封闭路径 |
quadraticCurveTo | 创建二次贝赛尔曲线的路径 |
arcTo | 依据给定的控制点和圆弧半径创建圆弧路径 |
ellipse | 在规定的矩形区域绘制一个椭圆 |
rect | 创建矩形路径 |
fill | 对当前路径进行填充 |
clip | 设置当前路径为剪切路径 |
reset | 重置为其默认状态,清除后台缓冲区、绘制状态栈、绘制路径和样式 |
saveLayer | 创建一个图层 |
scale | 设置canvas画布的缩放变换属性,后续的绘制操作将按照缩放比例进行缩放 |
drawImage | 进行图像绘制 |
createLinearGradient | 创建一个线性渐变色 |
源码:
import { getScreenWidth } from '../utils/DisplayUtil';
@Entry
@ComponentV2
struct CanvasTest {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private context2: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private context3: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
@Local centerX: number = 0
@Local centerY: number = 0
@Local currentSpeed: number = 0
@Local angle: number = 0
@Local startAngle:number= Math.PI * 140 / 180 //弧线的起始弧度
@Local endAngle:number= Math.PI * 40 / 180 //弧线的起始弧度
//绘制渐变外环
draw(){
this.context3.clearRect(0,0,2*this.centerX,2*this.centerX)
this.context3.beginPath()
let grad = this.context3.createLinearGradient(this.getStartX(), this.getStartY(), this.getEndX(), this.getEndY())
grad.addColorStop(0, '#0000ff00')
grad.addColorStop(0.9, '#00ff00')
if (this.currentSpeed>120) {
grad.addColorStop(1, '#ff0000')
}else {
grad.addColorStop(1, '#00ff00')
}
this.context3.strokeStyle = grad;
this.context3.lineWidth = 4;
this.context3.arc(this.centerX, this.centerY, this.centerX - 4, this.startAngle, Math.PI * (140+(this.currentSpeed * 260 / 180) ) / 180)
this.context3.stroke()
}
build() {
Column() {
Stack() {
Canvas(this.context)
.width('100%')
.height('100%')
.backgroundColor("#fceb99")
.onReady(() => {
//绘制外环
this.context.beginPath()
this.context.strokeStyle = '#0000ff';
this.context.lineWidth = 2;
this.context.arc(this.centerX, this.centerY, this.centerX - 10, this.startAngle, this.endAngle)
this.context.stroke()
//绘制内环
this.context.beginPath()
this.context.strokeStyle = '#07A6EC';
this.context.lineWidth = 8;
this.context.arc(this.centerX, this.centerY, this.centerX - 16, this.startAngle, this.endAngle)
this.context.stroke()
//绘制刻度
this.context.save()
this.context.translate(this.centerX, this.centerY);
this.context.rotate(Math.PI * 50 / 180)
for (let i = 0; i <= 36; i++) {
if (i % 4 == 0) {
this.context.beginPath()
this.context.lineWidth = 4;
this.context.strokeStyle = '#07A6EC';
this.context.moveTo(0, this.centerY - 34);
this.context.lineTo(0, this.centerY - 12);
this.context.stroke();
} else {
this.context.beginPath()
this.context.lineWidth = 2;
this.context.strokeStyle = '#07A6EC';
this.context.moveTo(0, this.centerY - 26);
this.context.lineTo(0, this.centerY - 12);
this.context.stroke();
}
this.context.rotate(Math.PI * (260 / 36) / 180)
}
this.context.restore()
//绘制数字
this.context.save()
this.context.translate(this.centerX, this.centerY);
for (let i = 0; i < 10; i++) {
// 转换为弧度
const radians = (140 + i * (260 / 9)) * Math.PI / 180;
// 计算坐标
const x = (this.centerY - 60) * Math.cos(radians);
const y = (this.centerY - 60) * Math.sin(radians);
this.context.textAlign = 'center'
this.context.textBaseline = 'middle'
this.context.font = '30vp sans-serif'
this.context.fillText(i * 20 + '', x, y)
}
//绘制中间单位
this.context.textAlign = 'center'
this.context.textBaseline = 'middle'
this.context.font = '30vp sans-serif'
this.context.fillText('km/h', 0, -40)
this.context.restore()
//绘制红色圆环
this.context.translate(0, 0);
this.context.beginPath()
this.context.strokeStyle = Color.Red;
this.context.lineWidth = 3;
this.context.arc(this.centerX, this.centerY, this.centerX - 100, this.startAngle, this.endAngle)
this.context.stroke()
})
Canvas(this.context3)
.width('100%')
.height('100%')
Canvas(this.context2)
.width('100%')
.height('100%')
.onReady(() => {
this.context2.beginPath()
this.context2.lineWidth = 3;
this.context2.strokeStyle = "#95ff00";
this.context2.moveTo(this.centerX, this.centerY)
this.context2.lineTo(this.centerX, 14)
this.context2.stroke()
})
.rotate({ centerX: this.centerX, centerY: this.centerY, angle: this.cacleAngle() })
Text(this.currentSpeed + '').fontColor(Color.Black).fontSize(30).offset({ y: 20 })
}.height('50%').width('100%')
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
this.centerX = (newValue.width as number) / 2
this.centerY = (newValue.height as number) / 2
})
Slider({
value: this.currentSpeed,
min: 0,
max: 180,
step: 1,
})
.width('80%')
.blockColor("#0099ff")//设置滑块的颜色
.onChange((value: number, mode: SliderChangeMode) => {
this.currentSpeed = value
})
}
}
cacleAngle(): number {
this.draw();
// (当前速度/最大速度180)*圆环角度260- 默认其实位置从中心位置向左旋转130
return (this.currentSpeed * 260 / 180) - 130
}
getStartX() {
const angleRadians = 140 * Math.PI / 180;
return this.centerX + this.centerX * Math.cos(angleRadians);
}
getStartY(): number {
const angleRadians = 140 * Math.PI / 180;
return this.centerX + this.centerX * Math.sin(angleRadians);
}
getEndX() {
const angleRadians = (140+(this.currentSpeed * 260 / 180) )* Math.PI / 180;
return this.centerX + this.centerX * Math.cos(angleRadians);
}
getEndY(): number {
const angleRadians = (140+(this.currentSpeed * 260 / 180))* Math.PI / 180;
return this.centerX + this.centerX * Math.sin(angleRadians);
}
}
- 0回答
- 0粉丝
- 0关注
- 02 HarmonyOS Next仪表盘案例详解(一):基础篇
- 03 HarmonyOS Next仪表盘案例详解(二):进阶篇
- 鸿蒙开发:简单自定义一个绘制画板
- [HarmonyOS NEXT 实战案例十二] 健康数据仪表盘网格布局(上)
- [HarmonyOS NEXT 实战案例十二] 健康数据仪表盘网格布局(下)
- 鸿蒙开发:了解Canvas绘制
- [HarmonyOS NEXT 实战案例:健康应用] 基础篇 - 水平分割布局打造健康数据仪表盘
- [HarmonyOS NEXT 实战案例:健康应用] 进阶篇 - 健康数据仪表盘的交互功能与状态管理
- 使用鸿蒙Next复刻一个上古小游戏:《是男人就坚持100秒》
- [HarmonyOS NEXT 实战案例:健康应用] 高级篇 - 健康数据仪表盘的高级布局与自适应设计
- HarmonyOS NEXT 闹钟表盘绘制方案分享
- 鸿蒙开发:Canvas绘制之画笔对象Pen
- 鸿蒙开发:Canvas绘制之画笔对象Brush
- 创建一个登录界面
- 鸿蒙开发:自定义一个Toast