当前位置: 移动技术网 > IT编程>移动开发>Android > Android 实现WebView点击图片查看大图列表及图片保存功能

Android 实现WebView点击图片查看大图列表及图片保存功能

2019年07月24日  | 移动技术网IT编程  | 我要评论

国际人才网佛山,傅振华怎么了,云阅

 在日常开发过程中,有时候会遇到需要在app中嵌入网页,此时使用webview实现效果,但在默认情况下是无法点击图片查看大图的,更无法保存图片。本文将就这一系列问题的实现进行说明。

图示:

项目的知识点:

加载网页后如何捕捉网页中的图片点击事件;

获取点击的图片资源后进行图片显示,获取整个页面所有的图片;

支持查看上下一张的图片以及对图片缩放显示;

对图片进行保存;

其他:图片缓存的处理(不用每次都重新加载已查看过的图片)

项目代码结构:

前期准备(添加权限、依赖和混淆设置):

添加权限:

 <uses-permission android:name="android.permission.internet" />
 <uses-permission android:name="android.permission.write_external_storage"/>
 <uses-permission android:name="android.permission.mount_unmount_filesystems"/>

添加依赖:

 compile 'com.bm.photoview:library:1.4.1'
 compile 'com.github.bumptech.glide:glide:3.7.0'
 compile 'com.android.support:support-v4:25.0.0'

混淆文件设置:

-keep public class * implements com.bumptech.glide.module.glidemodule 
-keep public enum com.bumptech.glide.load.resource.bitmap.imageheaderparser$** { 
 **[] $values; 
 public *; 
} 

代码解析:

mainactivity很简单,代码如下:

@override 
 public void oncreate(bundle savedinstancestate) { 
  super.oncreate(savedinstancestate); 
  setcontentview(r.layout.activity_main); 
  contentwebview = (webview) findviewbyid(r.id.webview); 
  contentwebview.getsettings().setjavascriptenabled(true); 
  contentwebview.loadurl("http://a.mp.uc.cn/article.html?uc_param_str=frdnsnpfvecpntnwprdssskt&client=ucweb&wm_aid=c51bcf6c1553481885da371a16e33dbe&wm_id=482efebe15ed4922a1f24dc42ab654e6&pagetype=share&btifl=100"); 
  contentwebview.addjavascriptinterface(new mjavascriptinterface(this,imageurls), "imagelistener"); 
  contentwebview.setwebviewclient(new mywebviewclient()); 
 } 

很显然,就是webview的基本初始化操作。其中1.自定义了mjavascriptinterface的类用来实现js调用本地的方法;2.自定义mywebviewclient来实现对webview的监听管理。

mywebviewclient代码如下:

public class mywebviewclient extends webviewclient { 
 @override 
 public void onpagefinished(webview view, string url) { 
  view.getsettings().setjavascriptenabled(true); 
  super.onpagefinished(view, url); 
  addimageclicklistener(view);//待网页加载完全后设置图片点击的监听方法 
 } 
 @override 
 public void onpagestarted(webview view, string url, bitmap favicon) { 
  view.getsettings().setjavascriptenabled(true); 
  super.onpagestarted(view, url, favicon); 
 } 
 private void addimageclicklistener(webview webview) { 
  webview.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); " +//通过js代码找到标签为img的代码块,设置点击的监听方法与本地的openimage方法进行连接 
    " } " + 
    "}" + 
    "})()"); 
 } 
} 

该类继承自webviewclient,在onpagefinished方法中设置addimageclicklistener的监听方法——>当整个webview页面加载完毕后,为每张图片设置监听事件——>这意味着,整个页面未加载完毕时,点击是无效的。
addimageclicklistener的代码实现也很简单,通过js找到相应的img标签,这样就知道是图片了,然后为这些图片设置点击监听事件——>每当点击时调用自定义的openimage(url)方法。这个openimage(url)方法与mjavascriptinterface中对应的方法交相辉映,这样就形成了js调用本地的方法。

mjavascriptinterface代码(主要为与js对应的本地方法的实现):

public class mjavascriptinterface { 
 private context context; 
 private string [] imageurls; 
 public mjavascriptinterface(context context,string[] imageurls) { 
  this.context = context; 
  this.imageurls = imageurls; 
 } 
 @android.webkit.javascriptinterface 
 public void openimage(string img) { 
  intent intent = new intent(); 
  intent.putextra("imageurls", imageurls); 
  intent.putextra("curimageurl", img); 
  intent.setclass(context, photobrowseractivity.class); 
  context.startactivity(intent); 
 } 
} 

可以看到,openimage(url)方法实现的逻辑是:通过传递当前图片的url与该webview整个页面的图片列表(imageurls)进行跳转至photobrowseractivity中。photobrowseractivity就是用来显示大图的图片列表的页面。

此处的疑问:imageurls怎么获得呢?

方式:1.服务器端直接将webview中所有的图片按照顺序组合成string数组传递过来;2.或者直接将所有含img标签的html代码传递过来,从而让客户端自己解析出所有图片地址组合成的string数组。(此处是采用的第二种,具体如何解析,可以下载源码查看。)

ok,到了这里算是完成了项目知识点的第1点:1.加载网页后如何捕捉网页中的图片点击事件;

接下来就说明后面的几点:

2.获取点击的图片资源后进行图片显示,获取整个页面所有的图片;

3.支持查看上下一张的图片以及对图片缩放显示;

4.对图片进行保存;

其他所有的几点实现均在photobrowseractivity中,代码如下:主要就是将图片放进viewpager中进行显示:

mpager = (viewpager) findviewbyid(r.id.pager); 
  mpager.setpagemargin((int) (getresources().getdisplaymetrics().density * 15)); 
  mpager.setadapter(new pageradapter() { 
   @override 
   public int getcount() { 
    return imageurls.length; 
   } 
   @override 
   public boolean isviewfromobject(view view, object object) { 
    return view == object; 
   } 
   @override 
   public object instantiateitem(viewgroup container, final int position) { 
    if (imageurls[position] != null && !"".equals(imageurls[position])) { 
     final photoview view = new photoview(photobrowseractivity.this); 
     view.enable(); 
     view.setscaletype(imageview.scaletype.fit_center); 
     glide.with(photobrowseractivity.this).load(imageurls[position]).override(target.size_original, target.size_original).fitcenter().crossfade().listener(new requestlistener<string, glidedrawable>() { 
      @override 
      public boolean onexception(exception e, string model, target<glidedrawable> target, boolean isfirstresource) { 
       if (position == curposition) { 
        hideloadinganimation(); 
       } 
       showerrorloading(); 
       return false; 
      } 
      @override 
      public boolean onresourceready(glidedrawable resource, string model, target<glidedrawable> target, boolean isfrommemorycache, boolean isfirstresource) { 
       occupyoneposition(position); 
       if (position == curposition) { 
        hideloadinganimation(); 
       } 
       return false; 
      } 
     }).into(view); 
     container.addview(view); 
     return view; 
    } 
    return null; 
   } 
   @override 
   public void destroyitem(viewgroup container, int position, object object) { 
    releaseoneposition(position); 
    container.removeview((view) object); 
   } 
  }); 
  curposition = returnclickedposition() == -1 ? 0 : returnclickedposition(); 
  mpager.setcurrentitem(curposition); 
  mpager.settag(curposition); 
  if (initialedpositions[curposition] != curposition) {//如果当前页面未加载完毕,则显示加载动画,反之相反; 
   showloadinganimation(); 
  } 
  photoordertv.settext((curposition + 1) + "/" + imageurls.length);//设置页面的编号 
  mpager.addonpagechangelistener(new viewpager.onpagechangelistener() { 
   @override 
   public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { 
   } 
   @override 
   public void onpageselected(int position) { 
    if (initialedpositions[position] != position) {//如果当前页面未加载完毕,则显示加载动画,反之相反; 
     showloadinganimation(); 
    } else { 
     hideloadinganimation(); 
    } 
    curposition = position; 
    photoordertv.settext((position + 1) + "/" + imageurls.length);//设置页面的编号 
    mpager.settag(position);//为当前view设置tag 
   } 
   @override 
   public void onpagescrollstatechanged(int state) { 
   } 
  }); 
 } 
 private int returnclickedposition() { 
  if (imageurls == null || curimageurl == null) { 
   return -1; 
  } 
  for (int i = 0; i < imageurls.length; i++) { 
   if (curimageurl.equals(imageurls[i])) { 
    return i; 
   } 
  } 
  return -1; 
 } 

1.首先通过returnclickedposition方法来获得用户点击的是哪一张图片的位置并设置当前是哪一个page——>通过遍历当前url与所有url来匹配获取;

2.通过addonpagechangelistener来实现对页面滑动事件的监听——>此处主要用来处理设置当前页面的position、动画、页面序号显示的逻辑;

3.pageradapter的实现——>每一页内容的初始化,主要为instantiateitem,核心代码再次拖出来如下;

if (imageurls[position] != null && !"".equals(imageurls[position])) { 
     final photoview view = new photoview(photobrowseractivity.this); 
     view.enable(); 
     view.setscaletype(imageview.scaletype.fit_center); 
     glide.with(photobrowseractivity.this).load(imageurls[position]).override(target.size_original, target.size_original).fitcenter().crossfade().listener(new requestlistener<string, glidedrawable>() { 
      @override 
      public boolean onexception(exception e, string model, target<glidedrawable> target, boolean isfirstresource) { 
       if (position == curposition) { 
        hideloadinganimation(); 
       } 
       showerrorloading(); 
       return false; 
      } 
      @override 
      public boolean onresourceready(glidedrawable resource, string model, target<glidedrawable> target, boolean isfrommemorycache, boolean isfirstresource) { 
       occupyoneposition(position); 
       if (position == curposition) { 
        hideloadinganimation(); 
       } 
       return false; 
      } 
     }).into(view); 
     container.addview(view); 
     return view; 
    } 

大体思路:

1.通过photoview来实现图片的伸缩显示;2.通过glide来加载图片等处理;

photoview是什么——>就是图片组件,对图片的伸缩、动效、缓存等方面进行了处理,点击地址查看github介绍>>:

gilde是什么——>google推荐的图片加载库,此处用它的理由是好用、简单,点击地址查看github介绍>>:

glide的简化形式——>glide.with(...).load(图片地址).override(加载图片的大小).listener(设置监听方法).into(某个一个组件,此处是photoview),此处使用的是原图加载,监听方法中有两个回调方法:

onexception和onresourceready,此处在onresourceready做的处理是:当资源加载完毕时调用——>此时取消加载动画的显示。

页面中的“页面编号”和“保存”的组件显示是通过写在整个activity的布局文件中实现的,而不是通过在每一页中写入这些组件。以下为获取图片资源对象的代码:

private void savephototolocal() { 
  viewgroup containertemp = (viewgroup) mpager.findviewwithtag(mpager.getcurrentitem()); 
  if (containertemp == null) { 
   return; 
  } 
  photoview photoviewtemp = (photoview) containertemp.getchildat(0); 
  if (photoviewtemp != null) { 
   glidebitmapdrawable glidebitmapdrawable = (glidebitmapdrawable) photoviewtemp.getdrawable(); 
   if (glidebitmapdrawable == null) { 
    return; 
   } 
   bitmap bitmap = glidebitmapdrawable.getbitmap(); 
   if (bitmap == null) { 
    return; 
   } 
   fileutils.savephoto(this, bitmap, new fileutils.saveresultcallback() { 
    @override 
    public void onsavedsuccess() { 
     runonuithread(new runnable() { 
      @override 
      public void run() { 
       toast.maketext(photobrowseractivity.this, "保存成功", toast.length_short).show(); 
      } 
     }); 
    } 
    @override 
    public void onsavedfailed() { 
     runonuithread(new runnable() { 
      @override 
      public void run() { 
       toast.maketext(photobrowseractivity.this, "保存失败", toast.length_short).show(); 
      } 
     }); 
    } 
   }); 
  } 
 } 

因为下载图片需要知道当前处于哪一页,所以在viewpager初始化显示和滑动时都给每一页设置了tag,此时就派上了用场——>mpager.findviewwithtag获取当前page中的布局对象,然后获得对应的photoview对象,从而经过处理最终获取到bitmap对象。这样已经很简单了,接下来只要将bitmap对象保存至本地即可,代码如下:

public class fileutils { 
 public static void savephoto(final context context, final bitmap bmp , final saveresultcallback saveresultcallback) { 
  new thread(new runnable() { 
   @override 
   public void run() { 
    file appdir = new file(environment.getexternalstoragedirectory(), "out_photo"); 
    if (!appdir.exists()) { 
     appdir.mkdir(); 
    } 
    simpledateformat df = new simpledateformat("yyyymmddhhmmss");//设置以当前时间格式为图片名称 
    string filename = df.format(new date()) + ".png"; 
    file file = new file(appdir, filename); 
    try { 
     fileoutputstream fos = new fileoutputstream(file); 
     bmp.compress(bitmap.compressformat.png, 100, fos); 
     fos.flush(); 
     fos.close(); 
     saveresultcallback.onsavedsuccess(); 
    } catch (filenotfoundexception e) { 
     saveresultcallback.onsavedfailed(); 
     e.printstacktrace(); 
    } catch (ioexception e) { 
     saveresultcallback.onsavedfailed(); 
     e.printstacktrace(); 
    } 
    //保存图片后发送广播通知更新数据库 
    uri uri = uri.fromfile(file); 
    context.sendbroadcast(new intent(intent.action_media_scanner_scan_file, uri)); 
   } 
  }).start(); 
 } 
 public interface saveresultcallback{ 
  void onsavedsuccess(); 
  void onsavedfailed(); 
 } 
} 

图片如何保存已经如代码所示,但要注意的是需要将已经保存的图片进行广播通知数据库更新——>这样立马进入微信或者扣扣点击发送图片,就可以看到刚刚保存的图片。

缓存的处理:

使用glide其中的一个好处是会将图片默认缓存,在需要清除缓存时,只需要执行下面的代码(此处是放在mainactivity中,退出页面即清除缓存):

@override 
 protected void ondestroy() { 
  new thread(new runnable() { 
   @override 
   public void run() { 
    glide.get(mainactivity.this).cleardiskcache();//清理磁盘缓存需要在子线程中执行 
   } 
  }).start(); 
  glide.get(this).clearmemory();//清理内存缓存可以在ui主线程中进行 
  super.ondestroy(); 
 } 

特别注意:

1.若项目配置中将targetsdkversion 指定为22以上,则要加入动态权限申请的模块,否则在进行保存操作时则会提示失败!

2.项目中暴露的js接口类:mjavascriptinterface不能混淆,其调用的方法的声明也不能混淆,所以还要添加如下混淆设置代码(代码因包名而变化):

-keepclassmembers class com.example.administrator.webviewpagescannerapp.other.mjavascriptinterface{ 
 public *; 
} 
-keepattributes *annotation* 
-keepattributes *javascriptinterface* 

源码已经上传至github,点击此处查看>>

以上所述是小编给大家介绍的android 实现webview点击图片查看大图列表及图片保存功能,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网