1. 程式人生 > >Android 實現WebView點選圖片檢視大圖列表及圖片儲存

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. }

很顯然,就是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. @Override

  9. public void onPageStarted(WebView view, String url, Bitmap favicon) {

  10. view.getSettings().setJavaScriptEnabled(true);

  11. super.onPageStarted(view, url, favicon);

  12. }

  13. private void addImageClickListener(WebView webView) {

  14. webView.loadUrl("javascript:(function(){" +

  15. "var objs = document.getElementsByTagName(\"img\"); " +

  16. "for(var i=0;i<objs.length;i++) " +

  17. "{"

  18. + " objs[i].onclick=function() " +

  19. " { "

  20. + " window.imagelistener.openImage(this.src); " +//通過js程式碼找到標籤為img的程式碼塊,設定點選的監聽方法與本地的openImage方法進行連線

  21. " } " +

  22. "}" +

  23. "})()");

  24. }

  25. }


該類繼承自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. public MJavascriptInterface(Context context,String[] imageUrls) {

  5. this.context = context;

  6. this.imageUrls = imageUrls;

  7. }

  8. @android.webkit.JavascriptInterface

  9. public void openImage(String img) {

  10. Intent intent = new Intent();

  11. intent.putExtra("imageUrls", imageUrls);

  12. intent.putExtra("curImageUrl", img);

  13. intent.setClass(context, PhotoBrowserActivity.class);

  14. context.startActivity(intent);

  15. }

  16. }

可以看到,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. @Override

  9. public boolean isViewFromObject(View view, Object object) {

  10. return view == object;

  11. }

  12. @Override

  13. public Object instantiateItem(ViewGroup container, final int position) {

  14. if (imageUrls[position] != null && !"".equals(imageUrls[position])) {

  15. final PhotoView view = new PhotoView(PhotoBrowserActivity.this);

  16. view.enable();

  17. view.setScaleType(ImageView.ScaleType.FIT_CENTER);

  18. Glide.with(PhotoBrowserActivity.this).load(imageUrls[position]).override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).fitCenter().crossFade().listener(new RequestListener<String, GlideDrawable>() {

  19. @Override

  20. public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {

  21. if (position == curPosition) {

  22. hideLoadingAnimation();

  23. }

  24. showErrorLoading();

  25. return false;

  26. }

  27. @Override

  28. public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {

  29. occupyOnePosition(position);

  30. if (position == curPosition) {

  31. hideLoadingAnimation();

  32. }

  33. return false;

  34. }

  35. }).into(view);

  36. container.addView(view);

  37. return view;

  38. }

  39. return null;

  40. }

  41. @Override

  42. public void destroyItem(ViewGroup container, int position, Object object) {

  43. releaseOnePosition(position);

  44. container.removeView((View) object);

  45. }

  46. });

  47. curPosition = returnClickedPosition() == -1 ? 0 : returnClickedPosition();

  48. mPager.setCurrentItem(curPosition);

  49. mPager.setTag(curPosition);

  50. if (initialedPositions[curPosition] != curPosition) {//如果當前頁面未載入完畢,則顯示載入動畫,反之相反;

  51. showLoadingAnimation();

  52. }

  53. photoOrderTv.setText((curPosition + 1) + "/" + imageUrls.length);//設定頁面的編號

  54. mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

  55. @Override

  56. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

  57. }

  58. @Override

  59. public void onPageSelected(int position) {

  60. if (initialedPositions[position] != position) {//如果當前頁面未載入完畢,則顯示載入動畫,反之相反;

  61. showLoadingAnimation();

  62. } else {

  63. hideLoadingAnimation();

  64. }

  65. curPosition = position;

  66. photoOrderTv.setText((position + 1) + "/" + imageUrls.length);//設定頁面的編號

  67. mPager.setTag(position);//為當前view設定tag

  68. }

  69. @Override

  70. public void onPageScrollStateChanged(int state) {

  71. }

  72. });

  73. }

  74. private int returnClickedPosition() {

  75. if (imageUrls == null || curImageUrl == null) {

  76. return -1;

  77. }

  78. for (int i = 0; i < imageUrls.length; i++) {

  79. if (curImageUrl.equals(imageUrls[i])) {

  80. return i;

  81. }

  82. }

  83. return -1;

  84. }

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. @Override

  15. public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {

  16. occupyOnePosition(position);

  17. if (position == curPosition) {

  18. hideLoadingAnimation();

  19. }

  20. return false;

  21. }

  22. }).into(view);

  23. container.addView(view);

  24. return view;

  25. }

大體思路: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. @Override

  27. public void onSavedFailed() {

  28. runOnUiThread(new Runnable() {

  29. @Override

  30. public void run() {

  31. Toast.makeText(PhotoBrowserActivity.this, "儲存失敗", Toast.LENGTH_SHORT).show();

  32. }

  33. });

  34. }

  35. });

  36. }

  37. }


因為下載圖片需要知道當前處於哪一頁,所以在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. Uri uri = Uri.fromFile(file);

  28. context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));

  29. }

  30. }).start();

  31. }

  32. public interface SaveResultCallback{

  33. void onSavedSuccess();

  34. void onSavedFailed();

  35. }

  36. }

圖片如何儲存已經如程式碼所示,但要注意的是需要將已經儲存的圖片進行廣播通知資料庫更新——>這樣立馬進入微信或者扣扣點擊發送圖片,就可以看到剛剛儲存的圖片。

快取的處理:

使用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. -keepattributes *Annotation*

  5. -keepattributes *JavascriptInterface*