0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

HarmonyOS開發(fā)實(shí)例:【手勢截屏】

jf_46214456 ? 來源:jf_46214456 ? 作者:jf_46214456 ? 2024-04-11 22:38 ? 次閱讀

介紹

本篇Codelab基于手勢處理和截屏能力,介紹了手勢截屏的實(shí)現(xiàn)過程。樣例主要包括以下功能:

  1. 根據(jù)下滑手勢調(diào)用全屏截圖功能。
  2. 全屏截圖,同時(shí)右下角有彈窗提示截圖成功。
  3. 根據(jù)雙擊手勢調(diào)用區(qū)域截圖功能。
  4. 區(qū)域截圖,通過調(diào)整選擇框大小完成。

GestureScreenshot.gif

相關(guān)概念

  • Canvas:畫布組件,用于自定義繪制圖形。
  • CanvasRenderingContext2D對(duì)象:使用RenderingContext在Canvas組件上進(jìn)行繪制,繪制對(duì)象可以是矩形、文本、圖片等。
  • 雙擊手勢:手指雙擊屏幕回調(diào)事件。
  • 手指滑動(dòng)手勢:手指在屏幕滑動(dòng)回調(diào)事件。

相關(guān)權(quán)限

  • 本篇Codelab用到屏幕截圖的能力,需要在配置文件module.json5里添加屏幕截圖的權(quán)限:ohos.permission.CAPTURE_SCREEN。
  • 本篇Codelab需要使用的screenshot為系統(tǒng)接口。需要使用Full SDK手動(dòng)從鏡像站點(diǎn)獲取,并在DevEco Studio中替換。
  • 本篇Codelab使用的部分API僅系統(tǒng)應(yīng)用可用,需要提升應(yīng)用等級(jí)為system_core。

環(huán)境搭建

軟件要求

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

硬件要求

  • 開發(fā)板類型:[潤和RK3568開發(fā)板]。
  • OpenHarmony系統(tǒng):3.2 Release。

環(huán)境搭建

完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進(jìn)行:

  1. [獲取OpenHarmony系統(tǒng)版本]:標(biāo)準(zhǔn)系統(tǒng)解決方案(二進(jìn)制)。以3.2 Release版本為例:
  2. 搭建燒錄環(huán)境。
    1. [完成DevEco Device Tool的安裝]
    2. [完成RK3568開發(fā)板的燒錄]
  3. 搭建開發(fā)環(huán)境。
    1. 開始前請(qǐng)參考[工具準(zhǔn)備][qr23.cn/AKFP8k],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
    2. 開發(fā)環(huán)境配置完成后,請(qǐng)參考[使用工程向?qū)[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md]創(chuàng)建工程(模板選擇“Empty Ability”)。
    3. 工程創(chuàng)建完成后,選擇使用[真機(jī)進(jìn)行調(diào)測]。

代碼結(jié)構(gòu)解讀

本篇Codelab只對(duì)核心代碼進(jìn)行講解,對(duì)于完整代碼,我們會(huì)在gitee中提供。

├──entry/src/main/ets           // 代碼區(qū)
│  ├──common
│  │  └──utils
│  │     ├──CommonConstants.ets	// 公共常量類
│  │     ├──DrawUtil.ets        // 畫布相關(guān)工具類
│  │     └──Logger.ets          // 日志打印類
│  ├──entryability
│  │  └──EntryAbility.ets       // 程序入口類
│  ├──model
│  │  └──OffsetModel.ets        // 區(qū)域截圖坐標(biāo)相關(guān)工具類
│  ├──pages
│  │  └──GestureScreenshot.ets  // 主界面	
│  └──view
│     ├──AreaScreenshot.ets     // 自定義區(qū)域截屏組件類
│     └──ScreenshotDialog.ets   // 自定義截屏顯示彈窗組件類
└──entry/src/main/resources     // 資源文件目錄

構(gòu)建截屏主頁面

使用下滑手勢,進(jìn)行全屏截圖并展示圖片。效果如圖所示:

主界面主要實(shí)現(xiàn)以下功能:

  1. 下滑手勢綁定在主界面上,雙擊手勢綁定在區(qū)域手勢的最底層Stack組件上。
  2. 如果使用下滑手勢,就進(jìn)行全屏截圖并展示圖片。
  3. 如果使用雙擊手勢,就喚起區(qū)域截圖相關(guān)組件。

搜狗高速瀏覽器截圖20240326151450.png

// 區(qū)域截圖最底層,當(dāng)主頁面縮放后會(huì)露出,設(shè)置為黑色
Stack() {
  // 主頁面布局
  Column() {
    ...
  })
  // 添加滑動(dòng)手勢事件
  .gesture(
    // fingers:觸發(fā)手指數(shù) direction:觸發(fā)方向 distance:觸發(fā)滑動(dòng)距離
    PanGesture({
      fingers: 1,
      direction: PanDirection.Down,
      distance: CommonConstants.MINIMUM_FINGER_DISTANCE
    })// 觸發(fā)開始回調(diào)
      .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)

  // 區(qū)域截圖相關(guān)組件
  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
      }
    })
)

構(gòu)建區(qū)域截圖組件

本章節(jié)將完成區(qū)域選擇框的繪制并完成區(qū)域截圖,效果如圖所示:

在繪制區(qū)域選擇框之前,首先需要在AreaScreenshot.ets的aboutToAppear方法中獲取屏幕的寬和高,并初始化offsetModel和drawUtil對(duì)象(初始化參數(shù)為屏幕的寬高)。offsetModel對(duì)輸入的坐標(biāo)進(jìn)行計(jì)算和更改,drawUtil使用offsetModel的坐標(biāo)在屏幕上繪制區(qū)域選擇框。

// 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)
      );

      // 在展示截圖的時(shí)候,用于計(jì)算圖片大小
      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. 根據(jù)手指按下的位置確定需要移動(dòng)的邊框。
  2. 手指移動(dòng)后,更新offsetModel記錄的坐標(biāo)信息
  3. 根據(jù)offsetModel提供的坐標(biāo),使用drawUtil繪制區(qū)域選擇框。
  4. 點(diǎn)擊保存按鈕后,設(shè)置截屏區(qū)域坐標(biāo)。
  5. 根據(jù)截屏區(qū)域坐標(biāo)進(jìn)行區(qū)域截屏。
// AreaScreenshot.ets
// 關(guān)閉區(qū)域截屏相關(guān)組件,并還原主頁面
private resetParameter() {
  this.showScreen = false;
  this.scaleNum = {
    x: CommonConstants.NO_SCALE_DOWN,
    y: CommonConstants.NO_SCALE_DOWN
  };
  offsetModel.resetDefaultOffSet();
}

// 使用if渲染,控制區(qū)域截圖相關(guān)組件的顯隱
if (this.showScreen) {
  Stack() {
    Canvas(this.canvasRenderingContext)
      ...
      .onReady(() = > {
        // 通過draw方法繪制選擇框和非高亮區(qū)域
        drawUtil.draw();
      })
    // 截圖的工具欄
    Row() {
      ...
      // 區(qū)域截圖并展示圖像
      Image($r('app.media.ic_save'))
        .onClick(() = > {
          let screenshotOptions: screenshot.ScreenshotOptions = {
            // 截屏區(qū)域Rect參數(shù)
            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();
        })
    }
    ...
    // 根據(jù)手指位置調(diào)整選擇框大小和位置
    .onTouch((event: TouchEvent) = > {
      switch(event.type) {
        case TouchType.Down:
          // 根據(jù)手指位置,判斷移動(dòng)哪個(gè)坐標(biāo)
          offsetModel.setXLocationType(event.touches[0].screenX);
          offsetModel.setYLocationType(event.touches[0].screenY);
          break;
        case TouchType.Move:
          // 更新坐標(biāo)信息,并保證坐標(biāo)值合法
          offsetModel.resetOffsetXY(event.touches[0].screenX, event.touches[0].screenY);
          drawUtil.draw();
          break;
        default:
          break;
      }
    })
}

區(qū)域選擇框工具類的實(shí)現(xiàn)

在構(gòu)建區(qū)域截圖組件中介紹了OffsetModel和DrawUtil兩個(gè)工具類,本章節(jié)介紹一下具體的實(shí)現(xiàn)步驟。

使用OffsetModel校驗(yàn)坐標(biāo)的范圍,并保存坐標(biāo)相關(guān)信息。

  1. 在初始化對(duì)象的時(shí)候,根據(jù)屏幕的縮放比例計(jì)算出黑色區(qū)域的寬高。
  2. 使用setXLocationType方法和setYLocationType方法,判斷需要移動(dòng)的x、y坐標(biāo)位置。
  3. 根據(jù)傳入的x、y坐標(biāo)值,更改offset對(duì)應(yīng)的坐標(biāo)值,并保證選擇框的寬高大于等于預(yù)設(shè)的選擇框的最小值。
  4. 再次校驗(yàn)offset坐標(biāo)值,是否超出可截屏區(qū)域。
// 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坐標(biāo)位置
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坐標(biāo)位置
public setYLocationType(offsetY: number) {
  ...
}

// 根據(jù)參數(shù)改變坐標(biāo)值
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();
}

// 再次校驗(yàn)坐標(biāo)值,是否超出可截屏區(qū)域
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主要提供繪制方法,用于繪制區(qū)域選擇框。

// DrawUtil.ets
// 繪制整個(gè)區(qū)域選擇框
public draw() {
  this.offsetXLeft = offsetModel.getXLeft();
  this.offsetXRight = offsetModel.getXRight();
  this.offsetYTop = offsetModel.getYTop();
  this.offsetYBottom = offsetModel.getYBottom();
  
  // 填充非高亮區(qū)域
  this.drawScreenSelection();
  // 繪制框選線
  this.drawLines();
}

// 填充非高亮區(qū)域,設(shè)置回形區(qū)域并填充顏色
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方法中計(jì)算對(duì)應(yīng)的寬度:

  1. 截圖長寬比小于或者等于屏幕長寬比:此截圖展示時(shí)和全屏截圖展示時(shí)等寬。
  2. 截圖長寬比大于屏幕長寬比:此截圖展示時(shí)和全屏截圖展示時(shí)等長,通過計(jì)算對(duì)應(yīng)的寬來實(shí)現(xiàn)。
// 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;
    }
  }
}

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 鴻蒙
    +關(guān)注

    關(guān)注

    56

    文章

    2267

    瀏覽量

    42485
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3548

    瀏覽量

    15736
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    HarmonyOS】應(yīng)用開發(fā)文檔

    /basic-fundamentals-0000000000041611快速入門補(bǔ)充該實(shí)例在新建工程時(shí)需要選擇的設(shè)備類型和模板,避免開發(fā)者選擇錯(cuò)誤https://developer.harmonyos.com/cn/docs/d
    發(fā)表于 10-14 18:04

    HarmonyOS應(yīng)用開發(fā)-ets基礎(chǔ)手勢LongPressGesture

    最小觸發(fā)長按的時(shí)間,單位為毫秒(ms)。 2.事件名稱功能描述onAction((event?: GestureEvent) => void)LongPress手勢識(shí)別成功回調(diào)
    發(fā)表于 01-07 10:52

    HarmonyOS應(yīng)用開發(fā)ets基礎(chǔ)手勢TapGesture

    。(說明:1. 當(dāng)配置多指時(shí),第一根手指按下后300毫秒(ms)內(nèi)未有足夠的手指數(shù)按下,手勢識(shí)別失敗。2. 實(shí)際點(diǎn)擊手指數(shù)超過配置值,手勢識(shí)別失敗。)2.事件名稱功能描述onAction((event
    發(fā)表于 01-08 12:18

    HarmonyOS應(yīng)用API手勢方法-綁定手勢方法

    onAction((event?:GestureEvent) => void)Tap手勢識(shí)別成功回調(diào)。多類枚舉對(duì)象參考:https://developer.harmonyos.com/cn
    發(fā)表于 11-23 15:53

    HarmonyOS應(yīng)用API手勢方法-PanGesture

    描述:用于觸發(fā)拖動(dòng)手勢事件,滑動(dòng)的最小距離為5vp時(shí)拖動(dòng)手勢識(shí)別成功。Api:從API Version 7開始支持接口:PanGesture(value?: { fingers
    發(fā)表于 11-28 10:30

    STM32開發(fā)板能嗎?

    STM32開發(fā)板能嘛?
    發(fā)表于 10-17 07:15

    華為榮耀8花式,4種方式你都知道嗎?

    手機(jī)功能是大家常用到的,方法當(dāng)然是越簡單越好啦。華為榮耀8就有好幾種方式,你都了解嗎?看看哪種是你最喜歡的。第一種是同時(shí)按住音量下鍵和電源鍵約2秒,就能
    發(fā)表于 04-19 08:56 ?1w次閱讀

    華為mate9手機(jī)怎么?華為mate9圖實(shí)用教程分享

    華為mate9除了自身搭載強(qiáng)大的麒麟960,AI人工智能學(xué)習(xí)系統(tǒng)和萊卡雙鏡頭,搭載人工智能的操作系統(tǒng)該如何?我們來匯總一下各種操作方法。比如傳統(tǒng)的電源加音量鍵;指關(guān)節(jié)雙擊
    發(fā)表于 06-05 14:40 ?1.8w次閱讀

    30款老產(chǎn)品用戶正式體驗(yàn)EMUI 10.1,快速切換應(yīng)用手勢+單指關(guān)節(jié)超香

    小伙伴們耍出了花式操作;就連“單指關(guān)節(jié)”也能的搞出炫酷玩法?! 】焖偾袚Q應(yīng)用手勢化繁瑣為“一滑”  EMUI 10.1在UX交互上做了大量的優(yōu)化工作,比如基于人因研究成果的阻尼感動(dòng)效,以及深受
    的頭像 發(fā)表于 07-27 14:27 ?1733次閱讀

    小米 11 支持指關(guān)節(jié)以及指關(guān)節(jié)畫字母 V 啟動(dòng)自定義功能

    小米 11 使用 MIUI 12.0.8 穩(wěn)定版本,進(jìn)入設(shè)置 - 更多設(shè)置 - 快捷手勢,可以看到指關(guān)節(jié)的選項(xiàng)。設(shè)備支持指關(guān)節(jié)雙擊、
    的頭像 發(fā)表于 01-06 15:56 ?7155次閱讀

    鴻蒙系統(tǒng)功能在哪里

    在哪里? 1.雙擊 用手雙擊屏幕即可。啟用方式:設(shè)置——輔助功能——快捷啟動(dòng)及手勢——
    的頭像 發(fā)表于 07-09 15:44 ?1.1w次閱讀

    嵌入式Linux開發(fā)環(huán)境搭建-(7)嵌入式Linux開發(fā)工具gsnap移植

    嵌入式Linux開發(fā)工具gsnap移植PC機(jī):ubuntu16.04.2 LTS開發(fā)板:i.MX6UL交叉編譯器:arm-linux-gnueabihf-gcc (5.3.1 2
    發(fā)表于 11-01 17:38 ?12次下載
    嵌入式Linux<b class='flag-5'>開發(fā)</b>環(huán)境搭建-(7)嵌入式Linux<b class='flag-5'>開發(fā)</b>板<b class='flag-5'>截</b><b class='flag-5'>屏</b>工具gsnap移植

    屏幕功能

    屏幕功能
    發(fā)表于 05-26 15:25 ?15次下載

    關(guān)于LabVIEW如何實(shí)現(xiàn)

    群里的小伙伴問起了如何用LabVIEW去實(shí)現(xiàn)?那么就去實(shí)現(xiàn)一下咯。
    的頭像 發(fā)表于 11-28 15:43 ?1.3w次閱讀

    華為pockets怎么

    華為Pocket S的方法有以下幾種。
    的頭像 發(fā)表于 03-06 16:06 ?1344次閱讀