LongScreenshot代码流程概述

本文介绍了长截图的实现过程,从静态注册ScreenShotReceiver开始,启动ScreenshotService并动态注册接收器。在服务中初始化窗口并添加监听器,监听滚动和保存单个截屏。NextScreenThread用于滚动和计算参数,主线程负责滚动。SavePictureThread负责拼接所有截图。通过StopSelfBySelf()停止截图,isScrollToBottom()判断是否滚动到底。消息处理包括显示边框、禁用滚动和捕获屏幕等。点击下一步时,判断是否需要滚动,滚动前先截图,滚动后对比图片以确定是否到达底部。保存图片使用StorageHelper,考虑了SD卡存储权限问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


首先静态注册ScreenShotReceiver,通过这个receiver启动ScreenShotService,在ScreenshotService中动态注册上图中3个Receiver。

先通过在ScreenShotService中初始化窗口,为View添加监听器。事件的处理就从监听器入手。主要是滚动和保存单个截屏,最后拼接成一个长截屏图片。

保存单个截屏和计算滚动的一些参数的工作主要由NextScreenThread子线程去完成,滚动由NextScreenThread发消息给mUpdateScreenHanler在主线程中完成。

而最终保存长截图的工作有SavePictureThread子线程负责,其中就包括完成所有截屏的拼接工作。



停止screenshot:stopSelfBySelf();

判断是否到底了:isScrollToBottom();

mHandler中处理按下nextScreen后的三种消息:

1.MESSAGE_SHOW_DRAG_UP_VIEW---滚屏完后,显示出边框和bottomview

2.MESSAGE_DISABLE_NEXT_SCREEN---滚动后发现没有下一屏了。

3.MESSAGE_CAPTURE_SCREEN) ---scrollScreen()执行完了,即滚完屏幕了


点击nextScreen的处理流程:

1.判断是否需要滚动,需要就执行第2步。不需要就不执行第2步。

2.若是需要滚动,则先然所有外加边框和按钮都隐藏了。

(前面两步都是preNextScreen()做的,下面的步骤在NextScreenTread中完成)

3.在滚动前,先使用captrueScreen()截取图片。

<!-- pages/webview/webview.vue --> <template> <view class="webview-container"> <cover-view class="webview-wrapper"> <web-view :src="webviewPath" id="targetWebview"></web-view> </cover-view> <cover-view class="bottom-action-area"> <cover-view class="capture-btn" @click="captureWebview"> <cover-view class="btn-text">立即取证</cover-view> </cover-view> </cover-view> <!-- 预览组件 --> <cover-view v-if="showPreview" class="custom-preview"> <cover-view class="preview-header"> <cover-view class="title-container"> <cover-view class="preview-title">预览界面</cover-view> </cover-view> </cover-view> <cover-image :src="screenshotPath" class="preview-image" mode="aspectFit" @error="handleImageError" ></cover-image > <cover-view class="action-buttons"> <cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view> <cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view> </cover-view> <cover-view> </cover-view> </cover-view> <!-- 加载提示 --> <cover-view v-if="isLoading" class="loading-overlay"> <cover-view class="loading-spinner"></cover-view> <cover-view class="loading-text">截图中...</cover-view> </cover-view> </view> </template> <script setup lang="ts"> import { onLoad, onReady } from '@dcloudio/uni-app'; import html2canvas from 'html2canvas'; import { ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const isLoading = ref(false); const platform = ref(''); onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } // 获取当前平台 const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { const wv = currentWebview.children()[0]; ws.value = wv; wv.setStyle({ height: 'auto' }); } }, 1000); }); const captureWebview = async () => { if (!ws.value) { uni.showToast({ title: 'WebView未准备好', icon: 'none' }); return; } // #ifdef APP-PLUS const bitmap = new plus.nativeObj.Bitmap('screenshot') const fileName = `${Date.now()}.jpg` // const savePath = `_doc/${fileName}` const rand = Math.floor(Math.random() * 10000) const saveUrl = '_doc/' + rand + 'order.jpg' // 绘制页面截图 ws.value.draw(bitmap, () => { // 保存图片 bitmap.save( saveUrl, { format: 'jpg', quality: 90 }, (res:any) => { // 将本地路径转换为可以展示的路径 const previewPath = plus.io.convertLocalFileSystemURL(res.target) screenshotPath.value = previewPath console.log('截图保存成功,预览路径:', previewPath) // 延迟展示,确保加载完成 setTimeout(() => { showPreview.value = true isLoading.value = false }, 300) bitmap.clear() // 清理 bitmap 占用资源 }, (err:any) => { console.error('保存失败:', err) isLoading.value = false uni.showToast({ title: '保存失败: ' + err.message, icon: 'none', }) bitmap.clear() } ) }, (err:any) => { console.error('截图失败:', err) isLoading.value = false uni.showToast({ title: '截图失败: ' + err.message, icon: 'none', }) }) // #endif // #ifdef APP-H5 html2canvas(document.querySelector('.webview-wrapper')).then((canvas) => { const dataUrl = canvas.toDataURL('image/jpeg') screenshotPath.value = dataUrl showPreview.value = true }) // #endif } const closePreview = () => { showPreview.value = false; } const handleAction = (action: string) => { switch(action) { case '放弃': closePreview(); break; case '固化': uni.saveImageToPhotosAlbum({ filePath: screenshotPath.value, success: () => { uni.showToast({ title: '证据已固化保存' }); closePreview(); }, fail: (err) => { uni.showToast({ title: '固化失败: ' + err.errMsg, icon: 'none' }); } }); break; } } const handleImageError = (e: any) => { console.error('图片加载错误:', e); uni.showToast({ title: '图片加载错误', icon: 'none' }); } </script> <style> .webview-container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .webview-wrapper { height: calc(100vh - 120rpx); width: 100%; overflow: hidden; } .bottom-action-area { position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); display: flex; justify-content: center; align-items: center; } .capture-btn { width: 100%; height: 90rpx; background-color: #007aff; border-radius: 45rpx; box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2); display: flex; align-items: center; justify-content: center; } .btn-text { color: #ffffff; font-size: 32rpx; font-weight: 500; } /* 优化预览组件 */ .custom-preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; /* 垂直排列子元素 */ align-items: center; /* 关键修改:去掉顶部内边距,只保留底部内边距 */ padding: 0 0 40rpx 0; box-sizing: border-box; } .preview-header { width: 100%; height: 200rpx; /* 增加高度 */ background-color: #007aff; color: #ffffff; font-size: 34rpx; /* 增大字体 */ font-weight: bold; display: flex; align-items: center; justify-content: center; /* 添加圆角效果 */ border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; } .preview-title { color: #ffffff; /* 白色字体 */ font-size: 28rpx; /* 字体大小适配40rpx高度 */ font-weight: bold; line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */ width: 100%; text-align: center; } .preview-image { width: 100%; height: 100%; object-fit: contain; border: 1rpx solid rgba(255, 255, 255, 0.1); border-radius: 8rpx; background-color: #f5f5f5; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3); } .action-buttons { display: flex; width: 90%; justify-content: space-between; margin-top: 30rpx; } .action-btn { flex: 1; height: 90rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 34rpx; font-weight: 500; margin: 0 15rpx; } .cancel-btn { background-color: #f0f0f0; color: #333; } .confirm-btn { background-color: #007aff; color: #fff; } /* 加载指示器 */ .loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9998; background-color: rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; align-items: center; justify-content: center; } .loading-spinner { width: 80rpx; height: 80rpx; border: 6rpx solid #f3f3f3; border-top: 6rpx solid #007aff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-text { color: #fff; font-size: 32rpx; margin-top: 20rpx; } .bottom-action-img{ position: fixed; top: 100; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; display: flex; justify-content: center; align-items: center; } .action-img{ width: 100%; height: 90rpx; background-color: #007aff; display: flex; } </style> 将app内截图变成长截图
最新发布
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值