首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

HarmonyOS鸿蒙开发实战( Beta5.0)手写绘制及保存图片案例实践

  • 25-03-03 06:42
  • 3900
  • 5585
blog.csdn.net

鸿蒙HarmonyOS开发实战往期必看文章:(持续更新......)

HarmonyOS NEXT应用开发性能实践总结(持续更新......)

HarmonyOS NEXT应用开发案例实践总结合集(持续更新......)

一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发!

最新版!“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)


介绍

本示例使用drawing库的Pen和Path结合NodeContainer组件实现手写绘制功能。手写板上完成绘制后,通过调用image库的packToFile和packing接口将手写板的绘制内容保存为图片,并将图片文件保存在应用沙箱路径中。

效果图预览

使用说明

  1. 在虚线区域手写绘制,点击撤销按钮撤销前一笔绘制,点击重置按钮清空绘制。
  2. 点击packToFile保存图片按钮和packing保存图片按钮可以将绘制内容保存为图片写入文件,显示图片的沙箱路径。

实现思路

  1. 创建NodeController的子类MyNodeController,用于获取根节点的RenderNode和绑定的NodeContainer组件宽高。源码参考RenderNodeModel.ets
  1. export class MyNodeController extends NodeController {
  2. private rootNode: FrameNode | null = null; // 根节点
  3. rootRenderNode: RenderNode | null = null; // 从NodeController根节点获取的RenderNode,用于添加和删除新创建的MyRenderNode实例
  4. width: number = 0; // 实例绑定的NodeContainer组件的宽,单位px
  5. height: number = 0; // 实例绑定的NodeContainer组件的宽,单位px
  6. // MyNodeController实例绑定的NodeContainer创建时触发,创建根节点rootNode并将其挂载至NodeContainer
  7. makeNode(uiContext: UIContext): FrameNode {
  8. this.rootNode = new FrameNode(uiContext);
  9. if (this.rootNode !== null) {
  10. this.rootRenderNode = this.rootNode.getRenderNode();
  11. }
  12. return this.rootNode;
  13. }
  14. // 绑定的NodeContainer布局时触发,获取NodeContainer的宽高
  15. aboutToResize(size: Size): void {
  16. this.width = size.width;
  17. this.height = size.height;
  18. // 设置画布底色为白色
  19. if (this.rootRenderNode !== null) {
  20. // NodeContainer布局完成后设置rootRenderNode的背景色为白色
  21. this.rootRenderNode.backgroundColor = 0XFFFFFFFF;
  22. // rootRenderNode的位置从组件NodeContainer的左上角(0,0)坐标开始,大小为NodeContainer的宽高
  23. this.rootRenderNode.frame = { x: 0, y: 0, width: this.width, height: this.height };
  24. }
  25. }
  26. }
  1. 创建RenderNode的子类MyRenderNode,初始化画笔和绘制path路径。源码参考RenderNodeModel.ets
  1. export class MyRenderNode extends RenderNode {
  2. path: drawing.Path = new drawing.Path(); // 新建路径对象,用于绘制手指移动轨迹
  3. // RenderNode进行绘制时会调用draw方法,初始化画笔和绘制路径
  4. draw(context: DrawContext): void {
  5. const canvas = context.canvas;
  6. // 创建一个画笔Pen对象,Pen对象用于形状的边框线绘制
  7. const pen = new drawing.Pen();
  8. // 设置画笔开启反走样,可以使得图形的边缘在显示时更平滑
  9. pen.setAntiAlias(true);
  10. // 设置画笔颜色为黑色
  11. const pen_color: common2D.Color = { alpha: 0xFF, red: 0x00, green: 0x00, blue: 0x00 };
  12. pen.setColor(pen_color);
  13. // 开启画笔的抖动绘制效果。抖动绘制可以使得绘制出的颜色更加真实。
  14. pen.setDither(true);
  15. // 设置画笔的线宽为5px
  16. pen.setStrokeWidth(5);
  17. // 将Pen画笔设置到canvas中
  18. canvas.attachPen(pen);
  19. // 绘制path
  20. canvas.drawPath(this.path);
  21. }
  22. }
  1. 创建变量currentNode用于存储当前正在绘制的节点,变量nodeCount用来记录已挂载的节点数量。源码参考HandWritingToImage.ets
  1. private currentNode: MyRenderNode | null = null; // 当前正在绘制的节点
  2. private nodeCount: number = 0; // 已挂载到根节点的子节点数量
  1. 创建自定义节点容器组件NodeContainer,接收MyNodeController的实例,将自定义的渲染节点挂载到组件上,实现自定义绘制。源码参考HandWritingToImage.ets
  1. NodeContainer(this.myNodeController)
  2. .width('100%')
  3. .height($r('app.integer.hand_writing_canvas_height'))
  4. .onTouch((event: TouchEvent) => {
  5. this.onTouchEvent(event);
  6. })
  7. .id(NODE_CONTAINER_ID)
  1. 在NodeContainer组件的onTouch回调函数中,手指按下创建新的节点并挂载到rootRenderNode,nodeCount加一,手指移动更新节点中的path对象,绘制移动轨迹,并将节点重新渲染。源码参考HandWritingToImage.ets
  1. onTouchEvent(event: TouchEvent): void {
  2. // TODO:知识点:在手指按下时创建新的MyRenderNode对象,挂载到rootRenderNode上,手指移动时根据触摸点坐标绘制线条,并重新渲染节点
  3. // 获取手指触摸位置的坐标点
  4. const positionX: number = vp2px(event.touches[0].x);
  5. const positionY: number = vp2px(event.touches[0].y);
  6. logger.info(TAG, `Touch positionX: ${positionX}, Touch positionY: ${positionY}`);
  7. switch (event.type) {
  8. case TouchType.Down: {
  9. // 每次手指按下,创建一个MyRenderNode对象,用于记录和绘制手指移动的轨迹
  10. const newNode = new MyRenderNode();
  11. // 定义newNode的大小和位置,位置从组件NodeContainer的左上角(0,0)坐标开始,大小为NodeContainer的宽高
  12. newNode.frame = { x: 0, y: 0, width: this.myNodeController.width, height: this.myNodeController.height };
  13. this.currentNode = newNode;
  14. // 移动新节点中的路径path到手指按下的坐标点
  15. this.currentNode.path.moveTo(positionX, positionY);
  16. if (this.myNodeController.rootRenderNode !== null) {
  17. // appendChild在renderNode最后一个子节点后添加新的子节点
  18. this.myNodeController.rootRenderNode.appendChild(this.currentNode);
  19. // 已挂载的节点数量加一
  20. this.nodeCount++;
  21. }
  22. break;
  23. }
  24. case TouchType.Move: {
  25. if (this.currentNode !== null) {
  26. // 手指移动,绘制移动轨迹
  27. this.currentNode.path.lineTo(positionX, positionY);
  28. // 节点的path更新后需要调用invalidate()方法触发重新渲染
  29. this.currentNode.invalidate();
  30. }
  31. break;
  32. }
  33. case TouchType.Up: {
  34. // 手指抬起,释放this.currentNode
  35. this.currentNode = null;
  36. }
  37. default: {
  38. break;
  39. }
  40. }
  41. }
  1. rootRenderNode调用getChild方法获取最后一个挂载的子节点,再使用removeChild方法移除,实现撤销上一笔的效果。源码参考HandWritingToImage.ets
  1. goBack() {
  2. if (this.myNodeController.rootRenderNode !== null && this.nodeCount > 0) {
  3. // getChild获取最后挂载的子节点
  4. const node = this.myNodeController.rootRenderNode.getChild(this.nodeCount - 1);
  5. // removeChild移除指定子节点
  6. this.myNodeController.rootRenderNode.removeChild(node);
  7. this.nodeCount--;
  8. }
  9. }
  1. 使用clearChildren清除当前rootRenderNode的所有子节点,实现画布重置,nodeCount清零。源码参考HandWritingToImage.ets
  1. resetCanvas() {
  2. if (this.myNodeController.rootRenderNode !== null && this.nodeCount > 0) {
  3. // 清除当前rootRenderNode的所有子节点
  4. this.myNodeController.rootRenderNode.clearChildren();
  5. this.nodeCount = 0;
  6. }
  7. }
  1. 使用componentSnapshot.get获取组件NodeContainer的PixelMap对象,用于保存图片。源码参考HandWritingToImage.ets
  1. componentSnapshot.get(NODE_CONTAINER_ID, async (error: Error, pixelMap: image.PixelMap) => {
  2. if (pixelMap !== null) {
  3. // 图片写入文件
  4. this.filePath = await this.saveFile(getContext(), pixelMap);
  5. logger.info(TAG, `Images saved using the packing method are located in : ${this.filePath}`);
  6. }
  7. })
  1. 使用image库的packToFile()和packing()将获取的PixelMap对象保存为图片,并将图片文件保存在应用沙箱路径中。源码参考HandWritingToImage.ets
  • ImagePacker.packToFile()可直接将PixelMap对象写入为图片。
  1. async packToFile(context: Context, pixelMap: PixelMap): Promise<string> {
  2. // 创建图像编码ImagePacker对象
  3. const imagePackerApi = image.createImagePacker();
  4. // 设置编码输出流和编码参数。format为图像的编码格式;quality为图像质量,范围从0-100,100为最佳质量
  5. const options: image.PackingOption = { format: "image/jpeg", quality: 100 };
  6. // 图片写入的沙箱路径
  7. const filePath: string = `${context.filesDir}/${getTimeStr()}.jpg`;
  8. const file: fs.File = await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
  9. // 使用packToFile直接将pixelMap写入文件
  10. await imagePackerApi.packToFile(pixelMap, file.fd, options);
  11. fs.closeSync(file);
  12. return filePath;
  13. }
  • ImagePacker.packing()可获取图片的ArrayBuffer数据,再使用fs将数据写入为图片。
  1. async saveFile(context: Context, pixelMap: PixelMap): Promise<string> {
  2. // 创建图像编码ImagePacker对象
  3. const imagePackerApi = image.createImagePacker();
  4. // 设置编码输出流和编码参数。format为图像的编码格式;quality为图像质量,范围从0-100,100为最佳质量
  5. const options: image.PackingOption = { format: "image/jpeg", quality: 100 };
  6. // 图片写入的沙箱路径
  7. const filePath: string = `${context.filesDir}/${getTimeStr()}.jpg`;
  8. const file: fs.File = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  9. // 使用packing打包获取图片的ArrayBuffer
  10. const data: ArrayBuffer = await imagePackerApi.packing(pixelMap, options);
  11. // 将图片的ArrayBuffer数据写入文件
  12. fs.writeSync(file.fd, data);
  13. fs.closeSync(file);
  14. return filePath;
  15. }

高性能知识点

不涉及

工程结构&模块类型

  1. handwritingtoimage // har类型
  2. |---/src/main/ets/model
  3. | |---RenderNodeModel.ets // 模型层-节点数据模型
  4. |---/src/main/ets/view
  5. | |---HandWritingToImage.ets // 视图层-手写板场景页面

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)路线图、学习视频、文档用来跟着学习是非常有必要的。 

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员

鸿蒙 NEXT 全栈开发学习笔记  希望这一份鸿蒙学习文档能够给大家带来帮助~

这份鸿蒙(HarmonyOS NEXT)包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。


鸿蒙(HarmonyOS NEXT)最新学习路线

​

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程 全球开发者的开源社区,开源代码

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)  全球开发者的开源社区,开源代码

​​

《鸿蒙大厂面试真题》GitCode - 全球开发者的开源社区,开源代码

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

鸿蒙NEXT全套学习资料
微信名片
注:本文转载自blog.csdn.net的让开,我要吃人了的文章"https://blog.csdn.net/weixin_55362248/article/details/142491835"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top