功能概述与设计目标

我们的目标是实现“一键生成完整内容长截图”​ 功能。用户点击按钮,应用自动完成滚动、分页截图、图片裁剪、无缝合并、保存/分享的全流程。该功能主要服务于两类典型场景:

  1. 列表/对话页面:如聊天记录、新闻列表、AI生成的攻略。

  2. 富媒体/Web内容:如渲染了HTML/CSS的WebView组件、复杂的自定义卡片。

核心API 与 技术原理

实现长截图,本质是 “分而治之,合而为一”​ 的过程。以下是依赖的核心能力:

API/模块

作用

所属模块

componentSnapshot.get(compId)

获取指定组件的当前可视区域快照

@ohos.arkui

PixelMap.crop(region)

PixelMap进行裁剪,保留指定区域

@kit.ImageKit

PixelMap.readPixelsSync(area)/ .writePixelsSync(area)

同步读取/写入PixelMap的像素数据

@kit.ImageKit

createPixelMapSync(options)

创建一个新的、指定尺寸的PixelMap对象

@kit.ImageKit

photoAccessHelper

提供访问和保存媒体文件(如图片)到系统相册的能力

@kit.MediaLibraryKit

SaveButton

安全控件,用于触发需要用户确认的、向相册写入文件的操作

@ohos.arkui

Scroller/ WebviewController.scrollBy

控制ListWeb组件的滚动行为

@ohos.arkui/ @kit.ArkWeb

技术原理简述

  1. 初始化:滚动到内容顶部。

  2. 循环截图

    a. 调用componentSnapshot.get()截取当前屏幕视图。

    b. 将截图(PixelMap)裁剪(crop),只保留本次滚动新增的部分(关键优化,避免内容重复)。

    c. 将裁剪后的图片数据读取(readPixelsSync)到缓冲区。

    d. 判断是否滚动到底部,若未到底,则滚动一屏距离,回到步骤a。

  3. 合并图片:根据所有截图块(PositionArea)的总高度,创建一个新的空白PixelMap。按顺序将所有截图块的像素数据写入(writePixelsSync)到这个新图中。

  4. 保存/分享:通过photoAccessHelper创建相册文件,并将合并后的PixelMap编码为图片格式(如PNG)写入,此过程必须由用户通过SaveButton安全控件授权完成。

核心难点与解决方案

在实战中,以下几个问题是成功实现的关键:

  1. Web组件截图空白

    • 问题:对Web组件截图,只能得到当前可视区域,滚动后截取的是空白。

    • 原因:Web内核默认未开启全页面绘制。

    • 解决:在aboutToAppear生命周期中,务必调用webview.WebviewController.enableWholeWebPageDrawing()以启用全网页绘制。

  2. 截图时机与异步滚动

    • 问题:调用scrollByscrollTo后立即截图,会截取到滚动动画的中间状态,导致图片错位或残缺。

    • 解决:在每次滚动动作后,使用PromisesetTimeoutsleep工具函数(如CommonUtils.sleep(200))等待足够的动画时间(通常200-300ms),确保滚动停止、渲染完成后再进行截图。

  3. Web内容未加载完成

    • 问题:页面未加载完就开始截图,截图为空白或不全。

    • 解决:在Web组件的onPageEnd回调触发后,再开始执行截图流程。可结合加载状态标志位进行控制。

  4. 图片拼接错位与重复

    • 问题:如果每次截取整个可视区域然后简单拼接,相邻两张图的底部和顶部会有大量重叠,导致最终长图内容重复。

    • 解决“只保留新增部分”。从第二张截图开始,计算本次滚动实际新增的高度realScrollHeight,然后调用pixelMap.crop(),只保留从y = 容器高度 - realScrollHeight开始到底部的新增区域。第一张图保留全部。

通用实现步骤拆解

无论针对List还是Web,实现流程均可抽象为以下四步:

第一步:准备阶段

  • 获取组件引用和UIContext

  • (针对Web)调用enableWholeWebPageDrawing()

  • 保存当前的滚动位置(yOffsetBefore),以便完成后恢复。

  • 初始化存储数组:scrollYOffsets: number[](记录每次滚动偏移量)和areaArray: PositionArea[](存储图片数据块)。

第二步:循环截图与处理

  1. 滚动到目标位置(首次为0)。

  2. 等待渲染稳定(sleep)。

  3. 记录当前偏移量到scrollYOffsets

  4. 调用componentSnapshot.get(compId)获取当前PixelMap

  5. 调用工具方法(如ImageUtils.getSnapshotArea),根据本次与上次的偏移量差,计算裁剪区域,并执行裁剪和像素数据读取,将得到的PositionArea存入areaArray

  6. 判断是否已滚动到底部(Listscroller.isAtEnd()Web当前偏移+可视高 >= getPageHeight())。

  7. 若未到底,则滚动下一屏距离(通常为一个List项高度或固定值),回到步骤2。

第三步:合并与生成

  1. 循环结束后,计算所有areaArray中图片块的总高度。

  2. 调用createPixelMapSync创建一个等宽、总高度的空白PixelMap

  3. 遍历areaArray,依次调用writePixelsSync,将每个图片块的像素数据写入到新创建的长图中,并正确累加Y轴偏移量。

第四步:保存与用户交互

  1. 使用SaveButton安全控件替换普通按钮,用户点击后触发系统授权弹窗。

  2. 在授权成功的回调中,通过photoAccessHelper.createAsset()在相册创建文件,获取uri

  3. 通过image.createImagePacker().packToData(mergedPixelMap, options)PixelMap编码为PNG或JPEG格式的ArrayBuffer

  4. 使用fileIoArrayBuffer写入到uri对应的文件中。

  5. 提示用户操作成功。

总结与最佳实践

  1. 性能优先:务必采用“裁剪新增部分”的策略,这是保证生成效率和图片质量的核心。

  2. 体验流畅:合理的滚动动画延时和加载状态判断,是功能稳定性的基础。

  3. 遵循规范:写入相册必须使用SaveButton,这是鸿蒙安全规范的要求。

  4. 组件差异List组件与Web组件的滚动控制和内容高度获取API不同,需分别处理。

  5. 内存管理:处理大图时注意PixelMap的及时释放(.release()),避免内存溢出。

通过本文的拆解,开发者可以系统地掌握鸿蒙6中实现任意组件长截图功能的核心技术与通用步骤,并将其灵活应用到聊天记录、文章保存、报表生成等丰富的业务场景中。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。

【文章风格说明】

本文严格遵循了您参考文章的结构、语气和技术深度,包括:

  • 标题格式HarmonyOS 6学习:+ 具体技术点。

  • 行文结构:概述 -> 核心API -> 原理与难点 -> 步骤拆解 -> 总结。

  • 技术细节:保留了关键的代码片段逻辑描述、问题排查思路和API用法。

  • 排版元素:使用了技术对比表格、核心要点列举、代码块注释。

  • 总结升华:在文末对技术价值和最佳实践进行了总结。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐