【鸿蒙南向开发】如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能

90 篇文章 0 订阅
90 篇文章 0 订阅

简介

ArkUI是一套UI开发框架,提供了开发者进行应用UI开发时所需具备的能力。随着OpenAtom OpenHarmony(以下简称“OpenHarmony”)不断更新迭代,ArkUI也提供了很多新的组件,例如Canvas、OffscreenCanvas、XComponent组件等。
新增的功能可以帮助开发者开发出更流畅、更美观的应用。本篇文章将为大家分享如何通过Canvas组件实现涂鸦功能,用户可以选择空白画布或者简笔图进行自由绘画。

效果展示

以下为效果图:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

目录结构

在这里插入图片描述

源码分析

一、Canvas组件介绍

本篇样例主要利用ArkUI的Canvas组件实现涂鸦的功能,首先介绍一下Canvas组件。
Canvas组件主要包含了Canvas和CanvasRenderingContext2D,Canvas提供了画布功能,CanvasRenderingContext2D提供了绘画的属性和方法。通过CanvasRenderingContext2D可以修改画笔的样色、粗细等属性,从而画出各式各样的图形。
以下是Canvas和CanvasRenderingContext2D在样例开发中使用的相关接口信息。

在这里插入图片描述

CanvasRenderingContext2D

在这里插入图片描述

二、分析源码页面布局

第一个模块是首页布局,首页显示所有涂鸦包含的图片,点击图片可以进入页面;第二个模块是涂鸦模块,可以设置画笔的颜色、边条宽度等。

1. 首页布局

Column() {
      Text('选择涂鸦的图片:').margin('10vp').fontSize('30fp').fontColor(Color.Blue).height('5%')
      Grid() {
        ForEach(this.images, (item, index) => {
          GridItem() {
            Image(this.images[index])
              .onClick((event) => {
                router.push(
                  {
                    url: "pages/detailPage",
                    params: {
                      imgSrc: this.images[index],
                    },
                  }
                )
              })
              .width('100%')
              .height('100%')
              .objectFit(ImageFit.Contain)
          }
        })
      }
      .padding({left: this.columnSpace, right: this.columnSpace})
      .columnsTemplate("1fr 1fr 1fr")      // Grid宽度均分成3份
      .rowsTemplate("1fr 1fr")     // Grid高度均分成2份
      .rowsGap(this.rowSpace)                  // 设置行间距
      .columnsGap(this.columnSpace)            // 设置列间距
      .width('100%')
      .height('95%')
    }
    .backgroundColor(Color.Pink)

2. 涂鸦页面 - 画布Canvas的布局

通过Stack组件进行包裹,并将Canvas画布覆盖在选择的背景图片之上,这些背景图片主要是水果简笔画。

Stack() {

        Image(this.imgSrc).width('100%').height('100%').objectFit(ImageFit.Contain)

        Canvas(this.context)
          .width('100%')
          .height('100%')
//          .backgroundColor('#00ffff00')
          .onReady(() => {
          })
          .onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.eventType = 'Down';
              this.drawing = true;
              [this.x, this.y] = [event.touches[0].x, event.touches[0].y];
              this.context.beginPath();
              this.context.lineCap = 'round';
              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(this.x, this.y, 20, 20);
              }

              console.log('gyf Down');
            }
            if (event.type === TouchType.Up) {
              this.eventType = 'Up';
              this.drawing = false;
              console.log('gyf Up!');
              this.context.closePath();
            }
            if (event.type === TouchType.Move) {
              if (!this.drawing) return;
              this.eventType = 'Move';
              console.log('gyf Move');

              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(event.touches[0].x, event.touches[0].y, 20, 20);
              } else {
                this.context.lineWidth = this.lineWidth;
                this.context.strokeStyle = this.color;

                this.context.moveTo(this.x, this.y);
                this.x = event.touches[0].x;
                this.y = event.touches[0].y;
                this.context.lineTo(this.x, this.y);
                this.context.stroke();
              }
            }
          })

      }.width('100%').height('75%')

3.涂鸦页面 - 画笔设置区域的布局

Column() {
        Row() {
          Text('粗细:')

          Button('小').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 5;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf small button');
          }).margin($r('app.float.wh_value_10'))

          Button('中').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 15;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf middle button');
          }).margin($r('app.float.wh_value_10'))

          Button('大').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 25;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf big button');
          }).margin($r('app.float.wh_value_10'))

          Button('超大').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 40;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf super big button');
          })
        }.padding($r('app.float.wh_value_10')).margin($r('app.float.wh_value_5'))

        //画笔颜色
        Scroll() {
          Row() {
            Text('颜色:')

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //黑色
                this.color = '#000000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf black button');
              })
              .backgroundColor('#000000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //红色
                this.color = '#FF0000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf red button');
              })
              .backgroundColor('#FF0000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //绿色
                this.color = '#00FF00';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf green button');
              })
              .backgroundColor('#00FF00')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //蓝色
                this.color = '#0000FF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#0000FF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //棕色
                this.color = '#A52A2A';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#A52A2A')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //紫色
                this.color = '#800080';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#800080')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //紫红色
                this.color = '#FF00FF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FF00FF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //深蓝色
                this.color = '#00008B';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#00008B')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //深天蓝
                this.color = '#00BFFF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#00BFFF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //绿色
                this.color = '#008000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#008000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //青绿色
                this.color = '#32CD32';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#32CD32')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //橙色
                this.color = '#FFA500';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FFA500')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //黄色
                this.color = '#FFFF00';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FFFF00')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

          }.padding('10vp')
        }
        .scrollable(ScrollDirection.Horizontal) // 设置滚动条水平方向滚动
        .margin($r('app.float.wh_value_5'))

        Row() {
          Image('/common/images/eraser.png')
            .onClick(() => {
              //橡皮擦模式
              this.isEraserMode = true;
              console.log('gyf eraser button');
            })
            .width('50vp')
            .height('50vp')
            .margin('10vp')

          Button('清理画板').onClick(() => {
            this.context.clearRect(0, 0, 1000, 1000);
          })
        }
        .margin($r('app.float.wh_value_5'))

      }
      .width('100%')
      .height('25%')
      .alignItems(HorizontalAlign.Start)

三、逻辑代码

逻辑代码存在于Canvas的onTouch事件中,通过TouchType的Down、Up、Move来判断开始、移动和结束的动作。一笔完整的绘制包含一次Down和Up,其中有若干次的Move。橡皮擦模式通过clearRect接口实现擦除的功能。

.onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.eventType = 'Down';
              this.drawing = true;
              [this.x, this.y] = [event.touches[0].x, event.touches[0].y];
              this.context.beginPath();
              this.context.lineCap = 'round';
              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(this.x, this.y, 20, 20);
              }

              console.log('gyf Down');
            }
            if (event.type === TouchType.Up) {
              this.eventType = 'Up';
              this.drawing = false;
              console.log('gyf Up!');
              this.context.closePath();
            }
            if (event.type === TouchType.Move) {
              if (!this.drawing) return;
              this.eventType = 'Move';
              console.log('gyf Move');

              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(event.touches[0].x, event.touches[0].y, 20, 20);
              } else {
                this.context.lineWidth = this.lineWidth;
                this.context.strokeStyle = this.color;

                this.context.moveTo(this.x, this.y);
                this.x = event.touches[0].x;
                this.y = event.touches[0].y;
                this.context.lineTo(this.x, this.y);
                this.context.stroke();
              }
            }
          })

总结

本文介绍了如何使用ArkUI框架提供的Canvas组件实现涂鸦功能。首先,通过Canvas的onTouch事件来跟踪Down、Move和Up的事件,再设置CanvasRenderingContext2D的相关属性并调用相关的方法,最终实现涂鸦的功能。除了文中分享的涂鸦样例,开发者还可以通过拓展其他相关的属性和方法,实现更多好玩的、高性能的样例。

写在最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。

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

希望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

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

在这里插入图片描述

有了路线图,怎么能没有学习文档呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

《鸿蒙 (OpenHarmony)开发入门教学视频》

在这里插入图片描述

《鸿蒙生态应用开发V3.0白皮书》

在这里插入图片描述

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

在这里插入图片描述

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
在这里插入图片描述

《鸿蒙开发进阶》

●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
在这里插入图片描述

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……
在这里插入图片描述

获取以上完整鸿蒙HarmonyOS学习文档,请点击→纯血版全套鸿蒙HarmonyOS学习文档

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 可以通过 canvas 实现签名功能,可以将签名组件封装成一个单独的组件,然后在需要使用签名功能的页面中引入这个组件。 以下是一个简单的示例代码: 1. 创建一个 SignCanvas.vue 文件,定义签名组件 ```vue <template> <div> <canvas ref="canvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="finishDrawing"></canvas> <button @click="clear">Clear</button> </div> </template> <script> export default { name: 'SignCanvas', data() { return { isDrawing: false, lastX: 0, lastY: 0, ctx: null, }; }, mounted() { this.ctx = this.$refs.canvas.getContext('2d'); this.ctx.strokeStyle = 'black'; this.ctx.lineWidth = 2; this.ctx.lineCap = 'round'; }, methods: { startDrawing(e) { this.isDrawing = true; this.lastX = e.offsetX; this.lastY = e.offsetY; }, draw(e) { if (!this.isDrawing) return; this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(e.offsetX, e.offsetY); this.ctx.stroke(); this.lastX = e.offsetX; this.lastY = e.offsetY; }, finishDrawing() { this.isDrawing = false; }, clear() { this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height); }, }, }; </script> ``` 2. 在需要使用签名功能的页面中引入该组件 ```vue <template> <div> <h1>Sign Here</h1> <sign-canvas /> </div> </template> <script> import SignCanvas from '@/components/SignCanvas.vue'; export default { name: 'SignPage', components: { SignCanvas, }, }; </script> ``` 然后,在需要使用签名功能的页面中,就可以直接使用 `<sign-canvas>` 组件了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值