Android WebView 实现点击界面图片滑动浏览和保存图片功能

   2016-10-31 0
核心提示:-文章来源:itsCoder 的 WeeklyBolg 项目 itsCoder主页:http://itscoder.com/ 作者: yongyu0102 审阅者: wuchangfeng 一、概要最近在公司的项目中遇到 需求如下 : 点击 WebView 页面的图片实现开启查看图片模式,即可以显示点击的图片,然后滑动显示下一

-文章来源:itsCoder 的 WeeklyBolg 项目

一、概要

最近在公司的项目中遇到 需求如下

  1. 点击 WebView 页面的图片实现开启查看图片模式,即可以显示点击的图片,然后滑动显示下一张图片。

  2. 长按 WebView 页面图片弹出对话框可以选择保存长按的图片到本地相册。

拿到这个需求笔者第一反应是没做过 WebView 相关的交互,甚至分不清这个需求是否需要服务端配合完成 Java 与 JavaScript 的互相调用,一脸茫然。遇到这种情况笔者的解决思路一般 分两个方向

  1. 找一个比较出名的客户端有类似功能的,然后 Google 搜索,仿 XXXX,先粗略看一下有没有现成的 Demo 可以参考,比如我这个需要,先去搜索一下 ”Android 仿微信朋友圈浏览图片效果“ (这个搜索关键字很关键啊),可是笔者没找到符合该需要的 Demo。

  2. 在第一个方案不好使的情况下,我们没有了参考,那么咱们就得自己思考这个大概实现思路,然后把这个需求进行拆分,逐一击破。所以思考大概如下:

    (1)要想展示图片那么就得先拿到图片,要拿到图片只有两种可能,第一种可能是 WebView 本身缓存了图片,我们去缓存中读取图片进行显示,可是想一下,咱们浏览微博看图的时候如果没有网,这时候去点击图片那么图片是加载不出来的,所以这种可能否定了;所以只有第二种可能就是点击图片的时候拿到该图片对应的 URL 网址,然后咱们自己去网络加载图片进行显示,所以这个点我们 Get 到了。

    (2)要滑动图片进行显示下一张,那么就需要我们能拿到所有要显示的图片的 URL ,然后放到一个数组里面,每次滑动就进行加载一张图片,那么也就是我们一次性拿到所有 WebView 包含图片的 URL,这个就不是在点击图片的时候去获取,而是在 WebView 加载完成后获取到,这怎么能拿到?再想一下,WebView 进行加载显示的时候其实是加载 HTML(比如 Assets 目录中的文件)文本的字符串,然后进行渲染处理显示出来,所以 HTML文本文件里面包含了我们想要的图片网址,大家看一下下面这张截图就是一个带图片的 WebView 对应加载的 HTML文本文件部分截图,

    Android WebView 实现点击界面图片滑动浏览和保存图片功能

    其中标签 src 对象的内容就是我们想要的图片 URL,所以到这里我们就有了思路,我们先拿到 WebView 加载的 HTML 内容,然后在从 HTML 里面提取我要想要的 URL。

    (3)现在我们能拿到所有图片对应的 URL,那么滑动图片显示下一张就简单了,我们直接用一个 ViewPaper 来实现滑动加载图片即可。

    总结要实现这个需要我们需要做的工作有:

    1. 拿到 WebView 加载的 HTML 文本。
    2. 从 HTML 文本中提取所有图片对应的 URL。
    3. 处理 WebView 中图片的点击和长按响应事件。
    4. 用 ViewPaper 来实现滑动加载下一张图片。

    下面我们就按照以上几个步骤来实现我们想要的功能。

二、主要内容

2.1 获取 WebView 页面所有图片对应地址

2.1.1 解析 WebView 页面加载的 HTML文本文件

定义供 JavaScript 调用的交互接口

/**
 *这个接口就是给 JavaScript 调用的,调用结果就是返回 HTML 文本,
 *然后 getAllImageUrlFromHtml(HTML) 
 *从 HTML文件中提取页面所有图片对应的地址对象
 **/
privateclassInJavaScriptLocalObj{
/**
 * 获取 WebView 加载对应的 HTML 文本
 * @paramHTML WebView 加载对应的 HTML 文本
 */
@android.webkit.JavascriptInterface
publicvoidshowSource(String html){
//从 HTML 文件中提取页面所有图片对应的地址对象
 getAllImageUrlFromHtml(html);
 }
 }

WebView 开启 JavaScript 脚本执行,调用 JavaScript 代码

 mWebView.getSettings().setJavaScriptEnabled(true);
 mWebView.addJavascriptInterface(newInJavaScriptLocalObj(),"local_obj");
 mWebView.setWebViewClient(newWebViewClient() {
// 网页跳转
@Override
publicbooleanshouldOverrideUrlLoading(WebView view, String url){
 view.loadUrl(url);
returntrue;
 }
// 网页加载结束
@Override
publicvoidonPageFinished(WebView view, String url){
super.onPageFinished(view, url);
//解析 HTML
 parseHTML(view);
 }
/**
 * Java 调取 js 代码,
 * @paramview WebView
 */
privatevoidparseHTML(WebView view){
//这段 js 代码是解析获取到了 HTML 文本文件,然后调用本地定义的 Java 代码返回
//解析出来的 HTML 文本文件
 view.loadUrl("javascript:window.local_obj.showSource('<head>'+"
 + "document.getElementsByTagName('html')[0].innerHTML+'</head>');");
}

2.1.2 从获取到的 HTML文本文件中提取页面所有图片对应的地址对象

// 获取 img 标签正则
privatestaticfinalString IMAGE_URL_TAG ="<img.*src=http://www.tuicool.com/articles/(.*?)[^>]*?>";
// 获取 src 路径的正则
privatestaticfinalString IMAGE_URL_CONTENT ="http:\"?(.*?)(\"|>|\\s+)";

/***
 * 获取页面所有图片对应的地址对象,
 * 例如 
 * @paramHTML WebView 加载的 HTML 文本
 * @return
 */
privateListgetAllImageUrlFromHtml(String html){
 Matcher matcher = Pattern.compile(IMAGE_URL_TAG).matcher(html);
 List listImgUrl = newArrayList();
while(matcher.find()) {
 listImgUrl.add(matcher.group());
 }
//从图片对应的地址对象中解析出 src 标签对应的内容
 getAllImageUrlFormSrcObject(listImgUrl);
returnlistImgUrl;
}

/***
 * 从图片对应的地址对象中解析出 src 标签对应的内容,即 url
 * 例如 "http://sc1.hao123img.com/data/f44d0aab7bc35b8767de3c48706d429e"
 * @paramlistImageUrl 图片地址对象例如 :
 *
 */
privateListgetAllImageUrlFormSrcObject(List listImageUrl){
for(String image : listImageUrl) {
 Matcher matcher = Pattern.compile(IMAGE_URL_CONTENT).matcher(image);
while(matcher.find()) {
 listImgSrc.add(matcher.group().substring(0, matcher.group().length() -1));
 }
 }
returnlistImgSrc;
 }

到这里我们获取到了 WebView 页面中所有图片对象对应的 URL 地址,下面就还差一步,就是在点击 WebView 界面的图片时候去响应点击事件,然后把相应的 URL 地址传递给 ViewPaper 进行显示就齐活了。

2.2 响应 WebView 界面图片的点击事件

2.2.1定义供 JavaScript 调用的交互接口

// js 通信接口,定义供 JavaScript 调用的交互接口
privateclassMyJavascriptInterface{
privateContext context;
publicMyJavascriptInterface(Context context){
this.context = context;
 }
/**
 * 点击图片启动新的 ShowImageFromWebActivity,并传入点击图片对应的 url
 * 和页面所有图片对应的 url
 * @paramurl 点击图片对应的 url
 */
@android.webkit.JavascriptInterface
publicvoidopenImage(String url){
 Intent intent = newIntent();
 intent.putExtra("image", url);
//listImgSrc 该参数为页面所有图片对应的 url
 intent.putStringArrayListExtra(URL_ALL, (ArrayList<String>) listImgSrc);
 intent.setClass(context, ShowImageFromWebActivity.class);
 context.startActivity(intent);
 }
}

2.2.2 WebView 开启 JavaScript 脚本执行,调用 JavaScript 代码

 mWebView.getSettings().setJavaScriptEnabled(true);
//载入 js
mWebView.addJavascriptInterface(newMyJavascriptInterface(this),"imageListener");
mWebView.setWebViewClient(newWebViewClient() {
// 网页跳转
@Override
publicbooleanshouldOverrideUrlLoading(WebView view, String url){
 view.loadUrl(url);
returntrue;
 }

// 网页加载结束
@Override
publicvoidonPageFinished(WebView view, String url){
super.onPageFinished(view, url);
// web 页面加载完成,添加监听图片的点击 js 函数
 addImageClickListener();
 }

/**
 * 注入 js 函数监听,这段 js 函数的功能就是,遍历所有的图片,并添加 onclick 函数,
 * 实现点击事件,
 * 函数的功能是在图片点击的时候调用本地 java 接口并传递点击图片对应的 url 过去
 */
privatevoidaddImageClickListener(){
 mWebView.loadUrl("javascript:(function(){"+
"var objs = document.getElementsByTagName(\"img\"); "+
"for(var i=0;i<objs.length;i++) "+
"{"
 + " objs[i].onclick=function() "+
" { "
 + " window.imageListener.openImage(this.src); "+
" } "+
"}"+
"})()");
 }

到这里我们完成了前两步,拿去到 WebView 界面图片对应的所有 URL 地址和响应 WebView 界面图片的点击事件,下面的事情就简单了,用 ViewPaper 滑动显示每一张图片,再我们进行最后一步之前,我们再来实现一个功能就是长按 WebView 界面图片,弹出对话框来,然后可以选择保存图片功能,代码如下:

WebView 中图片长按点击事件处理

//长按点击事件
mWebView.setOnLongClickListener(newView.OnLongClickListener() {
@Override
publicbooleanonLongClick(View v){
//响应长按事件
 responseWebLongClick(v);
returnfalse;
 }
});

/**
 * 响应 WebView 长按图片的点击事件
 * @paramv
 */
privatevoidresponseWebLongClick(View v){
if(vinstanceofWebView) {
 WebView.HitTestResult result = ((WebView) v).getHitTestResult();
if(result !=null) {
inttype = result.getType();
//判断点击类型如果是图片
if(type == WebView.HitTestResult.IMAGE_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 longClickUrl = result.getExtra();
//弹出对话框
 showDialog(longClickUrl);
 }
 }
 }
 }

/**
 * 长按 WebView 中图片弹出对话框,可以选择保存图片
 * @paramurl 点击图片对应的 url
 */
privatevoidshowDialog(finalString url){
newActionSheetDialog(this)
 .builder()
 .setCancelable(true)
 .setCanceledOnTouchOutside(true)
 .addSheetItem(
"保存到相册",
 ActionSheetDialog.SheetItemColor.Blue,
newActionSheetDialog.OnSheetItemClickListener() {
@Override
publicvoidonClick(intwhich){
//下载图片
 downloadImage(url);
 }
 }).show();
 }

2.3 ViewPaper 滑动显示每一张图片,PhotoView 实现自由缩放功能

由于这部分代码比较简单,这里就直接贴出部分代码,文章中所用的 Demo 代码最终会上传到 GitHub上,有兴趣可以去瞧一瞧完整的代码,这里简单介绍几个类,ShowImageFromWebActivity.java 这个类内部就包含一个 ViewPaper 和两个按钮, ViewPaper 用来滑动显示每一张图片,按钮用来显示滑动的页数和实现点击保存图片功能,代码如下:

publicclassShowImageFromWebActivityextendsActivityimplementsView.OnClickListener{
privateViewPager vpImageBrowser;
privateTextView tvImageIndex;//显示滑动页数
privateButton btnSave;//保存图片按钮

privateImageBrowserAdapter adapter;
privateArrayList<String> imgUrls;//WebView 页面所有图片 URL
privateString url;//WebView 页面所有图片中被点击图片对应 URL
privateintcurrentIndex;//标记被滑动图片在所有图片中的位置
privateHandler mHandler;//异步发送消息
@Override
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_show_image_from_web);
 initView();
 initListener();
 initData();
 }

privatevoidinitView(){
 vpImageBrowser = (ViewPager) findViewById(R.id.vp_image_browser);
 tvImageIndex = (TextView) findViewById(R.id.tv_image_index);
 btnSave = (Button) findViewById(R.id.btn_save);
 }


privatevoidinitData(){
 mHandler = newHandler();
 imgUrls=getIntent().getStringArrayListExtra(MainActivity.URL_ALL);
 url=getIntent().getStringExtra("image");
//获取被点击图片在所有图片中的位置
intposition=imgUrls.indexOf(url);
 adapter=newImageBrowserAdapter(this,imgUrls);
 vpImageBrowser.setAdapter(adapter);
finalintsize=imgUrls.size();

if(size >1) {
 tvImageIndex.setVisibility(View.VISIBLE);
 tvImageIndex.setText((position+1) +"/"+ size);
 } else{
 tvImageIndex.setVisibility(View.GONE);
 }
 vpImageBrowser.setOnPageChangeListener(newViewPager.OnPageChangeListener() {
@Override
publicvoidonPageSelected(intarg0){
 currentIndex=arg0;
intindex = arg0 % size;
 tvImageIndex.setText((index+1) +"/"+ size);
 }
@Override
publicvoidonPageScrolled(intarg0,floatarg1,intarg2){
// TODO Auto-generated method stub
 }
@Override
publicvoidonPageScrollStateChanged(intarg0){
// TODO Auto-generated method stub
 }
 });
 vpImageBrowser.setCurrentItem(position);
 }

privatevoidinitListener(){
 btnSave.setOnClickListener(this);
 }

@Override
publicvoidonClick(View v){
switch(v.getId()){
caseR.id.btn_save :
 Toast.makeText(getApplicationContext(), "开始下载图片", Toast.LENGTH_SHORT).show();
 downloadImage();
break;
 }

/**
 * 开始下载图片
 */
privatevoiddownloadImage(){
 downloadAsync(imgUrls.get(currentIndex), Environment.getExternalStorageDirectory().getAbsolutePath() + "/ImagesFromWebView");
 }

 }

ShowImageFromWebActivity.java 对应 xml 文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
tools:context="activity.ShowImageFromWebActivity">
<view.PhotoViewViewPager
android:id="@+id/vp_image_browser"
android:layout_width="match_parent"
android:layout_height="match_parent">
</view.PhotoViewViewPager>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:textSize="18sp"
android:id="@+id/tv_image_index"
android:layout_width="56dp"
android:layout_height="36dp"
android:background="@drawable/shape_corner_rect_gray"
android:gravity="center"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:text="1/9"
android:textColor="@android:color/white"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_save"
android:textSize="@dimen/_16sp"
android:layout_width="56dp"
android:layout_height="36dp"
android:background="@drawable/shape_corner_rect_gray"
android:gravity="center"
android:padding="4dp"
android:text="保存"
android:textColor="@color/white"/>
</LinearLayout>
</RelativeLayout>

ImageBrowserAdapter.java 类代码如下:

publicclassImageBrowserAdapterextendsPagerAdapter{
privateActivity context;
privateList<String> picUrls;

publicImageBrowserAdapter(Activity context, ArrayList<String> picUrls){
this.context = context;
this.picUrls = picUrls;
 }

@Override
publicintgetCount(){

returnpicUrls.size();
 }

@Override
publicbooleanisViewFromObject(View view, Object object){
returnview == object;
 }

@Override
publicViewinstantiateItem(ViewGroup container,intposition){
 View view = View.inflate(context, R.layout.item_image_browser, null);
 ImageView iv_image_browser = (ImageView) view.findViewById(R.id.show_webimage_imageview);
 String picUrl = picUrls.get(position);
finalPhotoViewAttacher photoViewAttacher=newPhotoViewAttacher(iv_image_browser);
 photoViewAttacher.setScaleType(ImageView.ScaleType.FIT_CENTER);
//显示图片
 Glide.with(context).
 load(picUrl)
 .crossFade()
 .placeholder(R.drawable.avatar_default)
 .error(R.drawable.image_default_rect)
 .into(newGlideDrawableImageViewTarget(iv_image_browser){
@Override
publicvoidonResourceReady(GlideDrawable resource, GlideAnimation<?superGlideDrawable> animation){
super.onResourceReady(resource, animation);
 photoViewAttacher.update();
 }
 });

 container.addView(view);
returnview;
 }

@Override
publicvoiddestroyItem(ViewGroup container,intposition, Object object){
 container.removeView((View) object);
 }

上面代码也很简单,就是根据 URL 来加载显示图片,然后利用 PhotoView 进行缩放。

//ImageBrowserAdapter Item 布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">

<RelativeLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<uk.co.senab.photoview.PhotoView
android:id="@+id/pv_show_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:src=http://www.tuicool.com/articles/"@drawable/image_default_rect"/>


以上为本次学习内容,如有错误还望指正,谢谢!

文章中 Demo 已经上传在 GitHub上,地址为 ShowImageFromWebView

 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行