HarmonyOS鸿蒙Next中如何解决使用组件componentSnapshot长列表截图不全问题

HarmonyOS鸿蒙Next中如何解决使用组件componentSnapshot长列表截图不全问题

【问题现象】

将一个列表的视图内容保存到本地一张图片,使用组件componentSnapshot.get以及componentSnapshot.createFromBuilder都无法获取视图完整内容;例如Scroll组件有2屏的内容,需要将2屏的内容截图保存,componentSnapshot截不完整。

问题现象如下:

点击放大

【定位思路】

首先需确认ohos.arkui.componentSnapshot模块提供获取组件截图的能力,是否能截取超过组件区域的内容。

【解决方案】

  1. 截长图需要使用滚动拼接,并计算好滚动距离,拼接的代码如下:
// 长屏拼接截图
createSnap = async () => {
  let i = 0
  
  // 这个判断条件可以优化,根据scroll的实际长度和一屏长度,计算次数
  while (!this.scroller.isAtEnd()) {
    // 一定要放在最上面
    this.scroller.scrollPage({ next: true })
    // 默认单位都是vp
    // 图片资源,绘制区域左上角在x轴的位置,绘制区域左上角在y 轴的位置,绘制区域的宽度,绘制区域的高度
    let curSnip: image.PixelMap = await componentSnapshot.get("builder")
    this.offContext.drawImage(curSnip, 0, this.ScrollHeight * i, this.scrollWidth, 2000);
    i++
  }
  this.pixmap = this.offContext.getPixelMap(0, 0, this.screenWidth, this.screenHeight); // 2100也可以优化,我这里随便写的数字
}

拼接完成后,将图片保存即可。

完整流程如下:

import componentSnapshot from '@ohos.arkui.componentSnapshot'
import image from '@ohos.multimedia.image'
import { common } from '@kit.AbilityKit';
import window from '@ohos.window';

@Entry
@Component
struct Index {
  private context = getContext(this) as common.UIAbilityContext;
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  private offCanvas: OffscreenCanvas = new OffscreenCanvas(600, 2100)
  private offContext = this.offCanvas.getContext("2d", this.settings)
  @State pixmap: image.PixelMap | undefined = undefined
  @State scrollWidth: number = 0
  @State ScrollHeight: number = 0
  @State screenWidth: number = 0
  @State screenHeight: number = 0
  @State shareStas: boolean = false
  scroller: Scroller = new Scroller()
  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  aboutToAppear(): void {

    window.getLastWindow(this.context, (err, data) => {
      if (err.code) {
        console.error('Failed to obtain the top window. Cause: ' + JSON.stringify(err));
        return;
      }

      let properties = data.getWindowProperties();
      // 单位px 转成 vp
      this.screenWidth = px2vp(properties.windowRect.width)
      this.screenHeight = px2vp(properties.windowRect.height)

      console.info('window. Data: ' + this.screenWidth + '-------' + this.screenHeight);
    });
  }

  // 长屏拼接截图
  createSnap = async () => {
    let i = 0

    // 这个判断条件可以优化,根据scroll的实际长度和一屏长度,计算次数
    while (!this.scroller.isAtEnd()) {
      // 一定要放在最上面
      this.scroller.scrollPage({ next: true })
      // 默认单位都是vp
      // 图片资源,绘制区域左上角在x轴的位置,绘制区域左上角在y 轴的位置,绘制区域的宽度,绘制区域的高度
      let curSnip: image.PixelMap = await componentSnapshot.get("builder")
      this.offContext.drawImage(curSnip, 0, this.ScrollHeight * i, this.scrollWidth, this.ScrollHeight);
      i++
    }
    this.pixmap = this.offContext.getPixelMap(0, 0, this.screenWidth, 2100); // 2100也可以优化,我这里随便写的数字
  }

  build() {
    Stack() {
      Scroll(this.scroller) {
        Column() {
          ForEach(this.arr, (item: number) => {
            Text(item.toString())
              .width('90%')
              .height(150)
              .backgroundColor(Color.Orange)
              .borderRadius(15)
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .margin({ top: 10 })
          }, (item: string) => item)
        }.width('100%')
      }
      .scrollable(ScrollDirection.Vertical) // 滚动方向纵向
      .scrollBar(BarState.On) // 滚动条常驻显示
      .scrollBarColor(Color.Gray) // 滚动条颜色
      .scrollBarWidth(10) // 滚动条宽度
      .friction(0.6)
      .edgeEffect(EdgeEffect.None)
      .onScrollEdge((side: Edge) => {
        console.info('To the edge')
      })
      .onReachStart(() => {
        // init会默认触发一次,shareStas 标记
        if (this.shareStas) {
          this.createSnap()
        }
        console.log('onReachStart----------')
      })
      .onScrollStop(() => {
        console.info('Scroll Stop')
      })
      .onAreaChange((oldValue: Area, newValue: Area) => {
        // 单位是vp
        this.scrollWidth = newValue.width as number;
        this.ScrollHeight = newValue.height as number
        console.log(`Scroll component width: ${this.scrollWidth}, height: ${this.ScrollHeight}`);
      })
      .id("builder")

      Scroll() {
        Column() {
          Button('生成截图')
            .onClick(() => {
              const yOffset: number = this.scroller.currentOffset().yOffset;
              console.log('yOffset----', yOffset)
              if (yOffset !== 0) {
                // scroll 没有置顶
                this.shareStas = true;
                this.scroller.scrollEdge(Edge.Top)
              } else {
                this.createSnap()
              }
            })
          Image(this.pixmap)
            .border({ color: Color.Black, width: 2 })
        }
      }.backgroundColor(Color.Red)
    }
  }
}

点击放大

【总结】

使用代码实现长截图,可参考上述Demo,滚动拼接方式实现;

若有Web组件网页长截图,可参考Web组件长截图方案


更多关于HarmonyOS鸿蒙Next中如何解决使用组件componentSnapshot长列表截图不全问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中如何解决使用组件componentSnapshot长列表截图不全问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,使用componentSnapshot截取长列表时,若出现截图不全问题,可通过滚动拼接方式解决。具体步骤如下:

  1. 使用Scroller组件控制滚动,逐屏截取内容。
  2. 通过componentSnapshot.get获取每屏截图,并利用OffscreenCanvas进行拼接。
  3. 计算滚动距离,确保每屏截图无缝拼接。
  4. 最终将拼接后的图片保存。

示例代码:

createSnap = async () => {
  let i = 0
  while (!this.scroller.isAtEnd()) {
    this.scroller.scrollPage({ next: true })
    let curSnip: image.PixelMap = await componentSnapshot.get("builder")
    this.offContext.drawImage(curSnip, 0, this.ScrollHeight * i, this.scrollWidth, this.ScrollHeight);
    i++
  }
  this.pixmap = this.offContext.getPixelMap(0, 0, this.screenWidth, 2100);
}

完整流程可参考上述代码实现。

回到顶部