0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

jf_46214456 来源:jf_46214456 作者:jf_46214456 2024-04-11 22:38 次阅读

介绍

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

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

GestureScreenshot.gif

相关概念

  • 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

// 区域截图最底层,当主页面缩放后会露出,设置为黑色
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;
    }
  }
}

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙
    +关注

    关注

    57

    文章

    2351

    浏览量

    42853
  • OpenHarmony
    +关注

    关注

    25

    文章

    3722

    浏览量

    16314
收藏 人收藏

    评论

    相关推荐

    HarmonyOS】应用开发文档

    /basic-fundamentals-0000000000041611快速入门补充该实例在新建工程时需要选择的设备类型和模板,避免开发者选择错误https://developer.harmonyos.com/cn/docs/d
    发表于 10-14 18:04

    HarmonyOS应用开发-ets基础手势LongPressGesture

    最小触发长按的时间,单位为毫秒(ms)。 2.事件名称功能描述onAction((event?: GestureEvent) => void)LongPress手势识别成功回调
    发表于 01-07 10:52

    HarmonyOS应用开发ets基础手势TapGesture

    。(说明:1. 当配置多指时,第一根手指按下后300毫秒(ms)内未有足够的手指数按下,手势识别失败。2. 实际点击手指数超过配置值,手势识别失败。)2.事件名称功能描述onAction((event
    发表于 01-08 12:18

    HarmonyOS应用API手势方法-绑定手势方法

    onAction((event?:GestureEvent) => void)Tap手势识别成功回调。多类枚举对象参考:https://developer.harmonyos.com/cn
    发表于 11-23 15:53

    HarmonyOS应用API手势方法-PanGesture

    描述:用于触发拖动手势事件,滑动的最小距离为5vp时拖动手势识别成功。Api:从API Version 7开始支持接口:PanGesture(value?: { fingers
    发表于 11-28 10:30

    STM32开发板能吗?

    STM32开发板能嘛?
    发表于 10-17 07:15

    华为荣耀8花式,4种方式你都知道吗?

    手机功能是大家常用到的,方法当然是越简单越好啦。华为荣耀8就有好几种方式,你都了解吗?看看哪种是你最喜欢的。第一种是同时按住音量下键和电源键约2秒,就能
    发表于 04-19 08:56 1w次阅读

    华为mate9手机怎么?华为mate9图实用教程分享

    华为mate9除了自身搭载强大的麒麟960,AI人工智能学习系统和莱卡双镜头,搭载人工智能的操作系统该如何?我们来汇总一下各种操作方法。比如传统的电源加音量键;指关节双击
    发表于 06-05 14:40 1.8w次阅读

    30款老产品用户正式体验EMUI 10.1,快速切换应用手势+单指关节超香

    小伙伴们耍出了花式操作;就连“单指关节”也能的搞出炫酷玩法。  快速切换应用手势化繁琐为“一滑”  EMUI 10.1在UX交互上做了大量的优化工作,比如基于人因研究成果的阻尼感动效,以及深受
    的头像 发表于 07-27 14:27 1799次阅读

    小米 11 支持指关节以及指关节画字母 V 启动自定义功能

    小米 11 使用 MIUI 12.0.8 稳定版本,进入设置 - 更多设置 - 快捷手势,可以看到指关节的选项。设备支持指关节双击
    的头像 发表于 01-06 15:56 7635次阅读

    鸿蒙系统功能在哪里

    在哪里? 1.双击 用手双击屏幕即可。启用方式:设置——辅助功能——快捷启动及手势——
    的头像 发表于 07-09 15:44 1.1w次阅读

    嵌入式Linux开发环境搭建-(7)嵌入式Linux开发工具gsnap移植

    嵌入式Linux开发工具gsnap移植PC机:ubuntu16.04.2 LTS开发板:i.MX6UL交叉编译器:arm-linux-gnueabihf-gcc (5.3.1 2
    发表于 11-01 17:38 12次下载
    嵌入式Linux<b class='flag-5'>开发</b>环境搭建-(7)嵌入式Linux<b class='flag-5'>开发</b>板<b class='flag-5'>截</b><b class='flag-5'>屏</b>工具gsnap移植

    屏幕功能

    屏幕功能
    发表于 05-26 15:25 15次下载

    关于LabVIEW如何实现

    群里的小伙伴问起了如何用LabVIEW去实现?那么就去实现一下咯。
    的头像 发表于 11-28 15:43 1.5w次阅读

    华为pockets怎么

    华为Pocket S的方法有以下几种。
    的头像 发表于 03-06 16:06 1932次阅读