写点什么

如何利用 OpenHarmony ArkUI 的 Canvas 组件实现涂鸦功能?

  • 2022 年 9 月 20 日
    上海
  • 本文字数:6905 字

    阅读完需:约 23 分钟

如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能?

简介

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


效果展示

以下为效果图:




首页显示了涂鸦的图片以及最后一张空白图片,在点击图片进入涂鸦页面后,可以对画笔的颜色、粗细进行设置。如果涂鸦过程中有错误,可以用橡皮擦将画面擦除,也可点击清除按钮,清空涂鸦的内容,重新进行涂鸦操作。相关代码已经上传至 SIG 仓库,链接如下:https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/FreeDraw


目录结构


源码分析

一、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('粗细:')


    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 的相关属性并调用相关的方法,最终实现涂鸦的功能。除了文中分享的涂鸦样例,开发者还可以通过拓展其他相关的属性和方法,实现更多好玩的、高性能的样例。



用户头像

OpenHarmony开发者官方账号 2021.12.15 加入

OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展

评论

发布
暂无评论
如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能?_OpenHarmony_OpenHarmony开发者社区_InfoQ写作社区