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

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

图示:



项目的知识点:

  1. 加载网页后如何捕捉网页中的图片点击事件;
  2. 获取点击的图片资源后进行图片显示,获取整个页面所有的图片;
  3. 支持查看上下一张的图片以及对图片缩放显示;
  4. 对图片进行保存;
  5. 其他:图片缓存的处理(不用每次都重新加载已查看过的图片)

项目代码结构:



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

添加权限:

    <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'


混淆文件设置:

  1. -keep public class * implements com.bumptech.glide.module.GlideModule  
  2. -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {  
  3.   **[] $VALUES;  
  4.   public *;  
  5. }  


代码解析:

MainActivity很简单,代码如下:

  1. @Override  
  2.  public void onCreate(Bundle savedInstanceState) {  
  3.      super.onCreate(savedInstanceState);  
  4.      setContentView(R.layout.activity_main);  
  5.      contentWebView = (WebView) findViewById(R.id.webView);  
  6.      contentWebView.getSettings().setJavaScriptEnabled(true);  
  7.      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");  
  8.      contentWebView.addJavascriptInterface(new MJavascriptInterface(this,imageUrls), "imagelistener");  
  9.      contentWebView.setWebViewClient(new MyWebViewClient());  
  10.   
  11.  }  

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


MyWebViewClient代码如下:

  1. public class MyWebViewClient extends WebViewClient {  
  2.     @Override  
  3.     public void onPageFinished(WebView view, String url) {  
  4.         view.getSettings().setJavaScriptEnabled(true);  
  5.         super.onPageFinished(view, url);  
  6.         addImageClickListener(view);//待网页加载完全后设置图片点击的监听方法  
  7.     }  
  8.   
  9.     @Override  
  10.     public void onPageStarted(WebView view, String url, Bitmap favicon) {  
  11.         view.getSettings().setJavaScriptEnabled(true);  
  12.         super.onPageStarted(view, url, favicon);  
  13.     }  
  14.   
  15.     private void addImageClickListener(WebView webView) {  
  16.         webView.loadUrl("javascript:(function(){" +  
  17.                 "var objs = document.getElementsByTagName(\"img\"); " +  
  18.                 "for(var i=0;i<objs.length;i++)  " +  
  19.                 "{"  
  20.                 + "    objs[i].οnclick=function()  " +  
  21.                 "    {  "  
  22.                 + "        window.imagelistener.openImage(this.src);  " +//通过js代码找到标签为img的代码块,设置点击的监听方法与本地的openImage方法进行连接  
  23.                 "    }  " +  
  24.                 "}" +  
  25.                 "})()");  
  26.     }  
  27. }  

该类继承自WebViewClient,在onPageFinished方法中设置addImageClickListener的监听方法——>当整个WebView页面加载完毕后,为每张图片设置监听事件——>这意味着,整个页面未加载完毕时,点击是无效的。

addImageClickListener的代码实现也很简单,通过js找到相应的img标签,这样就知道是图片了,然后为这些图片设置点击监听事件——>每当点击时调用自定义的openImage(url)方法。这个openImage(url)方法与MJavascriptInterface中对应的方法交相辉映,这样就形成了js调用本地的方法。


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

  1. public class MJavascriptInterface {  
  2.     private Context context;  
  3.     private String [] imageUrls;  
  4.   
  5.     public MJavascriptInterface(Context context,String[] imageUrls) {  
  6.         this.context = context;  
  7.         this.imageUrls = imageUrls;  
  8.     }  
  9.   
  10.     @android.webkit.JavascriptInterface  
  11.     public void openImage(String img) {  
  12.         Intent intent = new Intent();  
  13.         intent.putExtra("imageUrls", imageUrls);  
  14.         intent.putExtra("curImageUrl", img);  
  15.         intent.setClass(context, PhotoBrowserActivity.class);  
  16.         context.startActivity(intent);  
  17.     }  
  18. }  

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

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

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


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

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

2.获取点击的图片资源后进行图片显示,获取整个页面所有的图片;
3.支持查看上下一张的图片以及对图片缩放显示;
4.对图片进行保存;


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

  1. mPager = (ViewPager) findViewById(R.id.pager);  
  2.        mPager.setPageMargin((int) (getResources().getDisplayMetrics().density * 15));  
  3.        mPager.setAdapter(new PagerAdapter() {  
  4.            @Override  
  5.            public int getCount() {  
  6.                return imageUrls.length;  
  7.            }  
  8.   
  9.   
  10.            @Override  
  11.            public boolean isViewFromObject(View view, Object object) {  
  12.                return view == object;  
  13.            }  
  14.   
  15.            @Override  
  16.            public Object instantiateItem(ViewGroup container, final int position) {  
  17.                if (imageUrls[position] != null && !"".equals(imageUrls[position])) {  
  18.                    final PhotoView view = new PhotoView(PhotoBrowserActivity.this);  
  19.                    view.enable();  
  20.                    view.setScaleType(ImageView.ScaleType.FIT_CENTER);  
  21.                    Glide.with(PhotoBrowserActivity.this).load(imageUrls[position]).override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).fitCenter().crossFade().listener(new RequestListener<String, GlideDrawable>() {  
  22.                        @Override  
  23.                        public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {  
  24.                            if (position == curPosition) {  
  25.                                hideLoadingAnimation();  
  26.                            }  
  27.                            showErrorLoading();  
  28.                            return false;  
  29.                        }  
  30.   
  31.                        @Override  
  32.                        public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {  
  33.                            occupyOnePosition(position);  
  34.                            if (position == curPosition) {  
  35.                                hideLoadingAnimation();  
  36.                            }  
  37.                            return false;  
  38.                        }  
  39.                    }).into(view);  
  40.   
  41.                    container.addView(view);  
  42.                    return view;  
  43.                }  
  44.                return null;  
  45.            }  
  46.   
  47.   
  48.            @Override  
  49.            public void destroyItem(ViewGroup container, int position, Object object) {  
  50.                releaseOnePosition(position);  
  51.                container.removeView((View) object);  
  52.            }  
  53.   
  54.        });  
  55.   
  56.        curPosition = returnClickedPosition() == -1 ? 0 : returnClickedPosition();  
  57.        mPager.setCurrentItem(curPosition);  
  58.        mPager.setTag(curPosition);  
  59.        if (initialedPositions[curPosition] != curPosition) {//如果当前页面未加载完毕,则显示加载动画,反之相反;  
  60.            showLoadingAnimation();  
  61.        }  
  62.        photoOrderTv.setText((curPosition + 1) + "/" + imageUrls.length);//设置页面的编号  
  63.   
  64.        mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {  
  65.            @Override  
  66.            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  67.   
  68.            }  
  69.   
  70.            @Override  
  71.            public void onPageSelected(int position) {  
  72.                if (initialedPositions[position] != position) {//如果当前页面未加载完毕,则显示加载动画,反之相反;  
  73.                    showLoadingAnimation();  
  74.                } else {  
  75.                    hideLoadingAnimation();  
  76.                }  
  77.                curPosition = position;  
  78.                photoOrderTv.setText((position + 1) + "/" + imageUrls.length);//设置页面的编号  
  79.                mPager.setTag(position);//为当前view设置tag  
  80.            }  
  81.   
  82.            @Override  
  83.            public void onPageScrollStateChanged(int state) {  
  84.   
  85.            }  
  86.        });  
  87.    }  
  88.   
  89.    private int returnClickedPosition() {  
  90.        if (imageUrls == null || curImageUrl == null) {  
  91.            return -1;  
  92.        }  
  93.        for (int i = 0; i < imageUrls.length; i++) {  
  94.            if (curImageUrl.equals(imageUrls[i])) {  
  95.                return i;  
  96.            }  
  97.        }  
  98.        return -1;  
  99.    }  

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

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

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

  1. if (imageUrls[position] != null && !"".equals(imageUrls[position])) {  
  2.                    final PhotoView view = new PhotoView(PhotoBrowserActivity.this);  
  3.                    view.enable();  
  4.                    view.setScaleType(ImageView.ScaleType.FIT_CENTER);  
  5.                    Glide.with(PhotoBrowserActivity.this).load(imageUrls[position]).override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).fitCenter().crossFade().listener(new RequestListener<String, GlideDrawable>() {  
  6.                        @Override  
  7.                        public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {  
  8.                            if (position == curPosition) {  
  9.                                hideLoadingAnimation();  
  10.                            }  
  11.                            showErrorLoading();  
  12.                            return false;  
  13.                        }  
  14.   
  15.                        @Override  
  16.                        public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {  
  17.                            occupyOnePosition(position);  
  18.                            if (position == curPosition) {  
  19.                                hideLoadingAnimation();  
  20.                            }  
  21.                            return false;  
  22.                        }  
  23.                    }).into(view);  
  24.   
  25.                    container.addView(view);  
  26.                    return view;  
  27.                }  
大体思路:1.通过PhotoView来实现图片的伸缩显示;2.通过Glide来加载图片等处理;

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

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

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

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


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

  1. private void savePhotoToLocal() {  
  2.        ViewGroup containerTemp = (ViewGroup) mPager.findViewWithTag(mPager.getCurrentItem());  
  3.        if (containerTemp == null) {  
  4.            return;  
  5.        }  
  6.        PhotoView photoViewTemp = (PhotoView) containerTemp.getChildAt(0);  
  7.        if (photoViewTemp != null) {  
  8.            GlideBitmapDrawable glideBitmapDrawable = (GlideBitmapDrawable) photoViewTemp.getDrawable();  
  9.            if (glideBitmapDrawable == null) {  
  10.                return;  
  11.            }  
  12.            Bitmap bitmap = glideBitmapDrawable.getBitmap();  
  13.            if (bitmap == null) {  
  14.                return;  
  15.            }  
  16.            FileUtils.savePhoto(this, bitmap, new FileUtils.SaveResultCallback() {  
  17.                @Override  
  18.                public void onSavedSuccess() {  
  19.                    runOnUiThread(new Runnable() {  
  20.                        @Override  
  21.                        public void run() {  
  22.                            Toast.makeText(PhotoBrowserActivity.this"保存成功", Toast.LENGTH_SHORT).show();  
  23.                        }  
  24.                    });  
  25.                }  
  26.   
  27.                @Override  
  28.                public void onSavedFailed() {  
  29.                    runOnUiThread(new Runnable() {  
  30.                        @Override  
  31.                        public void run() {  
  32.                            Toast.makeText(PhotoBrowserActivity.this"保存失败", Toast.LENGTH_SHORT).show();  
  33.                        }  
  34.                    });  
  35.                }  
  36.            });  
  37.        }  
  38.    }  

因为下载图片需要知道当前处于哪一页,所以在ViewPager初始化显示和滑动时都给每一页设置了tag,此时就派上了用场——>mPager.findViewWithTag获取当前page中的布局对象,然后获得对应的PhotoView对象,从而经过处理最终获取到Bitmap对象。这样已经很简单了,接下来只要将Bitmap对象保存至本地即可,代码如下:
  1. public class FileUtils {  
  2.     public static void savePhoto(final Context context, final Bitmap bmp , final SaveResultCallback saveResultCallback) {  
  3.         new Thread(new Runnable() {  
  4.             @Override  
  5.             public void run() {  
  6.                 File appDir = new File(Environment.getExternalStorageDirectory(), "out_photo");  
  7.                 if (!appDir.exists()) {  
  8.                     appDir.mkdir();  
  9.                 }  
  10.                 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置以当前时间格式为图片名称  
  11.                 String fileName = df.format(new Date()) + ".png";  
  12.                 File file = new File(appDir, fileName);  
  13.                 try {  
  14.                     FileOutputStream fos = new FileOutputStream(file);  
  15.                     bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);  
  16.                     fos.flush();  
  17.                     fos.close();  
  18.                     saveResultCallback.onSavedSuccess();  
  19.                 } catch (FileNotFoundException e) {  
  20.                     saveResultCallback.onSavedFailed();  
  21.                     e.printStackTrace();  
  22.                 } catch (IOException e) {  
  23.                     saveResultCallback.onSavedFailed();  
  24.                     e.printStackTrace();  
  25.                 }  
  26.   
  27.                 //保存图片后发送广播通知更新数据库  
  28.                 Uri uri = Uri.fromFile(file);  
  29.                 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));  
  30.             }  
  31.         }).start();  
  32.     }  
  33.   
  34.    public interface SaveResultCallback{  
  35.         void onSavedSuccess();  
  36.         void onSavedFailed();  
  37.     }  
  38. }  

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

缓存的处理:

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

  1. @Override  
  2.  protected void onDestroy() {  
  3.      new Thread(new Runnable() {  
  4.          @Override  
  5.          public void run() {  
  6.              Glide.get(MainActivity.this).clearDiskCache();//清理磁盘缓存需要在子线程中执行  
  7.          }  
  8.      }).start();  
  9.      Glide.get(this).clearMemory();//清理内存缓存可以在UI主线程中进行  
  10.      super.onDestroy();  
  11.  }  


特别注意:

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

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

  1. -keepclassmembers class com.example.administrator.webviewpagescannerapp.other.MJavascriptInterface{  
  2.   public *;  
  3. }  
  4.   
  5. -keepattributes *Annotation*  
  6. -keepattributes *JavascriptInterface*  


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


原文地址:http://blog.csdn.net/ganshenml/article/details/55050983?ref=myread

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值