简介
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的相关属性并调用相关的方法,最终实现涂鸦的功能。除了文中分享的涂鸦样例,开发者还可以通过拓展其他相关的属性和方法,实现更多好玩的、高性能的样例。
为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……