diff --git a/ArkUI/entry/src/main/ets/pages/ComponentScreenshot.ets b/ArkUI/entry/src/main/ets/pages/ComponentScreenshot.ets new file mode 100644 index 0000000000000000000000000000000000000000..62820771a23288f072eddec679c03623b8b720dc --- /dev/null +++ b/ArkUI/entry/src/main/ets/pages/ComponentScreenshot.ets @@ -0,0 +1,185 @@ +/* +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* +* FAQ:组件截图(ComponentSnapshot)返回错误码100001,可能原因为截图尺寸过大,文档说明其与具体硬件限制有关,如何查看具体限制是多少 +*/ + +// [Start component_screenshot] +import { image } from '@kit.ImageKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Utils } from '../utils/Utils'; + +export class MyDataSource { + private _data: string[] = []; + private _listeners: DataChangeListener[] = []; + + pushData(data: string): void { + this._data.push(data); + this._listeners.forEach(listener => { + listener.onDataAdd(this._data.length - 1); + }) + } + + getAllData(): string[] { + return this._data; + } + + totalCount(): number { + return this._data.length; + } + + getData(index: number): string { + return this._data[index]; + } + + registerDataChangeListener(listener: DataChangeListener): void { + this._listeners.push(listener); + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const index = this._listeners.indexOf(listener); + if (index != -1) { + this._listeners.splice(index, 1); + } + } +} + +@Entry +@Component +struct SnapshotExample { + private scroller: Scroller = new Scroller(); + private listComponentWidth: number = 0; + private listComponentHeight: number = 0; + @State mergedImage: PixelMap | undefined = undefined; + private areaArray: image.PositionArea[] = []; + private scrollYOffsets: number[] = []; + private data: MyDataSource = new MyDataSource(); + private listId: string = 'LIST_ID'; + + aboutToAppear(): void { + for (let i = 0; i < 50; i++) { + this.data.pushData(`Hello ${i}`); + } + } + + async onceSnapshot() { + await this.beforeSnapshot(); + await this.snapAndMerge(); + this.afterGeneratorImage(); + } + + async snapAndMerge() { + try { + // Record the current scrolling position + this.scrollYOffsets.push(this.scroller.currentOffset().yOffset); + // Take a screenshot of the current display part of the component + const pixelMap = await this.getUIContext().getComponentSnapshot().get(this.listId); + // Calculate the valid screenshot area + let area: image.PositionArea = + await Utils.getSnapshotArea(this.getUIContext(), pixelMap, this.scrollYOffsets, this.listComponentWidth, + this.listComponentHeight); + this.areaArray.push(area); + // Determine whether to scroll to the bottom + if (!this.scroller.isAtEnd()) { + // Not to the bottom: Scroll down by one screen height + this.scroller.scrollTo({ + xOffset: 0, + yOffset: (this.scroller.currentOffset().yOffset + this.listComponentHeight), + animation: { + duration: 200 + } + }); + await Utils.sleep(200); + await this.snapAndMerge(); + } else { + this.mergedImage = + await Utils.mergeImage(this.getUIContext(), this.areaArray, this.scrollYOffsets[this.scrollYOffsets.length-1], + this.listComponentWidth, this.listComponentHeight); + } + } catch (err) { + let error = err as BusinessError; + console.error(`snapAndMerge err, code:${error.code}, message: ${error.message}`); + } + } + + async beforeSnapshot() { + try { + this.scroller.scrollTo({ + xOffset: 0, + yOffset: 0, + animation: { + duration: 200 + } + }); + await Utils.sleep(200); + } catch (err) { + let error = err as BusinessError; + console.error(`beforeSnapshot err, code:${error.code}, message: ${error.message}`); + } + } + + afterGeneratorImage() { + this.scrollYOffsets.length = 0; + this.areaArray.length = 0; + } + + build() { + Column({ space: 12 }) { + Button('Click to get the snapshot') + .onClick(() => { + this.onceSnapshot(); + }) + Stack() { + // Screenshot component + List({ space: 12, scroller: this.scroller }) { + LazyForEach(this.data, (item: string) => { + ListItem() { + Row() { + Text(item) + .fontSize(50) + .height(50) + } + } + }, (item: number) => item.toString()) + } + .scrollBar(BarState.Off) + .cachedCount(3) + .width('100%') + .height('100%') + .backgroundColor($r('sys.color.background_secondary')) + .id(this.listId) + .onAreaChange((oldValue, newValue) => { + this.listComponentWidth = newValue.width as number; + this.listComponentHeight = newValue.height as number; + }) + // Set the Z-sequence to -1 to ensure that this component is invisible + .zIndex(-1) + + // Use a mask to cover the screenshot area + Column() + .width('100%').height('100%').backgroundColor(Color.White) + // Long screenshot + Scroll() { + Image(this.mergedImage) + } + } + .width('100%') + .layoutWeight(1) + } + } +} + +// [End component_screenshot] \ No newline at end of file diff --git a/ArkUI/entry/src/main/ets/utils/Utils.ets b/ArkUI/entry/src/main/ets/utils/Utils.ets new file mode 100644 index 0000000000000000000000000000000000000000..089419779ca7a522d8d0863788f8d3b3cc08ee5a --- /dev/null +++ b/ArkUI/entry/src/main/ets/utils/Utils.ets @@ -0,0 +1,119 @@ +/* +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* +* FAQ:组件截图(ComponentSnapshot)返回错误码100001,可能原因为截图尺寸过大,文档说明其与具体硬件限制有关,如何查看具体限制是多少 +*/ + +// [Start component_screenshot] +import { image } from '@kit.ImageKit'; +import { BusinessError } from '@kit.BasicServicesKit'; + +export class Utils { + static sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + // Calculate the valid screenshot area + static async getSnapshotArea(context: UIContext, pixelMap: PixelMap, scrollYOffsets: number[], listWidth: number, + listHeight: number): Promise { + let stride = pixelMap.getBytesNumberPerRow(); + let bytesNumber = pixelMap.getPixelBytesNumber(); + let buffer: ArrayBuffer = new ArrayBuffer(bytesNumber); + let len = scrollYOffsets.length; + + if (scrollYOffsets.length >= 2) { + let realScrollHeight = scrollYOffsets[len-1] - scrollYOffsets[len-2]; + if (listHeight - realScrollHeight > 0) { + let cropRegion: image.Region = { + x: 0, + y: context.vp2px(listHeight - realScrollHeight) || 0, + size: { + height: context.vp2px(realScrollHeight) || 0, + width: context.vp2px(listWidth) || 0 + } + }; + await pixelMap.crop(cropRegion); + } + } + + let area: image.PositionArea = { + pixels: buffer, + offset: 0, + stride: stride, + region: { + size: { + width: 0, + height: 0 + }, + x: 0, + y: 0 + } + } + + try { + let imgInfo = pixelMap.getImageInfoSync(); + area.region.size.width = imgInfo.size.width; + area.region.size.height = imgInfo.size.height; + pixelMap.readPixelsSync(area); + } catch (err) { + let error = err as BusinessError; + console.error(`getSnapshotArea err, code:${error.code}, message: ${error.message}`); + } + return area; + } + + // Graphic splicing + static async mergeImage(context: UIContext, areaArray: image.PositionArea[], lastOffsetY: number, listWidth: number, + listHeight: number): Promise { + let opts: image.InitializationOptions = { + editable: true, + pixelFormat: 4, + size: { + width: context.vp2px(listWidth) || 0, + height: context.vp2px(lastOffsetY + listHeight) || 0 + } + }; + let longPixelMap = image.createPixelMapSync(opts); + let imgPosition: number = 0; + + for (let i = 0; i < areaArray.length; i++) { + let readArea = areaArray[i]; + let area: image.PositionArea = { + pixels: readArea.pixels, + offset: 0, + stride: readArea.stride, + region: { + size: { + width: readArea.region.size.width, + height: readArea.region.size.height + }, + x: 0, + y: imgPosition + } + } + imgPosition += readArea.region.size.height; + try { + longPixelMap.writePixelsSync(area); + } catch (err) { + let error = err as BusinessError; + console.error(`writePixelsSync err, code:${error.code}, message: ${error.message}`); + } + } + return longPixelMap; + } +} + +// [End component_screenshot]