Android 獲取WebView的HTML圖片點擊并查看

在日常開發過程中,有時候會遇到需要在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();  
 }

特別注意:

  • 若項目配置中將targetSdkVersion 指定為22以上,則要加入動態權限申請的模塊,否則在進行保存操作時則會提示失敗!
  • 項目中暴露的js接口類:MJavascriptInterface不能混淆,其調用的方法的聲明也不能混淆,所以還要添加如下混淆設置代碼(代碼因包名而變化):
-keepclassmembers class com.example.administrator.webviewpagescannerapp.other.MJavascriptInterface{  
  public *;  
}  
  
-keepattributes *Annotation*  
-keepattributes *JavascriptInterface*  

源碼已經上傳至GitHub,點擊此處查看>>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,660評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 擁抱給你 肩膀也給你 嬉笑是你 難過也是你 你是我的閨蜜 也是我的兄弟
    Pany閱讀 228評論 0 0
  • 2016年12月18-21日,被視為中國移動未來發展策略風向標的中國移動年度產業盛會2016第四屆中國移動全球合作...
    大嘴知事閱讀 130評論 0 0
  • 一 京城許久沒有下這么大的雪了,銀裝素裹著錦城,飛鳥也隱去蹤跡。 遠遠的有一點朱紅輕搖漸近,在這天地一色的雪白中,...
    心上有人閱讀 323評論 0 4