当前位置:   article > 正文

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

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中提供。

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

构建截屏主页面

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

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

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

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

  1. // GestureScreenshot.ets
  2. // 区域截图最底层,当主页面缩放后会露出,设置为黑色
  3. Stack() {
  4. // 主页面布局
  5. Column() {
  6. ...
  7. })
  8. // 添加滑动手势事件
  9. .gesture(
  10. // fingers:触发手指数 direction:触发方向 distance:触发滑动距离
  11. PanGesture({
  12. fingers: 1,
  13. direction: PanDirection.Down,
  14. distance: CommonConstants.MINIMUM_FINGER_DISTANCE
  15. })// 触发开始回调
  16. .onActionStart(() => {
  17. let screenshotOptions: screenshot.ScreenshotOptions = {
  18. rotation: 0
  19. };
  20. screenshot.save(screenshotOptions, (err: Error, data: image.PixelMap) => {
  21. if (err) {
  22. Logger.error(`Failed to save the screenshot. Error:${ JSON.stringify(err) }`);
  23. }
  24. if (this.pixelMap !== undefined) {
  25. this.pixelMap.release();
  26. }
  27. this.pixelMap = data;
  28. this.dialogController.open();
  29. });
  30. })
  31. )
  32. .scale(this.scaleNum)
  33. // 区域截图相关组件
  34. AreaScreenshot({ showScreen: this.showScreen, pixelMap: this.pixelMap, scaleNum: this.scaleNum })
  35. }
  36. .backgroundColor($r('app.color.black_area'))
  37. // 添加双击手势事件
  38. .gesture(
  39. TapGesture({ count: 2 })
  40. .onAction(() => {
  41. this.showScreen = true;
  42. this.scaleNum = {
  43. x: CommonConstants.X_SCALE_DOWN,
  44. y: CommonConstants.Y_SCALE_DOWN
  45. }
  46. })
  47. )

构建区域截图组件

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

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

  1. // AreaScreenshot.ets
  2. aboutToAppear() {
  3. window.getLastWindow(getContext(this))
  4. .then((window) => {
  5. let property = window.getWindowProperties();
  6. this.systemBarHeight = property.windowRect.top;
  7. drawUtil.initDrawUtil(
  8. this.canvasRenderingContext,
  9. px2vp(property.windowRect.width),
  10. px2vp(property.windowRect.height)
  11. );
  12. offsetModel.initOffsetModel(
  13. px2vp(property.windowRect.width),
  14. px2vp(property.windowRect.height)
  15. );
  16. // 在展示截图的时候,用于计算图片大小
  17. this.screenAspectRatio = px2vp(property.windowRect.height) / px2vp(property.windowRect.width);
  18. })
  19. .catch((err: Error) => {
  20. Logger.error(`window loading has error: ${ JSON.stringify(err) }`);
  21. })
  22. }

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

  1. 根据手指按下的位置确定需要移动的边框。
  2. 手指移动后,更新offsetModel记录的坐标信息。
  3. 根据offsetModel提供的坐标,使用drawUtil绘制区域选择框。
  4. 点击保存按钮后,设置截屏区域坐标。
  5. 根据截屏区域坐标进行区域截屏。
  1. // AreaScreenshot.ets
  2. // 关闭区域截屏相关组件,并还原主页面
  3. private resetParameter() {
  4. this.showScreen = false;
  5. this.scaleNum = {
  6. x: CommonConstants.NO_SCALE_DOWN,
  7. y: CommonConstants.NO_SCALE_DOWN
  8. };
  9. offsetModel.resetDefaultOffSet();
  10. }
  11. // 使用if渲染,控制区域截图相关组件的显隐
  12. if (this.showScreen) {
  13. Stack() {
  14. Canvas(this.canvasRenderingContext)
  15. ...
  16. .onReady(() => {
  17. // 通过draw方法绘制选择框和非高亮区域
  18. drawUtil.draw();
  19. })
  20. // 截图的工具栏
  21. Row() {
  22. ...
  23. // 区域截图并展示图像
  24. Image($r('app.media.ic_save'))
  25. .onClick(() => {
  26. let screenshotOptions: screenshot.ScreenshotOptions = {
  27. // 截屏区域Rect参数
  28. screenRect: {
  29. left: vp2px(offsetModel.getXLeft()),
  30. top: vp2px(offsetModel.getYTop()) + this.systemBarHeight,
  31. width: vp2px(offsetModel.getWidth()),
  32. height: vp2px(offsetModel.getHeight())
  33. } as screenshot.Rect,
  34. // 截图的大小
  35. imageSize: {
  36. width: vp2px(offsetModel.getWidth()),
  37. height: vp2px(offsetModel.getHeight())
  38. } as screenshot.Size,
  39. rotation: 0,
  40. displayId: 0
  41. };
  42. screenshot.save(screenshotOptions, (err: Error, data: image.PixelMap) => {
  43. if (err) {
  44. Logger.error(`Failed to save the screenshot. Error:${JSON.stringify(err)}`);
  45. }
  46. if (this.pixelMap !== undefined) {
  47. this.pixelMap.release();
  48. }
  49. this.pixelMap = data;
  50. // 使用弹窗组件展示截完的图片
  51. this.dialogController.open();
  52. });
  53. this.resetParameter();
  54. })
  55. }
  56. ...
  57. // 根据手指位置调整选择框大小和位置
  58. .onTouch((event: TouchEvent) => {
  59. switch(event.type) {
  60. case TouchType.Down:
  61. // 根据手指位置,判断移动哪个坐标
  62. offsetModel.setXLocationType(event.touches[0].screenX);
  63. offsetModel.setYLocationType(event.touches[0].screenY);
  64. break;
  65. case TouchType.Move:
  66. // 更新坐标信息,并保证坐标值合法
  67. offsetModel.resetOffsetXY(event.touches[0].screenX, event.touches[0].screenY);
  68. drawUtil.draw();
  69. break;
  70. default:
  71. break;
  72. }
  73. })
  74. }

区域选择框工具类的实现

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

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

  1. 在初始化对象的时候,根据屏幕的缩放比例计算出黑色区域的宽高。
  2. 使用setXLocationType方法和setYLocationType方法,判断需要移动的x、y坐标位置。
  3. 根据传入的x、y坐标值,更改offset对应的坐标值,并保证选择框的宽高大于等于预设的选择框的最小值。
  4. 再次校验offset坐标值,是否超出可截屏区域。
  1. // OffsetModel.ets
  2. public initOffsetModel(width: number, height: number) {
  3. ...
  4. this.blackAreaWidth = this.screenWidth * (1 - CommonConstant.X_SCALE_DOWN);
  5. this.blackAreaWidth = this.blackAreaWidth / CommonConstant.BLACK_AREA_NUM;
  6. this.blackAreaHeight = this.screenHeight * (1 - CommonConstant.Y_SCALE_DOWN);
  7. this.blackAreaHeight = this.blackAreaHeight / CommonConstant.BLACK_AREA_NUM;
  8. }
  9. // 判断x坐标位置
  10. public setXLocationType(offsetX: number) {
  11. if (offsetX > this.offsetXRight - CommonConstant.OFFSET_RANGE &&
  12. offsetX < this.offsetXRight + CommonConstant.OFFSET_RANGE) {
  13. this.xLocationType = XLocationEnum.XRight;
  14. } else if (offsetX > this.offsetXLeft - CommonConstant.OFFSET_RANGE &&
  15. offsetX < this.offsetXLeft + CommonConstant.OFFSET_RANGE) {
  16. this.xLocationType = XLocationEnum.XLeft;
  17. } else {
  18. this.xLocationType = XLocationEnum.noChange;
  19. }
  20. }
  21. // 判断y坐标位置
  22. public setYLocationType(offsetY: number) {
  23. ...
  24. }
  25. // 根据参数改变坐标值
  26. public resetOffsetXY(offsetX: number, offsetY: number) {
  27. if (this.xLocationType === XLocationEnum.XLeft) {
  28. this.offsetXLeft = this.offsetXRight - offsetX < CommonConstant.OFFSET_RANGE * 2 ?
  29. this.offsetXLeft : offsetX;
  30. }
  31. ...
  32. this.checkOffsetXY();
  33. }
  34. // 再次校验坐标值,是否超出可截屏区域
  35. private checkOffsetXY() {
  36. this.offsetXLeft = this.offsetXLeft < this.blackAreaWidth ? this.blackAreaWidth : this.offsetXLeft;
  37. this.offsetXRight = this.offsetXRight > this.screenWidth - this.blackAreaWidth ?
  38. this.screenWidth - this.blackAreaWidth : this.offsetXRight;
  39. this.offsetYTop = this.offsetYTop < this.blackAreaHeight ? this.blackAreaHeight : this.offsetYTop;
  40. this.offsetYBottom = this.offsetYBottom > this.screenHeight - this.blackAreaHeight ?
  41. this.screenHeight - this.blackAreaHeight : this.offsetYBottom;
  42. }

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

  1. // DrawUtil.ets
  2. // 绘制整个区域选择框
  3. public draw() {
  4. this.offsetXLeft = offsetModel.getXLeft();
  5. this.offsetXRight = offsetModel.getXRight();
  6. this.offsetYTop = offsetModel.getYTop();
  7. this.offsetYBottom = offsetModel.getYBottom();
  8. // 填充非高亮区域
  9. this.drawScreenSelection();
  10. // 绘制框选线
  11. this.drawLines();
  12. }
  13. // 填充非高亮区域,设置回形区域并填充颜色
  14. private drawScreenSelection() {
  15. this.canvasContext.clearRect(0, 0, this.screenWidth, this.screenHeight)
  16. this.canvasContext.beginPath();
  17. this.canvasContext.moveTo(0, 0);
  18. this.canvasContext.lineTo(this.screenWidth, 0);
  19. this.canvasContext.lineTo(this.screenWidth, this.screenHeight);
  20. this.canvasContext.lineTo(0, this.screenHeight);
  21. this.canvasContext.closePath();
  22. this.canvasContext.moveTo(this.offsetXRight, this.offsetYTop);
  23. this.canvasContext.lineTo(this.offsetXLeft, this.offsetYTop);
  24. this.canvasContext.lineTo(this.offsetXLeft, this.offsetYBottom);
  25. this.canvasContext.lineTo(this.offsetXRight, this.offsetYBottom);
  26. this.canvasContext.globalAlpha = Constants.UNSELECT_AREA_ALPHA;
  27. this.canvasContext.fillStyle = Constants.UNSELECT_AREA_COLOR;
  28. this.canvasContext.closePath();
  29. this.canvasContext.fill();
  30. }
  31. // 绘制框选线
  32. private drawLines() {
  33. this.canvasContext.beginPath();
  34. ...
  35. this.canvasContext.moveTo(
  36. (this.offsetXLeft + Constants.LINES_MAX_LENGTH),
  37. (this.offsetYTop - Constants.GAP_WIDTH)
  38. );
  39. this.canvasContext.lineTo(
  40. (this.offsetXLeft - Constants.GAP_WIDTH),
  41. (this.offsetYTop - Constants.GAP_WIDTH)
  42. );
  43. this.canvasContext.lineTo(
  44. (this.offsetXLeft - Constants.GAP_WIDTH),
  45. (this.offsetYTop + Constants.LINES_MAX_LENGTH)
  46. );
  47. ...
  48. this.canvasContext.stroke();
  49. }

展示截图

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

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

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

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

自己学习时必须要有一份实用的鸿蒙(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 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/578739
推荐阅读
相关标签
  

闽ICP备14008679号