鸿蒙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 创建一个线性渐变色

源码:

kotlin 复制代码
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);
  }
}
相关推荐
lili-felicity2 小时前
React Native 鸿蒙跨平台开发:LayoutAnimation 实现鸿蒙端按钮点击的缩放反馈动画
react native·react.js·harmonyos
哈__4 小时前
React Native 鸿蒙跨平台开发:Dimensions 屏幕尺寸获取
react native·华为·harmonyos
奋斗的小青年!!4 小时前
Flutter跨平台开发适配OpenHarmony:手势识别实战应用
flutter·harmonyos·鸿蒙
搬砖的kk5 小时前
Cordova 适配鸿蒙系统(OpenHarmony) 全解析:技术方案、环境搭建与实战开发
华为·开源·harmonyos
不爱吃糖的程序媛5 小时前
OpenHarmony 通用C/C++三方库 标准化鸿蒙化适配
c语言·c++·harmonyos
程序猿追5 小时前
鸿蒙PC应用开发深度实战:一次开发、多端适配的沉浸式音乐播放器迁移实践
华为·harmonyos
行者966 小时前
Flutter跨平台开发:安全检测组件适配OpenHarmony
flutter·harmonyos·鸿蒙
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— GridView 控件之多维网格美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨7 小时前
Flutter跨平台开发实战: 鸿蒙与循环交互艺术:无限循环的 Banner 引擎
flutter·ui·华为·交互·harmonyos·鸿蒙系统
奋斗的小青年!!8 小时前
Flutter与鸿蒙深度融合:打造物理引擎驱动的3D卡片交互体验
flutter·3d·harmonyos·鸿蒙