在日常开发过程中,有时候会遇到需要在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"/>
混淆文件设置:
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *; }
代码解析:
MainActivity很简单,代码如下: view plain
@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.getSettings().setAppCacheEnabled(true); contentWebView.getSettings().setDatabaseEnabled(true); contentWebView.getSettings().setDomStorageEnabled(true); contentWebView.loadUrl("http://api.99lovebuy.com/ccfinmobile/communityController/
getCommunityTopicDetail?id=704&uid=-1&ticket=656fc7b2-ee4e-4a7a-a65c-763b2131a0d1"); contentWebView.addJavascriptInterface(new MJavascriptInterface(this), "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); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 拦截 url 跳转,在里边添加点击链接跳转或者操作 view.loadUrl(url); return true; } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // 加载错误的时候会回调,在其中可做错误处理,比如再请求加载一次,或者提示404的错误页面 super.onReceivedError(view, errorCode, description, failingUrl); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { // 在每一次请求资源时,都会通过这个函数来回调 return super.shouldInterceptRequest(view, request); } /* private void addImageClickListener(WebView webView) { webView.loadUrl("javascript:(function(){" + "var objs = document.getElementsByTagName(\".detail-main img\"); " + "for(var i=0;i<objs.length;i++) " + "{" + " objs[i].οnclick=function() " + " { " + " window.imagelistener.openImage(this.src); " +//通过js代码找到标签为img的代码块,设置点击的监听方法与本地的openImage方法进行连接 " } " + "}" + "})()"); }*/ private void addImageClickListener(WebView webView) { webView.loadUrl("javascript:var objs = document.querySelectorAll(\".detail-main img\");" +//获取标签名为detail-main 里的所有图片 " function getImgUrlArray()" + " {" + " var img = new Array();" + " for(var i=0;i<objs.length;i++)" + " {" + " img[i] = objs[i].src;" +//通过js代码找到标签为img的代码块,把webview中指定标签的图片保存到数组中 " }" + " return img;" + " }" + " for(var i=0;i<objs.length;i++)" + " { " + " objs[i].onclick = function() " + " { " + " var img = getImgUrlArray();" + " window.imagelistener.openImage(this.src,img);" +//通过js代码找到标签为img的代码块,设置点击的监听方法与本地的openImage方法进行连接 " }" + " }"); } }
addImageClickListener的代码实现也很简单,通过js找到相应的img标签,这样就知道是图片了,然后为这些图片设置点击监听事件——>每当点击时调用自定义的openImage(url)方法。这个openImage(url)方法与MJavascriptInterface中对应的方法交相辉映,这样就形成了js调用本地的方法。
MJavascriptInterface代码(主要为与js对应的本地方法的实现):
/** * Created by SamZhao on 2017/12/19. */ public class MJavascriptInterface { private Context context; public MJavascriptInterface(Context context) { this.context = context; } @android.webkit.JavascriptInterface public void openImage(String img, String[] imageUrls) { Log.e("TAG", "图片地址" + img); Intent intent = new Intent(); intent.putExtra("imageUrls", imageUrls); intent.putExtra("curImageUrl", img); intent.setClass(context, PhotoBrowserActivity.class); context.startActivity(intent); for (int i = 0; i < imageUrls.length; i++) { Log.e("图片地址" + i, imageUrls[i].toString()); } } }
可以看到,openImage(url)方法实现的逻辑是:通过传递当前图片的url与该WebView整个页面的图片列表(imageUrls)进行跳转至PhotoBrowserActivity中。PhotoBrowserActivity就是用来显示大图的图片列表的页面。
此处的疑问:imageUrls怎么获得呢?
方式:1.服务器端直接将WebView中所有的图片按照顺序组合成String数组传递过来;2.或者直接将所有含img标签的html代码传递过来,从而让客户端自己解析出所有图片地址组合成的String数组。(此处是采用的第二种,具体如何解析,可以下载源码查看。)
OK,到了这里算是完成了项目知识点的第1点:1.加载网页后如何捕捉网页中的图片点击事件;
接下来就说明后面的几点:
2.获取点击的图片资源后进行图片显示,获取整个页面所有的图片;
3.支持查看上下一张的图片以及对图片缩放显示;
4.对图片进行保存;
其他所有的几点实现均在PhotoBrowserActivity中,代码如下:主要就是将图片放进ViewPager中进行显示:
1.首先通过returnClickedPosition方法来获得用户点击的是哪一张图片的位置并设置当前是哪一个page——>通过遍历当前url与所有url来匹配获取;
2.通过addOnPageChangeListener来实现对页面滑动事件的监听——>此处主要用来处理设置当前页面的position、动画、页面序号显示的逻辑;
3.PagerAdapter的实现——>每一页内容的初始化,主要为instantiateItem,核心代码再次拖出来如下;
大体思路:1.通过PhotoView来实现图片的伸缩显示;2.通过Glide来加载图片等处理;PhotoView是什么——>就是图片组件,对图片的伸缩、动效、缓存等方面进行了处理,点击地址查看GitHub介绍>>:
Gilde是什么——>Google推荐的图片加载库,此处用它的理由是好用、简单,点击地址查看GitHub介绍>>:
Glide的简化形式——>Glide.with(...).load(图片地址).override(加载图片的大小).listener(设置监听方法).into(某个一个组件,此处是PhotoView),此处使用的是原图加载,监听方法中有两个回调方法:
onException和onResourceReady,此处在onResourceReady做的处理是:当资源加载完毕时调用——>此时取消加载动画的显示。
页面中的“页面编号”和“保存”的组件显示是通过写在整个Activity的布局文件中实现的,而不是通过在每一页中写入这些组件。以下为获取图片资源对象的代码:
因为下载图片需要知道当前处于哪一页,所以在ViewPager初始化显示和滑动时都给每一页设置了tag,此时就派上了用场——>mPager.findViewWithTag获取当前page中的布局对象,然后获得对应的PhotoView对象,从而经过处理最终获取到Bitmap对象。这样已经很简单了,接下来只要将Bitmap对象保存至本地即可,代码如下:
图片如何保存已经如代码所示,但要注意的是需要将已经保存的图片进行广播通知数据库更新——>这样立马进入微信或者扣扣点击发送图片,就可以看到刚刚保存的图片。
缓存的处理:
使用Glide其中的一个好处是会将图片默认缓存,在需要清除缓存时,只需要执行下面的代码(此处是放在MainActivity中,退出页面即清除缓存):
特别注意:
1.若项目配置中将targetSdkVersion 指定为22以上,则要加入动态权限申请的模块,否则在进行保存操作时则会提示失败!
2.项目中暴露的js接口类:MJavascriptInterface不能混淆,其调用的方法的声明也不能混淆,所以还要添加如下混淆设置代码(代码因包名而变化):