当前位置: 首页 > news >正文

开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘

 文章的目的为了记录使用Arkts 进行Harmony app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 相关链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客

开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客

开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客

开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客

开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客

开源 Arkts 鸿蒙应用 开发(十五)自定义画布控件--仪表盘-CSDN博客

开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客

 推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

本章内容主要演示了如何使自定义控件,通过画布实现一个模拟车速表的应用。

1.工程结构

2.源码解析

3.图片资源

4.演示效果

5.工程下载网址

一、工程结构如下图,需要注意的是除了CanvasCom.ets文件为自定义控件以外,还需要用到2张图片,使用analog_clock_bg.png作为表盘背景,使用analog_clock_second_hand.png作为指针,可以很方便的自行修改表盘和指针。

二、源码解析

2.1  index.ets
这是主页面组件,主要功能是:

提供一个加速按钮来控制速度,显示当前速度,嵌入CanvasCom组件来显示车速表

关键点:

使用@State装饰器管理当前速度(currentSpeed)和整型速度(intspeed)

点击"加速"按钮时,速度增加10,超过240后归零

将速度传递给CanvasCom组件作为属性

以下为代码

import { CanvasCom } from './CanvasCom'
import { router } from '@kit.ArkUI'const  btnStep = 10;@Entry
@Component
struct Index {@State currentSpeed: string = '0' // 添加状态管理speed@State intspeed:number = 0;build() {Column() {// 速度控制按钮组Row() {Button('加速').onClick(() => {this.intspeed = this.intspeed + btnStep;if(this.intspeed>240)this.intspeed=0;this.currentSpeed = this.intspeed.toString();}).margin(10)Text(`当前速度: ${this.currentSpeed}`).fontSize(20).margin(10)}.margin({ top: 20 })// 或者直接嵌入使用CanvasCom({desc: '车速表',title: '时钟示例',speed: this.currentSpeed})}.width('100%').height('100%')}}

2.2  CanvasCom.ets
这是核心组件,实现了一个模拟车速表的功能,主要特点:

组件结构
接收desc、title和speed作为输入,使用@Watch装饰器监听_speed变化,触发表针移动,使用Canvas绘制车速表界面


表盘绘制:使用analog_clock_bg.png作为表盘背景,使用analog_clock_second_hand.png作为指针

速度映射:将速度值(0-240)映射到表盘角度(225°-45°),使用watchStart(225)和watchProp(180/160=1.125)进行线性映射

动画效果:

使用TimeChangeListener每200ms刷新一次界面

在timeChanged()方法中根据当前速度重绘指针位置

以下为代码

import { router } from '@kit.ArkUI';import { TimeChangeListener } from './TimeChangeListener';
import { BusinessError } from '@kit.BasicServicesKit';
import image from '@ohos.multimedia.image';// 常量定义
const ANGLE_PRE_SECOND = 1;
const CANVAS_SIZE = 250;
const CANVAS_ASPACTRADIO = 1;
const IMAGE_WIDTH = 10*3;
// 时钟图片名称
const CLOCK_BG_PATH = 'analog_clock_bg.png';
const CLOCK_SECOND_PATH = 'analog_clock_second_hand.png';const  watchStart = 225;
const  watchProp = 180/(200-40);@Entry
@Component
export  struct CanvasCom {@State desc: string = '';@State title: string = '';@Prop  speed:string ='0';// 2. 内部真正驱动绘制的状态@State @Watch('onSpeedChange') _speed: number =0;// 主要代码区域@State time: string = '';private settings: RenderingContextSettings = new RenderingContextSettings(true);private renderContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);private canvasSize: number = CANVAS_SIZE;private clockRadius: number = this.canvasSize / 2;private resourceDir: string = getContext(this).resourceDir;private clockPixelMap: image.PixelMap | null = null;private secondPixelMap: image.PixelMap | null = null;private timeListener: TimeChangeListener | null = null;onSpeedChange() {// 把 speed 映射到秒针角度//const second = this._speed ;const second = watchStart+ this._speed *  watchProp;this.timeChanged(second);}onPageShow(): void {const params = router.getParams() as Record<string, string>;if (params) {this.desc = params.desc as string;this.title = params.value as string;// 移除 this.speed = params.value as string}}aboutToAppear(): void {//this._speed = Number(this.speed) || 245;this.init();}aboutToDisappear(): void {if (this.timeListener) {this.timeListener.clearInterval();}}// 生命周期:父组件传值变化aboutToUpdate(): void {}build() {Column() {// 顶部描述文本Text(this.desc).fontSize(30).fontWeight(FontWeight.Bold).margin(20)// 中间 CanvasCanvas(this.renderContext).width(this.canvasSize).aspectRatio(CANVAS_ASPACTRADIO).onReady(() => {this.paintTask();})// 底部时间文本Text(`当前速度: ${this.speed}`).fontSize(20).margin(20)}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}/*** 初始化表盘和表针对应的变量,并首次绘制。*/private init() {const clockBgSource = image.createImageSource(this.resourceDir + '/' + CLOCK_BG_PATH);const secondSource = image.createImageSource(this.resourceDir + '/' + CLOCK_SECOND_PATH);// 创建表盘对应的PixelMap并绘制。let paintDial = clockBgSource.createPixelMap().then((pixelMap: image.PixelMap) => {this.clockPixelMap = pixelMap;this.paintDial();}).catch((err: BusinessError) => {console.log('打印错误信息')});// 创建秒针对应的PixelMap并绘制。secondSource.createPixelMap().then(async (pixelMap: image.PixelMap) => {await paintDial;//this.paintPin(ANGLE_PRE_SECOND * 10, pixelMap);this.paintPin(ANGLE_PRE_SECOND * watchStart, pixelMap);this.secondPixelMap = pixelMap;}).catch((err: BusinessError) => {console.log('打印错误信息')});}/*** 绘制模拟时钟任务*/private paintTask() {// 1.先将绘制原点转到画布中央this.renderContext.translate(this.clockRadius, this.clockRadius);// 2.监听时间变化,每秒重新绘制一次this.timeListener = new TimeChangeListener((hour: number, minute: number, second: number) => {this.renderContext.clearRect(-this.clockRadius, -this.clockRadius, this.canvasSize, this.canvasSize);this.paintDial();//this.timeChanged(15);//const initSecond = Number(this.speed) || 0;const initSecond = watchStart + watchProp * Number(this.speed);//this.timeChanged(initSecond % 60);this.timeChanged(initSecond );},);}/*** 时间变化回调函数*/private timeChanged(newSecond: number) {this.paintPin(ANGLE_PRE_SECOND * newSecond, this.secondPixelMap);}/*** 绘制表盘*/private paintDial() {this.renderContext.beginPath();if (this.clockPixelMap) {this.renderContext.drawImage(this.clockPixelMap,-this.clockRadius,-this.clockRadius,this.canvasSize,this.canvasSize)} else {console.log('打印错误信息')}}/*** 绘制表针*/private paintPin(degree: number, pinImgRes: image.PixelMap | null) {this.renderContext.save();const angleToRadian = Math.PI / 180;let theta = degree * angleToRadian;this.renderContext.rotate(theta);this.renderContext.beginPath();if (pinImgRes) {this.renderContext.drawImage(pinImgRes,-IMAGE_WIDTH / 2,-this.clockRadius /2,IMAGE_WIDTH,this.canvasSize / 2 );} else {console.log('打印错误信息')}this.renderContext.restore();}}

2.3  TimeChangeListener.ets
这是一个辅助类,用于定期触发回调:每200ms获取当前时间并回调,提供清理定时器的方法
以下为代码


// 回调声明
type TimeChangeCallback = (hour: number, minute: number, second: number,) => void;// 时钟刷新间隔
const REFRESH_INTERVAL = 200;export class TimeChangeListener {private onTimeChange: TimeChangeCallback;private intervalId: number = 0;constructor(onTimeChange: TimeChangeCallback) {// Store the callbacksthis.onTimeChange = onTimeChange;// Start the time checking loopthis.intervalId = setInterval(() => this.checkTime(), REFRESH_INTERVAL); // Check every second}private checkTime(): void {const now = new Date();const currentHour = now.getHours();const currentMinute = now.getMinutes();const currentSecond = now.getSeconds();// Check for second changeif (this.onTimeChange) {this.onTimeChange(currentHour, currentMinute, currentSecond);}}public clearInterval():void {clearInterval(this.intervalId);}
}

三、图片资源

analog_clock_bg.png

analog_clock_second_hand.png

四、演示效果

五、工程下载网址:https://download.csdn.net/download/ajassi2000/91681188

http://www.lryc.cn/news/621833.html

相关文章:

  • 17.3 删除购物车商品
  • 【科研绘图系列】R语言绘制多种饼图
  • 20day-人工智能-机器学习-线性回归
  • 超高车辆碰撞预警系统如何帮助提升城市立交隧道安全?
  • 【机器学习深度学习】生成式评测
  • 金融项目高可用分布式TCC-Transaction(开源框架)
  • 服装企业客户满意度调查:民安智库的市场调研赋能实践(北京市场调查)
  • 汽车行业 AI 视觉检测方案(二):守护车身密封质量
  • 指针类型:解引用与运算的关键
  • 电子电气架构 --- 探索软件定义汽车(SDV)的技术革新
  • 基于多模型的零售销售预测实战指南
  • Java 大视界 -- 基于 Java 的大数据可视化在城市交通拥堵治理与出行效率提升中的应用(398)
  • 【java】对word文件设置只读权限
  • 英文PDF翻译成中文怎么做?试试PDF翻译工具
  • Canal 技术解析与实践指南
  • ffmpeg 安装、配置与使用完全指南
  • 【python实用小脚本-187】Python一键批量改PDF文字:拖进来秒出新文件——再也不用Acrobat来回导
  • fastdds.ignore_local_endpoints 属性
  • PDF Replacer:高效便捷的PDF文档内容替换专家
  • 基于 Spring AI + Ollama + MCP Client 打造纯本地化大模型应用
  • JavaScript(JS)DOM(四)
  • 大模型微调分布式训练-大模型压缩训练(知识蒸馏)-大模型推理部署(分布式推理与量化部署)-大模型评估测试(OpenCompass)
  • MuMu模拟器Pro Mac 安卓手机平板模拟器(Mac中文)
  • 代码随想录Day51:图论(岛屿数量 深搜广搜、岛屿的最大面积)
  • 解决量化模型中的 NaN 问题:为何非量化层应选用 FP32?(41)
  • 波浪模型SWAN学习(1)——模型编译与波浪折射模拟(Test of the refraction formulation)
  • Docker安装——配置国内docker镜像源
  • flutter 跨平台编码库 protobuf 工具使用
  • RAGFlow入门
  • Trae2.0:AI 编程新时代的引领者