HarmonyOS开发实例:【手势截屏】

 介绍

本篇Codelab基于手势处理和截屏能力,介绍了手势截屏的实现过程。样例主要包括以下功能:

  1. 根据下滑手势调用全屏截图功能。
  2. 全屏截图,同时右下角有弹窗提示截图成功。
  3. 根据双击手势调用区域截图功能。
  4. 区域截图,通过调整选择框大小完成。

相关概念

  • Canvas:画布组件,用于自定义绘制图形。
  • CanvasRenderingContext2D对象:使用RenderingContext在Canvas组件上进行绘制,绘制对象可以是矩形、文本、图片等。
  • 双击手势:手指双击屏幕回调事件。
  • 手指滑动手势:手指在屏幕滑动回调事件。

相关权限

  • 本篇Codelab用到屏幕截图的能力,需要在配置文件module.json5里添加屏幕截图的权限:ohos.permission.CAPTURE_SCREEN。
  • 本篇Codelab需要使用的screenshot为系统接口。需要使用Full SDK手动从镜像站点获取,并在DevEco Studio中替换。
  • 本篇Codelab使用的部分API仅系统应用可用,需要提升应用等级为system_core。

环境搭建

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。

    1. 开始前请参考[工具准备]qr23.cn/AKFP8k,完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets           // 代码区
│  ├──common
│  │  └──utils
│  │     ├──CommonConstants.ets	// 公共常量类
│  │     ├──DrawUtil.ets        // 画布相关工具类
│  │     └──Logger.ets          // 日志打印类
│  ├──entryability
│  │  └──EntryAbility.ets       // 程序入口类
│  ├──model
│  │  └──OffsetModel.ets        // 区域截图坐标相关工具类
│  ├──pages
│  │  └──GestureScreenshot.ets  // 主界面	
│  └──view
│     ├──AreaScreenshot.ets     // 自定义区域截屏组件类
│     └──ScreenshotDialog.ets   // 自定义截屏显示弹窗组件类
└──entry/src/main/resources     // 资源文件目录

构建截屏主页面

使用下滑手势,进行全屏截图并展示图片。效果如图所示:

主界面主要实现以下功能:

  1. 下滑手势绑定在主界面上,双击手势绑定在区域手势的最底层Stack组件上。
  2. 如果使用下滑手势,就进行全屏截图并展示图片。
  3. 如果使用双击手势,就唤起区域截图相关组件。

搜狗高速浏览器截图20240326151450.png

// GestureScreenshot.ets
// 区域截图最底层,当主页面缩放后会露出,设置为黑色
Stack() {// 主页面布局Column() {...})// 添加滑动手势事件.gesture(// fingers:触发手指数 direction:触发方向 distance:触发滑动距离PanGesture({fingers: 1,direction: PanDirection.Down,distance: CommonConstants.MINIMUM_FINGER_DISTANCE})// 触发开始回调.onActionStart(() => {let screenshotOptions: screenshot.ScreenshotOptions = {rotation: 0};screenshot.save(screenshotOptions, (err: Error, data: image.PixelMap) => {if (err) {Logger.error(`Failed to save the screenshot. Error:${ JSON.stringify(err) }`);}if (this.pixelMap !== undefined) {this.pixelMap.release();}this.pixelMap = data;this.dialogController.open();});})).scale(this.scaleNum)// 区域截图相关组件AreaScreenshot({ showScreen: this.showScreen, pixelMap: this.pixelMap, scaleNum: this.scaleNum })
}
.backgroundColor($r('app.color.black_area'))
// 添加双击手势事件
.gesture(TapGesture({ count: 2 }).onAction(() => {this.showScreen = true;this.scaleNum = {x: CommonConstants.X_SCALE_DOWN,y: CommonConstants.Y_SCALE_DOWN}})
)

构建区域截图组件

本章节将完成区域选择框的绘制并完成区域截图,效果如图所示:

在绘制区域选择框之前,首先需要在AreaScreenshot.ets的aboutToAppear方法中获取屏幕的宽和高,并初始化offsetModel和drawUtil对象(初始化参数为屏幕的宽高)。offsetModel对输入的坐标进行计算和更改,drawUtil使用offsetModel的坐标在屏幕上绘制区域选择框。

// AreaScreenshot.ets
aboutToAppear() {window.getLastWindow(getContext(this)).then((window) => {let property = window.getWindowProperties();this.systemBarHeight = property.windowRect.top;drawUtil.initDrawUtil(this.canvasRenderingContext,px2vp(property.windowRect.width),px2vp(property.windowRect.height));offsetModel.initOffsetModel(px2vp(property.windowRect.width),px2vp(property.windowRect.height));// 在展示截图的时候,用于计算图片大小this.screenAspectRatio = px2vp(property.windowRect.height) / px2vp(property.windowRect.width);}).catch((err: Error) => {Logger.error(`window loading has error: ${ JSON.stringify(err) }`);})
}

在AreaScreenshot.ets布局页面中添加Canvas组件,通过showScreen变量控制局部截屏页面的显示,并控制主页面的缩放。步骤如下:

  1. 根据手指按下的位置确定需要移动的边框。
  2. 手指移动后,更新offsetModel记录的坐标信息。
  3. 根据offsetModel提供的坐标,使用drawUtil绘制区域选择框。
  4. 点击保存按钮后,设置截屏区域坐标。
  5. 根据截屏区域坐标进行区域截屏。
// AreaScreenshot.ets
// 关闭区域截屏相关组件,并还原主页面
private resetParameter() {this.showScreen = false;this.scaleNum = {x: CommonConstants.NO_SCALE_DOWN,y: CommonConstants.NO_SCALE_DOWN};offsetModel.resetDefaultOffSet();
}// 使用if渲染,控制区域截图相关组件的显隐
if (this.showScreen) {Stack() {Canvas(this.canvasRenderingContext)....onReady(() => {// 通过draw方法绘制选择框和非高亮区域drawUtil.draw();})// 截图的工具栏Row() {...// 区域截图并展示图像Image($r('app.media.ic_save')).onClick(() => {let screenshotOptions: screenshot.ScreenshotOptions = {// 截屏区域Rect参数screenRect: {left: vp2px(offsetModel.getXLeft()),top: vp2px(offsetModel.getYTop()) + this.systemBarHeight,width: vp2px(offsetModel.getWidth()),height: vp2px(offsetModel.getHeight())} as screenshot.Rect,// 截图的大小imageSize: {width: vp2px(offsetModel.getWidth()),height: vp2px(offsetModel.getHeight())} as screenshot.Size,rotation: 0,displayId: 0};screenshot.save(screenshotOptions, (err: Error, data: image.PixelMap) => {if (err) {Logger.error(`Failed to save the screenshot. Error:${JSON.stringify(err)}`);}if (this.pixelMap !== undefined) {this.pixelMap.release();}this.pixelMap = data;// 使用弹窗组件展示截完的图片this.dialogController.open();});this.resetParameter();})}...// 根据手指位置调整选择框大小和位置.onTouch((event: TouchEvent) => {switch(event.type) {case TouchType.Down:// 根据手指位置,判断移动哪个坐标offsetModel.setXLocationType(event.touches[0].screenX);offsetModel.setYLocationType(event.touches[0].screenY);break;case TouchType.Move:// 更新坐标信息,并保证坐标值合法offsetModel.resetOffsetXY(event.touches[0].screenX, event.touches[0].screenY);drawUtil.draw();break;default:break;}})
}

区域选择框工具类的实现

在构建区域截图组件中介绍了OffsetModel和DrawUtil两个工具类,本章节介绍一下具体的实现步骤。

使用OffsetModel校验坐标的范围,并保存坐标相关信息。

  1. 在初始化对象的时候,根据屏幕的缩放比例计算出黑色区域的宽高。
  2. 使用setXLocationType方法和setYLocationType方法,判断需要移动的x、y坐标位置。
  3. 根据传入的x、y坐标值,更改offset对应的坐标值,并保证选择框的宽高大于等于预设的选择框的最小值。
  4. 再次校验offset坐标值,是否超出可截屏区域。
// OffsetModel.ets
public initOffsetModel(width: number, height: number) {...this.blackAreaWidth = this.screenWidth * (1 - CommonConstant.X_SCALE_DOWN);this.blackAreaWidth = this.blackAreaWidth / CommonConstant.BLACK_AREA_NUM;this.blackAreaHeight = this.screenHeight * (1 - CommonConstant.Y_SCALE_DOWN);this.blackAreaHeight = this.blackAreaHeight / CommonConstant.BLACK_AREA_NUM;
}// 判断x坐标位置
public setXLocationType(offsetX: number) {if (offsetX > this.offsetXRight - CommonConstant.OFFSET_RANGE &&offsetX < this.offsetXRight + CommonConstant.OFFSET_RANGE) {this.xLocationType = XLocationEnum.XRight;} else if (offsetX > this.offsetXLeft - CommonConstant.OFFSET_RANGE &&offsetX < this.offsetXLeft + CommonConstant.OFFSET_RANGE) {this.xLocationType = XLocationEnum.XLeft;} else {this.xLocationType = XLocationEnum.noChange;}
}// 判断y坐标位置
public setYLocationType(offsetY: number) {...
}// 根据参数改变坐标值
public resetOffsetXY(offsetX: number, offsetY: number) {if (this.xLocationType === XLocationEnum.XLeft) {this.offsetXLeft = this.offsetXRight - offsetX < CommonConstant.OFFSET_RANGE * 2 ?this.offsetXLeft : offsetX;}...this.checkOffsetXY();
}// 再次校验坐标值,是否超出可截屏区域
private checkOffsetXY() {this.offsetXLeft = this.offsetXLeft < this.blackAreaWidth ? this.blackAreaWidth : this.offsetXLeft;this.offsetXRight = this.offsetXRight > this.screenWidth - this.blackAreaWidth ?this.screenWidth - this.blackAreaWidth : this.offsetXRight;this.offsetYTop = this.offsetYTop < this.blackAreaHeight ? this.blackAreaHeight : this.offsetYTop;this.offsetYBottom = this.offsetYBottom > this.screenHeight - this.blackAreaHeight ?this.screenHeight - this.blackAreaHeight : this.offsetYBottom;
}

DrawUtil主要提供绘制方法,用于绘制区域选择框。

// DrawUtil.ets
// 绘制整个区域选择框
public draw() {this.offsetXLeft = offsetModel.getXLeft();this.offsetXRight = offsetModel.getXRight();this.offsetYTop = offsetModel.getYTop();this.offsetYBottom = offsetModel.getYBottom();// 填充非高亮区域this.drawScreenSelection();// 绘制框选线this.drawLines();
}// 填充非高亮区域,设置回形区域并填充颜色
private drawScreenSelection() {this.canvasContext.clearRect(0, 0, this.screenWidth, this.screenHeight)this.canvasContext.beginPath();this.canvasContext.moveTo(0, 0);this.canvasContext.lineTo(this.screenWidth, 0);this.canvasContext.lineTo(this.screenWidth, this.screenHeight);this.canvasContext.lineTo(0, this.screenHeight);this.canvasContext.closePath();this.canvasContext.moveTo(this.offsetXRight, this.offsetYTop);this.canvasContext.lineTo(this.offsetXLeft, this.offsetYTop);this.canvasContext.lineTo(this.offsetXLeft, this.offsetYBottom);this.canvasContext.lineTo(this.offsetXRight, this.offsetYBottom);this.canvasContext.globalAlpha = Constants.UNSELECT_AREA_ALPHA;this.canvasContext.fillStyle = Constants.UNSELECT_AREA_COLOR;this.canvasContext.closePath();this.canvasContext.fill();
}// 绘制框选线
private drawLines() {this.canvasContext.beginPath();...this.canvasContext.moveTo((this.offsetXLeft + Constants.LINES_MAX_LENGTH),(this.offsetYTop - Constants.GAP_WIDTH));this.canvasContext.lineTo((this.offsetXLeft - Constants.GAP_WIDTH),(this.offsetYTop - Constants.GAP_WIDTH));this.canvasContext.lineTo((this.offsetXLeft - Constants.GAP_WIDTH),(this.offsetYTop + Constants.LINES_MAX_LENGTH));...this.canvasContext.stroke();
}

展示截图

采用弹窗组件展示截屏,需要在aboutToAppear方法中计算对应的宽度:

  1. 截图长宽比小于或者等于屏幕长宽比:此截图展示时和全屏截图展示时等宽。
  2. 截图长宽比大于屏幕长宽比:此截图展示时和全屏截图展示时等长,通过计算对应的宽来实现。
// ScreenshotDialog.ets
aboutToAppear() {this.getDialogWidth();
}
...
private async getDialogWidth() {if (this.pixelMap !== undefined) {let info = await this.pixelMap.getImageInfo();let pixelMapAspectRatio = info.size.height / info.size.width;if ((this.screenAspectRatio !== -1) && (pixelMapAspectRatio > this.screenAspectRatio)) {let width = CommonConstants.HEIGHT_FIRST / pixelMapAspectRatio * this.screenAspectRatio;this.dialogWidth = width + '%';} else {this.dialogWidth = CommonConstants.WIDTH_FIRST;}}
}

鸿蒙开发岗位需要掌握那些核心要领?

目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。

自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。

废话就不多说了,接下来好好看下这份资料。

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。

针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

其中内容包含:

《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往

  1. ArkTS语言
  2. 安装DevEco Studio
  3. 运用你的第一个ArkTS应用
  4. ArkUI声明式UI开发
  5. .……

《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往

  1. Stage模型入门
  2. 网络管理
  3. 数据管理
  4. 电话服务
  5. 分布式应用开发
  6. 通知与窗口管理
  7. 多媒体技术
  8. 安全技能
  9. 任务管理
  10. WebGL
  11. 国际化开发
  12. 应用测试
  13. DFX面向未来设计
  14. 鸿蒙系统移植和裁剪定制
  15. ……

《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往

  1. ArkTS实践
  2. UIAbility应用
  3. 网络案例
  4. ……

最后

鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

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

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

相关文章

GeoServer:忘记密码重置

操作步骤 1. 找到data_dir/security/usergroup/default目录下的users.xml文件&#xff0c; 2.修改password为plain:geoserver&#xff0c; 这里无论原来的密码是什么&#xff0c;改为plain:geoserver之后&#xff0c;就可以通过admin&#xff1a;geoserver默认账户密码登录了。…

室内定位中文综述阅读

1 室内高精度定位技术总结与展望 [4]柳景斌,赵智博,胡宁松等.室内高精度定位技术总结与展望[J].武汉大学学报(信息科学 版),2022,47(07):997-1008.DOI:10.13203/j.whugis20220029. 1.1.1 WiFi‐RTT定位 2016 年 12 月&#xff0c;随着新版 IEEE802.11 标准的公布&#xff0c…

开一家抖音小店都需要准备什么?今天一篇文章带你成功开店!

大家好&#xff0c;我是电商小布。 想要来玩抖店&#xff0c;第一件事情当然就是来开一家属于自己的抖店。 那么大家开抖店需要准备什么内容呢&#xff1f;这个是很多新手小伙伴不了解的东西。 所以呢今天&#xff0c;小布就从所需的材料和资金方面&#xff0c;来带大家详细的…

直播带货行业将迎来大地震

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 为什么这么多人喊着关闭直播带货?实体经济是到底因为什么萧条的?为什么大街上冷冷清清的?是房租、虚高的价格、还是直播带货引起的? 在4月9日的国务院政策吹风会上&#xff0c;市场监管明确指出&#xff1a; …

选择程序员是为什么?

本章节是关于为什么会选择一名程序员的经验分享 首先&#xff0c;我为什么会选择这个方向&#xff0c;可能是因为钱多&#xff0c;学东西不就是为了赚钱嘛&#xff1f;这是一点&#xff0c;不过最让我接收这个行业的是好奇世界的新大陆&#xff0c;可以简单的说就是&#xff0c…

ubuntu系统安装python虚拟环境

一、安装python&#xff1a; 步骤1&#xff1a;在Ubuntu系统中打开终端&#xff0c;你可以使用快捷键CtrlAltT来打开终端&#xff0c;或者在应用程序菜单中找到终端。 步骤2&#xff1a;更新软件包列表&#xff0c;在终端中输入以下命令&#xff0c;更新软件包列表&#xff1…

MobX原理剖析:基于可观察状态和自动依赖追踪的响应式状态管理

我们用代码示例来说明 MobX 的核心原理。 首先,我们定义一个简单的 Store 类,其中包含一个可观察的计数器状态: import { observable, action } from mobx;class CounterStore {observable count 0;actionincrement () > {this.count;};actiondecrement () > {this.…

Linux Shell:`alias`命令

Linux Shell&#xff1a;alias命令 alias命令是Linux和Unix系统中Shell的内置命令&#xff0c;用于创建命令的简短名称&#xff0c;即别名。这些别名通常用来缩短长命令或为常用命令序列创建便捷的缩写&#xff0c;从而提高工作效率。别名在当前Shell会话中有效&#xff0c;除…

Leetcode算法训练日记 | day20

一、合并二叉树 1.题目 Leetcode&#xff1a;第 617 题 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新…

基于SSM的电影网站(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的电影网站&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMv…

196算法之谜在 JSP 中使用内置对象 request 获取 form 表单的文本框 text 提交的数据。

(1&#xff09;编写 inputNumber . jsp &#xff0c;该页面提供一个 form 表单&#xff0c;该 form 表单提供一个文本框 text &#xff0c;用于用户输入一个正整数&#xff0c;用户在 form 表单中输入的数字&#xff0c;单击 submit 提交键将正整数提交给 huiwenNumber . jsp 页…

开源项目ChatGPT-Next-Web的容器化部署(四)-- k8s容器部署使用configmap配置

一、接着上文 本文的内容是在k8s容器中&#xff0c;如何使用configmap对.env文件进行挂载&#xff0c;实现环境的差异化配置。 二、源码结构 项目ChatGPT-Next-Web使用了.env文件来配置不同环境下的值&#xff1a; 所以&#xff0c;我们同理新增两个配置文件&#xff0c;见下…

windows上使用influx2.7学习

参考 官方文档&#xff1a;https://docs.influxdata.com/influxdb/v2/ 下载 需要下载两样东西&#xff1a;influxd.exe和influx.exe influxd:influx数据库的服务端。下载地址&#xff1a;https://dl.influxdata.com/influxdb/releases/influxdb2-2.7.5-windows.zipinflux:连…

Linux 计算机网络

目录 一、网络协议 1、 "协议" 是一种约定 2、协议分层 二、网络模型 1、OSI七层模型 2、TCP/IP五层(或四层)模型 三、网络传输基本流程 四、数据包封装和分用 五、网络中的地址管理 六、网络编程套接字 1、理解源IP地址和目的IP地址 2、端口号 理解 &q…

Prototype 原型

意图 用原型实例指定创建对象的种类&#xff0c;并且通过复制这些原型创建新的对象。 结构 Prototype声明一个复制自身的接口。ConcretePrototype实现一个复制自身的操作。Client让一个原型复制自身从而创建一个新的对象。 适用性 当一个系统应该独立于他的产品创建、构成和…

第四百五十三回

文章目录 1. 问题描述2. 优化方法2.1 缩小范围2.2 替代方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取AppBar的高度"相关的内容&#xff0c;本章回中将介绍关于MediaQuery的优化.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 问题描述 我们在…

微信小程序uniapp+vue电力巡线任务故障报修管理系统2q91t

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 前端开发:vue 语言&#xff1a;javapythonnodejsphp均支持 运行软件:idea/eclipse/vscode/pycharm/wamp均支持 框架支持:Ssm/django/flask/t…

《看漫画学C++》第12章 可大可小的“容器”——向量

在C编程的世界里&#xff0c;数组是一种基础且广泛使用的数据结构。然而&#xff0c;传统的静态数组在大小固定、管理不便等方面的局限性&#xff0c;常常让开发者感到束手束脚。幸运的是&#xff0c;C标准库中的vector类为我们提供了一种更加灵活、高效的动态数组解决方案。 …

Socks5代理IP使用教程

当我们在互联网上浏览网页、下载文件或者进行在线活动时&#xff0c;隐私和安全问题常常被提及。在这样的环境下&#xff0c;一个有效的解决方案是使用Sock5IP。本教程将向您介绍Sock5IP的使用方法&#xff0c;帮助您保护个人隐私并提升网络安全。 一、什么是Sock5IP&#xff1…

4月9号总结

java学习 一.steam流 1.介绍 Stream 是 Java 8 中引入的一种处理集合数据的新抽象。它提供了一种高效且便利的方式来处理集合中的元素&#xff0c;支持函数式编程的特性&#xff0c;使得集合操作变得更加简洁和灵活。 2.创建 List和Set可以直接调用接口的steam方法转换为流 …