news 2026/5/9 19:01:39

鸿蒙技术干货10:鸿蒙图形渲染基础,Canvas绘图与自定义组件实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙技术干货10:鸿蒙图形渲染基础,Canvas绘图与自定义组件实战

图形渲染是提升应用交互体验的核心技能,而 Canvas 组件作为鸿蒙图形渲染的基础载体,能实现从简单绘图到复杂自定义组件的各类需求。掌握 Canvas 绘图逻辑与自定义组件开发,能让你的应用在视觉呈现和功能扩展性上更上一层楼。本文将从 Canvas 基础绘图入手,逐步过渡到自定义组件开发,最终实现实用的「手写签名组件」。

一、核心认知:Canvas 与自定义组件的应用价值

1. 应用场景

  • 自定义可视化组件(如签名板、绘图工具、进度条)
  • 数据可视化图表(如折线图、饼图的底层绘制)
  • 交互型图形(如手写输入、涂鸦功能)
  • 个性化 UI 元素(如不规则按钮、动态图形效果)

2. 核心技术栈

  • 基础组件:Canvas(绘图容器)、CanvasRenderingContext2D(绘图上下文)
  • 自定义组件:继承Component并重写onDraw方法
  • 关键能力:路径绘制、样式设置、事件监听、图片导出

二、Canvas 组件基础:四大核心绘图能力

Canvas 绘图的核心是通过CanvasRenderingContext2D上下文对象操作,以下是线条、矩形、圆形、文字的基础绘制逻辑,代码可直接复用:

1. 初始化 Canvas 上下文

import { Canvas, CanvasRenderingContext2D } from '@ohos.ui.canvas'; @Component struct CanvasBasicDemo { private canvasContext: CanvasRenderingContext2D | null = null; // 初始化上下文 private initContext(canvas: Canvas) { this.canvasContext = canvas.getContext('2d'); if (!this.canvasContext) { console.error('Canvas 上下文初始化失败'); } } build() { Canvas(this.initContext.bind(this)) .width('100%') .height(400) .backgroundColor('#f5f5f5') } }

2. 绘制线条(手写签名基础)

// 绘制连续线条 private drawLine(startX: number, startY: number, endX: number, endY: number) { if (!this.canvasContext) return; const ctx = this.canvasContext; // 设置线条样式 ctx.beginPath(); // 开始新路径 ctx.moveTo(startX, startY); // 起点 ctx.lineTo(endX, endY); // 终点 ctx.strokeStyle = '#2f54eb'; // 线条颜色 ctx.lineWidth = 3; // 线条宽度 ctx.lineCap = 'round'; // 线条端点圆角 ctx.lineJoin = 'round'; // 线条交点圆角 ctx.stroke(); // 执行绘制 }

3. 绘制矩形与圆形

// 绘制矩形(填充+描边) private drawRect(x: number, y: number, width: number, height: number) { if (!this.canvasContext) return; const ctx = this.canvasContext; ctx.fillStyle = 'rgba(47, 84, 235, 0.2)'; // 填充颜色(半透明) ctx.fillRect(x, y, width, height); // 填充矩形 ctx.strokeStyle = '#2f54eb'; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); // 描边矩形 } // 绘制圆形(填充+描边) private drawCircle(x: number, y: number, radius: number) { if (!this.canvasContext) return; const ctx = this.canvasContext; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); // 圆心(x,y),半径radius,0到360度 ctx.fillStyle = 'rgba(255, 77, 79, 0.2)'; ctx.fill(); ctx.strokeStyle = '#ff4d4f'; ctx.lineWidth = 2; ctx.stroke(); }

4. 绘制文字

private drawText(text: string, x: number, y: number) { if (!this.canvasContext) return; const ctx = this.canvasContext; ctx.font = '20px sans-serif'; // 字体大小+字体族 ctx.fillStyle = '#333333'; // 文字颜色 ctx.textAlign = 'center'; // 水平居中 ctx.textBaseline = 'middle'; // 垂直居中 ctx.fillText(text, x, y); // 绘制文字 }

三、自定义组件开发:重写 onDraw 与事件响应

自定义组件是在 Canvas 基础上封装独立功能模块,核心是重写onDraw方法实现绘图逻辑,并绑定触摸事件实现交互:

1. 自定义组件基础结构

import { Component, Canvas, CanvasRenderingContext2D, TouchEvent } from '@ohos.ui.canvas'; // 自定义手写签名组件 @Component export struct SignaturePad extends Component { // 组件属性(外部可配置) private lineWidth: number = 3; private lineColor: string = '#2f54eb'; private bgColor: string = '#ffffff'; // 内部状态 private ctx: CanvasRenderingContext2D | null = null; private isDrawing: boolean = false; private lastX: number = 0; private lastY: number = 0; // 重写onDraw方法(组件绘制入口) override onDraw(canvas: Canvas) { this.ctx = canvas.getContext('2d'); if (!this.ctx) return; // 绘制背景 const { width, height } = canvas.getBoundingClientRect(); this.ctx.fillStyle = this.bgColor; this.ctx.fillRect(0, 0, width, height); } // 触摸事件响应(核心交互) private handleTouchStart(e: TouchEvent) { this.isDrawing = true; // 获取触摸起始坐标 const { x, y } = e.touches[0]; this.lastX = x; this.lastY = y; } private handleTouchMove(e: TouchEvent) { if (!this.isDrawing || !this.ctx) return; const { x, y } = e.touches[0]; // 绘制当前线段(从上次坐标到当前坐标) this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(x, y); this.ctx.strokeStyle = this.lineColor; this.ctx.lineWidth = this.lineWidth; this.ctx.lineCap = 'round'; this.ctx.lineJoin = 'round'; this.ctx.stroke(); // 更新上次坐标 this.lastX = x; this.lastY = y; } private handleTouchEnd() { this.isDrawing = false; } build() { Canvas(this.onDraw.bind(this)) .width('100%') .height(300) .onTouchStart(this.handleTouchStart.bind(this)) .onTouchMove(this.handleTouchMove.bind(this)) .onTouchEnd(this.handleTouchEnd.bind(this)) } }

2. 扩展功能:笔触配置与图片保存

给自定义组件添加笔触粗细切换、颜色选择、清空、保存图片功能:

// 续上SignaturePad组件代码 export struct SignaturePad extends Component { // 新增属性与状态 @Link lineWidth: number; // 双向绑定外部粗细配置 @Link lineColor: string; // 双向绑定外部颜色配置 private canvasRef: Canvas | null = null; // 清空画布 public clear() { if (!this.ctx) return; const { width, height } = this.canvasRef!.getBoundingClientRect(); this.ctx.clearRect(0, 0, width, height); // 清空整个画布 this.ctx.fillStyle = this.bgColor; this.ctx.fillRect(0, 0, width, height); // 重新绘制背景 } // 保存签名为图片(base64格式) public async saveAsImage(): Promise<string | null> { if (!this.canvasRef) return null; try { // 导出图片(格式png,质量1.0) const imageData = await this.canvasRef.toDataURL('image/png', 1.0); console.log('签名图片保存成功'); return imageData; } catch (err) { console.error('签名图片保存失败:', err); return null; } } // 重写onDraw,更新画布引用 override onDraw(canvas: Canvas) { this.canvasRef = canvas; this.ctx = canvas.getContext('2d'); // 背景绘制逻辑同上... } // build方法不变,外部通过组件实例调用clear和saveAsImage }

四、实战整合:手写签名组件完整应用

将自定义签名组件与配置面板结合,实现完整的手写签名功能:

@Entry @Component struct SignatureDemoPage { @State lineWidth: number = 3; @State lineColor: string = '#2f54eb'; private signaturePadRef: SignaturePad | null = null; build() { Column({ space: 20 }) .width('100%') .height('100%') .padding(30) .backgroundColor('#f5f5f5') { Text('手写签名组件演示') .fontSize(32) .fontWeight(FontWeight.Bold) .textAlign(TextAlign.Center) .width('100%') // 自定义签名组件 SignaturePad( lineWidth: $lineWidth, lineColor: $lineColor, ref: (ref) => this.signaturePadRef = ref ) .width('100%') .height(300) .border({ width: 1, color: '#eee' }) .borderRadius(12) // 配置面板 Column({ space: 15 }) .width('100%') .padding(20) .backgroundColor('#ffffff') .borderRadius(12) { // 笔触粗细调节 Row({ space: 15, alignItems: ItemAlign.Center }) { Text('笔触粗细:') .fontSize(18) .width('30%') Slider() .width('70%') .min(1) .max(10) .value(this.lineWidth) .onChange((value) => this.lineWidth = value) Text(`${this.lineWidth}px`) .fontSize(16) } // 笔触颜色选择 Row({ space: 15, alignItems: ItemAlign.Center }) { Text('笔触颜色:') .fontSize(18) .width('30%') Row({ space: 10 }) { ['#2f54eb', '#ff4d4f', '#36cfc9', '#ffc53d', '#000000'].forEach(color => { View() .width(30) .height(30) .backgroundColor(color) .borderRadius(15) .border(this.lineColor === color ? { width: 2, color: '#333' } : null) .onClick(() => this.lineColor = color) }) } } } // 操作按钮 Row({ space: 30 }) .width('100%') .justifyContent(FlexAlign.Center) { Button('清空签名') .type(ButtonType.Capsule) .width(150) .height(50) .backgroundColor('#ff4d4f') .onClick(() => this.signaturePadRef?.clear()) Button('保存签名') .type(ButtonType.Capsule) .width(150) .height(50) .backgroundColor('#2f54eb') .onClick(async () => { const imageData = await this.signaturePadRef?.saveAsImage(); if (imageData) { Toast.show({ message: '签名保存成功' }); // 后续可将imageData上传服务器或本地存储 } else { Toast.show({ message: '签名保存失败' }); } }) } } } }

五、实战踩坑指南

1. Canvas 上下文获取失败

  • 确保 Canvas 组件已渲染完成再获取上下文,避免在 build 阶段直接调用;
  • 检查 API 版本,getContext('2d')支持 API9+,低版本需使用getContext('2d', { compatible: true })

2. 绘制线条不流畅

  • 开启lineCap: 'round'lineJoin: 'round',避免线条端点和交点出现锯齿;
  • 触摸事件onTouchMove会频繁触发,无需额外节流(鸿蒙已优化),但需确保绘制逻辑简洁。

3. 图片保存失败

  • 确保 Canvas 组件有实际绘制内容,空白画布可能导出失败;
  • 保存图片需申请文件读写权限(ohos.permission.WRITE_USER_DATA),若需上传服务器可直接使用 base64 格式。

4. 组件适配问题

  • Canvas 尺寸建议使用百分比或自适应布局,避免固定像素导致不同设备显示异常;
  • 组件重绘时需先清空画布(clearRect),再重新绘制,避免内容叠加混乱。

加入班级,学习鸿蒙开发

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 5:15:43

20、Linux 恶意 ELF 文件分析全攻略

Linux 恶意 ELF 文件分析全攻略 1. 嵌入式工件提取再探 在成功执行恶意代码样本、进行进程内存轨迹分析或从物理内存中提取可执行文件后,需要重新检查样本以查找嵌入式工件。重新审视未混淆的程序,查看字符串、符号信息、文件元数据和 ELF 结构细节。通过比较文件执行前后的…

作者头像 李华
网站建设 2026/5/7 0:15:10

地理坐标计算神器:Geodesy库的完整使用指南

地理坐标计算神器&#xff1a;Geodesy库的完整使用指南 【免费下载链接】geodesy Libraries of geodesy functions implemented in JavaScript 项目地址: https://gitcode.com/gh_mirrors/ge/geodesy 想要快速实现精准的地理位置计算&#xff1f;Geodesy库为你提供了完整…

作者头像 李华
网站建设 2026/5/9 9:09:22

深度学习理论推导--二分类逻辑回归

文章目录前言二分类问题多元线性函数σ\sigmaσ 函数输出函数似然函数极大似然估计梯度下降法函数准备求偏导损失函数梯度更新python 实战LogisticRegression训练及结果运行结果总结当你迷茫的时候&#xff0c;请回头看看 目录大纲&#xff0c;也许有你意想不到的收获 前言 前…

作者头像 李华
网站建设 2026/5/8 7:46:28

微博超话自动签到神器:告别繁琐签到,享受智能追星新体验

微博超话自动签到神器&#xff1a;告别繁琐签到&#xff0c;享受智能追星新体验 【免费下载链接】weibo_supertopic_sign 基于Python/Nodejs的微博超话签到脚本&#xff0c;支持云函数运行或青龙面板运行 项目地址: https://gitcode.com/gh_mirrors/we/weibo_supertopic_sign…

作者头像 李华
网站建设 2026/5/2 23:53:20

25、系统日志管理与大文件处理全攻略

系统日志管理与大文件处理全攻略 1. 系统日志概述 在系统运行过程中,即使是使用频率较低的系统,在启动和关闭期间也会生成数千行日志文件,而繁忙的应用程序每天轻松就能产生数百万行日志。日志文件往往冗长且枯燥,因此我们通常会借助软件智能过滤出紧急条目,如即将发生故…

作者头像 李华
网站建设 2026/5/3 17:52:08

26、系统监控:日志文件处理与入侵检测

系统监控:日志文件处理与入侵检测 1. 日志文件搜索与分析 在系统管理中,日志文件是发现问题和监控系统状态的重要资源。如果你想确保搜索日志时有结果,可以使用 logger 程序手动生成日志条目,例如: logger "Authentication failure"也可以通过登录用户账户…

作者头像 李华