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

news2024/11/15 23:50:42

简介

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

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698294.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git Large File Storage (LFS) 的安装与使用

Git Large File Storage [LFS] 的安装与使用 1. An open source Git extension for versioning large files2. Installing on Linux using packagecloud3. Getting StartedReferences 1. An open source Git extension for versioning large files https://git-lfs.com/ Git …

手把手从0到1教你做STM32+FreeRTOS智能家居--第10篇之ASR-PRO语音识别模块

前言 先看实验效果,通过ASR-PRO语音智能识别控制模块,来控制STM32单片机实现对应的控制功能。因为后台好多小伙伴私信问用的是什么语音模块,并且很少在网上看到如何使用此模块相关的文章,所以我将会在本篇文章详细介绍一下此模块…

列表和元组

2.1序列概述 列表和元组的主要不同在于,列表是可以修改的,而元组不可以。这意味着列表适用于需 要中途添加元素的情形,而元组适用于出于某种考虑需要禁止修改序列的情形。 Python支持一种数据结构的基本概念,名为容器&#xff0…

Gradle筑基——Gradle Maven仓库管理

基础概念: 1.POM pom:全名Project Object Model 项目对象模型,用来描述当前maven项目发布模块的基础信息 pom主要节点信息如下: 配置描述举例(com.android.tools.build:gradle:4.1.1)groupId组织 / 公司的名称com.…

clone方法总结Java

Java中Object类当中有许多方法,如图所示: clone方法就是其中一种,分为浅拷贝,深拷贝举一个例子: 浅拷贝: 在Person类当中右键鼠标然后,选中Generate: 然后重写clone方法 protecte…

福昕PDF编辑器自定义快捷方式

你是否为用不惯福昕PDF编辑器自带的快捷键而发愁?今天,我和大家分享一下如何设置自己想要的快捷键方式,希望能对大家有帮助。 步骤一:打开福昕PDF编辑,并找到更多命令 步骤二:切换到键盘一栏,并…

微信小程序基础 --模板语法(4)

模板语法 1、wxml视图结构 1.1 概述 开发文档:https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/code.html#WXML-%E6%A8%A1%E6%9D%BF 从事过网页编程的人知道,网页编程采用的是 HTML CSS JS 这样的组合,其中 HT…

【热门话题】Debian常用命令指南

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 Debian常用命令指南引言1. 文件与目录操作lscdmkdirrmcpmv 2. 包管理aptdpkg 3.…

WSL安装CentOS系统

1.首选找一个linux系统,执行docker命令 docker run -it --rm centos:7 bash 2.开一个新窗口,将系统导出 docker export e0ee25406703 -o centos.tar 3.切换到wsl命令,导入tar包 wsl --import centos D:\wsl\centos D:\wsl\centos.tar cen…

FFmpeg+QT播放器实战1---UI页面的设计

1、播放器整体布局的设计 该部分使用QT的UI工具,进行整体页面设置,如下图1所示: 2、控制布局的设计 创建ctrBar的UI页面并进行页面布局设置,如下图2所示: 将图1中ctrBarWind对象提升为ctrBar类(该界面替代原先的控…

简单好用的文本识别方法--付费的好用,免费的更有性价比-记笔记

文章目录 先说付费的进入真题,免费的来喏!PixPin微信 先说付费的 直达网址!!! 进入真题,免费的来喏! PixPin 商店里就有 使用示例: 可以看到:贴在桌面上的图片可以复制图片中的文字,真的很…

香橙派AIpro使用SSH远程登录

香橙派AIpro可以连接HDMI显示器使用,也可以远程登录。这里采用MobaXterm软件远程登录开发板。 首先要使得控制电脑和香橙派开发板连接到同一个局域网,两者的IP地址能够ping通。在Windows 下可以使用MobaXterm 远程登录开发板,首先新建一个ss…

Imperva 导致的ORAbase 乱码

DBCA Failing Because Of Garbage Characters In ORACLE_BASE Variable (Doc ID 2947963.1)​编辑To Bottom In this Document Symptoms Changes Cause Solution APPLIES TO: Oracle Database Configuration Assistant - Version 19.14.0.0.0 and later Oracle Database - E…

分享活动规划

前两天去参加菁英学院的一些辅导,是关于苏州久富农业机械的发展,看了他们企业的故事,我觉得我们农机很有前景和发展空间,我希望重新经过一次分享活动来分享我的感触,希望能够再次把我学到的内容传输到其他班的同学们 请…

软件需求规范说明模板

每个软件开发组织都会为自己的项目选用一个或多个标准的软件需求规范说明模板。有许多软件需求规范说明模板可以使用(例如ISO/IEC/IEEE2011;Robertson and Robertson2013)。如果你的组织要处理各种类型或规模的项目,例如新的大型系统开发或是对现有系统进行微调&…

「YashanDB迁移体验官」Mysql生产环境迁移至YashanDB数据库深度体验

「YashanDB迁移体验官」Mysql生产环境迁移至YashanDB数据库深度体验 1. 前言1.1 产品介绍1.2 产品架构1.3 产品规格1.3.1 数据库版本支持1.3.2 数据类型支持 2. YMP安装2.1 环境说明2.2 执行安装2.3 访问YMP2.3.1 YMP登录界面2.3.2 YMP迁移流程 3. YMP数据迁移3.1 创建数据源3.…

创新实训2024.05.26日志:落地基于硬盘的数据库服务

1. 需求任务列表 以下描述易学大模型软件的web应用的功能。 用户注册 用户邮箱,密码,验证码开启官方邮箱,用来发验证码(QQ 网易都支持开启smtp协议,找教程,用邮箱不用手机号是为了省买发短信云服务的钱&a…

初识C语言——第二十四天

函数的基本使用和递归 1.函数是什么 2.库函数 3.自定义函数 4.函数参数 5.函数调用 6.函数的嵌套调用和链式访问 7.函数的声明和定义 函数是什么 C语言中函数的分类 1.库函数 2.自定义函数 库函数: 简单的总结,C语言常用的库函数都有: #includ…

关于数据库和数据表的基础SQL

目录 一. 数据库的基础SQL 1. 创建数据库 2. 查看当前有哪些数据库 3. 选中数据库 4. 删除数据库 5. 小结 二. 数据表的基础SQL 1. 创建数据表 2. 查看当前数据库中有哪些表 3. 查看指定表的详细情况(查看表的结构) 4. 删除表 5. 小结 一. 数据库的基础SQL 1. 创建…

YOLOv10 | 手把手教你利用yolov10训练自己数据集(含环境搭建 + 参数解析 + 数据集查找 + 模型训练、推理、导出)

一、前言 本文内含YOLOv10网络结构图 各个创新模块手撕结构图 训练教程 推理教程 参数解析 环境搭建 数据集获取等一些有关YOLOv10的内容! 目录 一、 前言 二、整体网络结构图 三、空间-通道分离下采样 3.1 SCDown介绍 3.2 C2fUIB介绍 3.3 PSA介绍 …