Android WebView实现网页滚动截图

WebView 网页滚动截屏,可对整个网页进行截屏而不是仅当前屏幕哦!

注意若Web页面存在position:fixed; 的话得在调用前设置为 position:absolute; 哦,否则会出现很多次的,请看下面的具体解说吧!!

private static Bitmap getViewBitmapWithoutBottom(View v) {
if (null == v) {
return null;
}
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
if (Build.VERSION.SDK_INT  = 11) {
v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(v.getHeight(), View.MeasureSpec.EXACTLY));
v.layout((int) v.getX(), (int) v.getY(), (int) v.getX() + v.getMeasuredWidth(), (int) v.getY() + v.getMeasuredHeight());
} else {
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
}
Bitmap bp = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight() - v.getPaddingBottom());
v.setDrawingCacheEnabled(false);
v.destroyDrawingCache();
return bp;
}
public static Bitmap getViewBitmap(View v) {
if (null == v) {
return null;
}
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
if (Build.VERSION.SDK_INT  = 11) {
v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(v.getHeight(), View.MeasureSpec.EXACTLY));
v.layout((int) v.getX(), (int) v.getY(), (int) v.getX() + v.getMeasuredWidth(), (int) v.getY() + v.getMeasuredHeight());
} else {
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
}
Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
v.setDrawingCacheEnabled(false);
v.destroyDrawingCache();
return b;
}
/**
* 获取 WebView 视图截图
* @param context
* @param view
* @return
*/
public static Bitmap getWebViewBitmap(Context context, WebView view) {
if (null == view) return null;
view.scrollTo(0, 0);
view.buildDrawingCache(true);
view.setDrawingCacheEnabled(true);
view.setVerticalScrollBarEnabled(false);
Bitmap b = getViewBitmapWithoutBottom(view);
// 可见高度
int vh = view.getHeight();
// 容器内容实际高度
int th = (int)(view.getContentHeight()*view.getScale());
Bitmap temp = null;
if (th   vh) {
int w = getScreenWidth(context);
int absVh = vh - view.getPaddingTop() - view.getPaddingBottom();
do {
int restHeight = th - vh;
if (restHeight <= absVh) {
view.scrollBy(0, restHeight);
vh += restHeight;
temp = getViewBitmap(view);
} else {
view.scrollBy(0, absVh);
vh += absVh;
temp = getViewBitmapWithoutBottom(view);
}
b = mergeBitmap(vh, w, temp, 0, view.getScrollY(), b, 0, 0);
} while (vh < th);
}
// 回滚到顶部
view.scrollTo(0, 0);
view.setVerticalScrollBarEnabled(true);
view.setDrawingCacheEnabled(false);
view.destroyDrawingCache();
return b;
}
/**
* 拼接图片
* @param newImageH
* @param newImageW
* @param background
* @param backX
* @param backY
* @param foreground
* @param foreX
* @param foreY
* @return
*/
private static Bitmap mergeBitmap(int newImageH, int newImageW, Bitmap background, float backX, float backY, Bitmap foreground, float foreX, float foreY) {
if (null == background || null == foreground) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(newImageW, newImageH, Bitmap.Config.RGB_565);
Canvas cv = new Canvas(bitmap);
cv.drawBitmap(background, backX, backY, null);
cv.drawBitmap(foreground, foreX, foreY, null);
cv.save(Canvas.ALL_SAVE_FLAG);
cv.restore();
return bitmap;
}
/**
* get the width of screen
*/
public static int getScreenWidth(Context ctx) {
int w = 0;
if (Build.VERSION.SDK_INT   13) {
Point p = new Point();
((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(p);
w = p.x;
} else {
w = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();
}
return w;
}
/**
* 保存图片
* @param context
* @param bitmap
* @param file
* @param quality
* @return
*/
public static boolean save(Context context, Bitmap bitmap, File file, int quality) {
if (bitmap == null) return false;
// 获得后缀格式
String abs = file.getAbsolutePath();
String suffix = abs.substring(abs.lastIndexOf(".")+1).toLowerCase();
Bitmap.CompressFormat format;
if ("jpg".equals(suffix) || "jpeg".equals(suffix)) {
format = Bitmap.CompressFormat.JPEG;
} else {
format = Bitmap.CompressFormat.PNG;
quality = 100;
}
if (file.exists() && ! file.delete()) return false;
try {
FileOutputStream stream = new FileOutputStream(file);
bitmap.compress(format, quality, stream);
stream.flush();
stream.close();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
return true;
} catch (Exception e) {
return false;
}
}

JS调用截屏操作

/**
* 屏幕截图
* @param name
* @param isRecover
*/
@JavascriptInterface
public String Capture(String name, boolean isRecover) {
File dir = new File(Config.PUBLIC_PICTURES_PATH);
LogUtil.i("capture", dir.getAbsolutePath());
if (! dir.exists() && ! dir.mkdirs()) return null;
final File file = new File(dir, name);
String path = file.getAbsolutePath();
if (file.exists() && ! isRecover) return path;
body.post(new Runnable() {
@Override
public void run() {
Bitmap bitmap = CaptureUtil.getWebViewBitmap(activity, body);
if (null != bitmap) ImageUtil.save(activity, bitmap, file, 100);
}
});
return path;
}
@JavascriptInterface
public String Capture(String name) {
return Capture(name, true);
}
@JavascriptInterface
public String Capture() {
String name = String.valueOf(System.currentTimeMillis()) + ".png";
return Capture(name);
}

示例图:我先通过 JS 触发显示了一个原生的 Button按钮, 然后WebView跳转到 csdn 页面,然后点击截屏按钮用来触发网页截屏的。下面的图是我手动截的图,不是上面代码的效果哈,下下面很长的那张才是Java程序的网页截图。

img

测试CSDN的网页完整截图:比较长哦~ 一般截图的功能都用于特殊的页面,如活动页面之类的,不会太长,那样是没有问题的。若是这种滚动到底部自动加载的话可能就会很长很长很长啦·····,自己看着办吧。。

img

但这里有个BUG,顶部固定Banner条每次截屏都有,这个有解决办法,不过得是你自己的网页才有操作权限哦,需要修改JS啦。

当截图JS命令触发前,把顶部悬浮的样式设置为绝对定位,当截屏完成后再改回固定定位即可,没什么难度了。

截屏是需要一些时间的,所以需要预设一个定时器来操作,JS栗子如下:

JS.Capture 是 WebView 绑定的自定义 Javascript 类对象

var file = '';
var $header = $("#layout-header");
$header.css({ position: "absolute" });
setTimeout(function(){
if (typeof name == "function" || typeof name == "undefined") {
file = JS.Capture();
} else {
file = JS.Capture(name, isRecover);
}
}, 500);
setTimeout(function(){
JS.Toast("截图已保存", "fast");
JS.Toast(file.replace("storage/emulated/0/", ""));
$header.css({ position: "fixed" });
if ($.isFunction(callback)) {
callback(file);
}
}, 1500);

以上就是本文的全部内容,希望对大家的学习有所帮助。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2814252.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

一周学会Django5 Python Web开发-Django5列表视图ListView

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计27条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

[数据集][目标检测]狗狗表情识别VOC+YOLO格式3971张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3971 标注数量(xml文件个数)&#xff1a;3971 标注数量(txt文件个数)&#xff1a;3971 标注…

Spring注解之前后端传值

目录 PathVariable 和 RequestParam RequestBody PathVariable 和 RequestParam PathVariable用于获取路径参数&#xff0c;RequestParam用于获取查询参数。 举个简单的例子&#xff1a; GetMapping("/lazzes/{clazzId}/teachers") public List<Teacher> …

HP笔记本电脑如何恢复出厂设置?这里提供几种方法

要恢复出厂设置Windows 11或10的HP笔记本电脑,你可以使用操作系统的标准方法。如果你运行的是早期版本,你可以使用HP提供的单独程序清除计算机并重新安装操作系统。 恢复出厂设置运行Windows 11的HP笔记本电脑​ 所有Windows 11计算机都有一个名为“重置此电脑”的功能,可…

【Nginx笔记02】通过Nginx服务器转发客户端的WebSocket接口到后端服务

这篇文章&#xff0c;主要介绍如何通过Nginx服务器转发客户端的WebSocket接口到后端服务【知识星球】。 目录 一、Nginx配置WebSocket 1.1、Nginx配置内容 1.2、客户端请求地址 1.3、创建WebSocket测试工程 1.4、启动测试 1.5、WebSocket超时问题 1.5.1、设置超时时间 …

数据结构与算法(数组,栈,队列,链表,哈希表,搜索算法,排序算法,查找算法,策略算法,递归算法,二叉搜索树BST,动态规划算法)

文章目录 1 课程介绍1.1 前置知识1.2 为什么要学习算法1.3 大厂面试常见数据结构题目(基础)1.4 数据结构和算法的关系 2 数据结构2.1 数据结构概述2.1.1 数据结构是什么2.1.2 数据结构分类2.1.2.1 线性结构2.1.2.2 非线性结构2.1.2.3 小总结 2.1.3 数据结构范围 2.2 数组Array2…

关于电脑一天24小时多少度电电脑的一天用电量计算

随着这几年物价的上涨&#xff0c;一些地区的电价越来越高&#xff0c;而我们经常需要使用电脑&#xff0c;那么一台电脑一天24小时用多少度电呢&#xff1f; 如何计算电脑一天的用电量&#xff1f; 让我们跟随小编来了解更多吧。 1、功耗、主机箱功耗 现在的计算机中&#xf…

IDEA如何开启Dashboard

普通的面板 Run Dashboard面板 修改配置文件 找到项目的.idea文件夹 点击编辑workspace.xml文件 添加下方代码 <component name"RunDashboard"><option name"ruleStates"><list><RuleState><option name"name" valu…

sublime双击后无法正常打开的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

4核8g服务器能支持多少人访问?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

云计算时代的运维: 职业发展方向与岗位选择

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

素皮材质的手机壳,如何才能做到经久耐用?

近几年&#xff0c;素皮材质开始在手机背壳上开始应用&#xff0c;各家手机厂商&#xff0c;基本都给自己的旗舰系列设备推出了带素皮材质版本的手机款式&#xff0c;比如华为的Mate 60系列&#xff0c;不仅Pro版本有素皮材质&#xff0c;Pro版本更是黑白两款全是素皮材质。 那…

Qt6.8 GRPC功能使用(2)标准 Qt实现客户端

简介 基于之前的文章所说&#xff0c; Qt6.7之后才开始支持客户端、服务端、及双向流&#xff0c;恰好电脑需要重装&#xff0c;看到Qt6.8版本就直接安装了&#xff0c;内容也是使用Qt6.8的版本进行编译的 客户端实现步骤 1. 安装Qt6.8, 包含GRPC功能模块 Qt 6.8安装目录下包…

php基础学习之错误处理(其一)

一&#xff0c;错误处理的概念 错误处理指的是系统(或者用户)在执行某些代码的时候&#xff0c;发现有错误&#xff0c;就会通过错误处理的形式告知程序员&#xff0c;俗称报错 二&#xff0c;错误分类 语法错误&#xff1a;书写的代码不符合 PHP 的语法规范&#xff0c;语法错…

开源大模型LLM大爆发,数据竞赛已开启!如何使用FuseLLM实现大语言模型的知识融合?

开源大模型LLM大爆发&#xff0c;数据竞赛已开启&#xff01;如何使用FuseLLM实现大语言模型的知识融合&#xff1f; 现在大多数人都知道LLM是什么&#xff0c;以及可以做什么。 人们讨论着它的优缺点&#xff0c;畅想着它的未来&#xff0c; 向往着真正的AGI&#xff0c;又有…

LeetCode_Java_动态规划系列(3)(题目+思路+代码)

338.比特位计数 给你一个整数 n &#xff0c;对于 0 < i < n 中的每个 i &#xff0c;计算其二进制表示中 1 的个数 &#xff0c;返回一个长度为 n 1 的数组 ans 作为答案。 class Solution {public int[] countBits(int n) {/** 思路&#xff1a;* 1.创建一个长度为 n…

ubuntu20下使用 torchviz可视化计算图

安装 torchviz&#xff1a; pip install torchviz示例代码&#xff1a;下面是一个简单的示例代码&#xff0c;展示如何使用 torchviz 可视化计算图&#xff1a; python import torch from torchviz import make_dot# 创建一个简单的模型 model torch.nn.Sequential(torch.nn…

Neoverse S3 系统 IP:机密计算和多芯片基础设施 SoC 的基础

第三代Neoverse系统IP Neoverse S3 产品推出了我们的第三代基础设施特定系统 IP&#xff0c;这是下一代基础设施 SOC 的理想基础&#xff0c;适用于从 HPC 和机器学习到 Edge 和 DPU 的各种应用。S3 机箱专注于为我们的合作伙伴提供 Chiplet、机密计算等关键创新以及 UCIe、DD…

BL0942 内置时钟免校准计量芯片 用于智能家居领域 低成本

BL0939是上海贝岭股份有限公司开发的一款用于智能家居领域进行电能测量的专用芯片&#xff0c;支持两路测量&#xff0c;可同时进行计量和漏电故障检测&#xff0c;漏电检测电流可设&#xff0c;响应时间快&#xff0c;具有体积小&#xff0c;外围电路简单&#xff0c;成本低廉…

自动驾驶框架:自动驾驶汽车定位-感知-规划-决策-控制概述,按照我的架构图理解:决策决定的是速度,规划决定的是路径(架构理解推荐)

1.按照我的架构图理解&#xff1a;决策决定的是速度&#xff0c;规划决定的是路径 参考链接&#xff1a;【自动驾驶】运动规划丨速度规划丨自动驾驶速度规划及状态协调方法 2.下面是参考别人的理解&#xff1a; 自动驾驶汽车定位-感知-规划-决策-控制概述 规划-决策-控制知…