本案例实现了Web组件中网页长截图的方案。支持截图后展示大小浮窗预览、生存图片到相册、手势左滑关闭等功能。
效果图预览
实现思路
本解决方案通过循环滚动Web组件,每次滚动截取当前状态后拼接到离屏画布,最后一次性转为PixelMap图片并显示在全屏模态窗口中。再通过安全控件SaveButton以免权限申请的方式生存到用户的相册中。
- 创建Web组件加载指定的网页,获取Web组件和网页的实际尺寸,并给Web组件绑定自定义的id。
由于Web组件为自适应填充剩余空间,所以通过onAreaChange接口来获取Web组件的实际尺寸。 Web网页加载完成后在onPageEnd回调中通过WebviewController的接口runJavaScriptExt执行javascript代码以获取网页的实际大小。
- Web({
- src: this.webPageUrl,
- controller: this.webviewController
- })
- .id(Constants.WEB_ID)
- .onAreaChange((oldValue, newValue) => {
- this.webWidth = newValue.width as number;
- this.webHeight = newValue.height as number;
- logger.info(TAG, `Web component width: ${this.webWidth}, height: ${this.webHeight}`);
- })
- .onPageEnd(() => {
- const script = '[document.documentElement.scrollWidth, document.documentElement.scrollHeight]';
- this.webviewController.runJavaScriptExt(script).then((result) => {
- switch (result.getType()) {
- case webview.JsMessageType.ARRAY:
- this.h5Width = (result.getArray() as number[])[0]; // 这里的单位是vp
- this.h5Height = (result.getArray() as number[])[1];
- logger.info(TAG, `h5Width = ${this.h5Width}, h5Height = ${this.h5Height}`);
- break;
- default:
- logger.error(TAG, `Get web page size tyep error.`);
- break;
- }
- });
- })
复制代码 截图的次数为网页高度/Web组件高度向上取整的结果。 最后一次截图的图片需要特殊处理,去除重复的部分,重复的部分高度即网页高度/Web组件高度取余。通过PixelMap对象的接口crop进行裁剪。
- const snipTimes = Math.ceil(this.h5Height / this.webHeight);
- for (let i = 0; i < snipTimes; i++) {
- let curSnip = await componentSnapshot.get(Constants.WEB_ID);
- // 最后一次截图需要特殊处理,去除重复部分
- if (i === snipTimes - 1) {
- let h = this.h5Height % this.webHeight;
- // 裁剪
- await curSnip.crop({ x: 0, y: vp2px(this.webHeight - h),
- size: {
- height: vp2px(h),
- width: vp2px(this.webWidth)
- }
- });
- offCanvasCtx.drawImage(curSnip, 0, this.webHeight * i, this.webWidth, h);
- } else {
- offCanvasCtx.drawImage(curSnip, 0, this.webHeight * i, this.webWidth, this.webHeight);
- }
- }
复制代码 这里图片拼接的方案选择的是离屏画布渲染对象OffscreenCanvasRenderingContext2D,离屏绘制会将需要绘制的内容先绘制在缓存区,加速绘制速率。
为什么不利用PixelMap首尾拼接?
固然componentSnapshot.get接口可以或许直接获取PixelMap对象,但是如果选择直接处理PixelMap需要手动将其转换为ArrayBuffer格式,再转为Uint8Array通过set接口拼接。 整个过程非常繁琐,且斲丧资源,并且如今PixelMap接口还有格式限制,影响图片输出。
而利用画布组件,只需要
- // 截图时拼接图片
- offCanvasCtx.drawImage(curSnip, 0, this.webHeight * i, this.webWidth, this.webHeight);
- ...
- // 截图完即可输出完整的长截图
- this.mergedImage = offCanvasCtx.getPixelMap(0, 0, this.h5Width, this.h5Height);
复制代码
- 截图后弹出预览窗口,可以滚动检察完整的截图,并生存图片到用户相册中。
本案例利用全屏模态窗口,开始截图后即弹出。截图未完成时,提示用户正在截图,截图完成后转为图片预览窗口,并且支持切换大小窗口。
窗口位置通过position属性设置,团结属性动画可以形成切换时的动画效果。
- /**
- * 设置弹窗居中。
- */
- setPopupCenter() {
- this.snapPopupPosition = {
- x: (this.displayWidth - this.snapPopupWidth) / 2,
- y: (this.displayHeight - this.snapPopupHeight) / 2
- }
- }
- /**
- * 设置弹窗位置为左下。
- */
- setPopupBottomLeft() {
- this.snapPopupPosition = {
- x: Constants.POPUP_MARGIN_LEFT,
- y: this.displayHeight - this.snapPopupHeight - Constants.POPUP_MARGIN_BOTTOM
- }
- }
复制代码 生存图片相册利用SaveButton安全控件,该接口可以免申请读写相册权限,暂时获取存储权限。
- // 安全控件的UI有严格的限制,智能使用系统提供的属性
- SaveButton({
- icon: SaveIconStyle.FULL_FILLED,
- text: SaveDescription.SAVE_IMAGE,
- buttonType: ButtonType.Capsule
- })
- .onClick(async (event, result) => {
- this.saveSnapshot(result);
- })
- /**
- * 保存图片到相册。
- */
- async saveSnapshot(result: SaveButtonOnClickResult) {
- // TODO: 知识点:使用SaveButton组件可以免申请权限,用户点击后,临时将文件存入系统目录
- if (result == SaveButtonOnClickResult.SUCCESS) {
- let helper = photoAccessHelper.getPhotoAccessHelper(this.context);
- // 使用保存控件
- try {
- // onClick触发后10秒内通过createAsset接口创建图片文件,10秒后createAsset权限收回。
- let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
- // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制
- let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
- const imagePackerApi: image.ImagePacker = image.createImagePacker();
- let packOpts: image.PackingOption = {
- format: Constants.SAVE_IMAGE_FORMAT,
- quality: Constants.SAVE_IMAGE_QUALITY,
- };
- imagePackerApi.packToFile(this.mergedImage, file.fd, packOpts).then(() => {
- logger.info(TAG, `Succeeded in packToFile`);
- promptAction.showToast({
- message: $r('app.string.saved_to_album'),
- duration: Constants.SAVED_TO_ALBUM_PROMPT_DURATION
- })
- }).catch((error: BusinessError) => {
- logger.error(TAG, `Failed to packToFile. Error code is ${error.code}, message is ${error.message}`);
- })
- } catch (error) {
- const err: BusinessError = error as BusinessError;
- logger.error(TAG, `Failed to save photo. Error code is ${err.code}, message is ${err.message}`);
- }
- }
- this.closeSnapPopup();
- }
复制代码 本案例还支持左滑关闭预览小窗口的手势特性。窗口组件利用gesture通用属性绑定滑动手势,团结属性动画实现滑动窗口效果。
- .gesture(
- PanGesture(this.panOption)
- .onActionStart(() => {
- // 保存滑动前的位置,以便用于恢复状态
- this.xPositionBefore = this.snapPopupPosition.x as number;
- })
- .onActionUpdate((event) => {
- // 左滑弹窗关闭,但允许向右滑动一小部分,形成弹性效果,提升操作手感
- if (event.offsetX < Constants.POPUP_RIGHT_PAN_GESTURE) {
- this.snapPopupPosition.x = this.xPositionBefore + event.offsetX;
- }
- })
- .onActionEnd((event) => {
- // 左滑超过一定阈值才会触发关闭弹窗,提升用户体验
- if (event.offsetX < Constants.POPUP_LEFT_PAN_GESTURE && !this.showPreview) {
- // 避免瞬间消失,而是平移动画后消失
- this.snapPopupPosition.x = Constants.POPUP_LEFT_SCREEN;
- sleep(Constants.ANIMATE_DURATION).then(() => {
- this.isShowSnapPopup = false;
- })
- } else if (!this.showPreview) {
- // 小窗预览时,右滑或者左滑没有达到阈值时恢复弹窗位置,结合动画属性形成弹性效果
- this.setPopupBottomLeft();
- }
- })
- )
复制代码 其他窗口UI及效果详见代码。
高性能知识点
- 本案例利用了Web预加载接口initializeWebEngine提前加载webview内核,并且利用prepareForPageLoad对目的网页进行预连接,提拔打开网页的速率。
- 本案例利用了离屏渲染对象来拼接截图,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,加速了绘制速率。
- 本案例利用了onAreaChange接口动态获取Web组件的尺寸,该接口属于高频回调接口,避免在该回调中调用冗余和耗时操作。还应尽大概的减少对状态变量的修改以避免触发大量UI重绘和动效耗时。
工程结构&模块类型
- webpagesnapshot // har类型
- ├───mainpage
- │ ├───MainPage.ets // ArkTS页面
- ├───common
- │ ├───Constants.ets // 常量
- │ └───Utils.ets // 通用工具
- │
复制代码 最后
有很多小伙伴不知道学习哪些鸿蒙开发技能?不知道需要重点把握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必把握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模子、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技能、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技能知识点。
希望这一份鸿蒙学习资料可以或许给大家带来资助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感爱好以及转行职员,可以直接领取这份资料
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
鸿蒙(HarmonyOS NEXT)最新学习路线
- HarmonOS就业必备技能
- HarmonOS多媒体技能
有了路线图,怎么能没有学习资料呢,小编也准备了一份团结鸿蒙官方发布笔记整理收纳的一套体系性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模子、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V2.0白皮书》
《鸿蒙 (OpenHarmony)开发根本到实战手册》
OpenHarmony北向、南向开发情况搭建
《鸿蒙开发根本》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- Stage模子入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技能
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙体系移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年步伐员来说是一个挑衅,也是一个机会。只有积极应对变化,不断学习和提拔自己,他们才能在这个变革的时代中立于不败之地。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |