Android 實現(xiàn)WebView點擊圖片查看大圖列表及圖片保存功能
在日常開發(fā)過程中,有時候會遇到需要在app中嵌入網(wǎng)頁,此時使用WebView實現(xiàn)效果,但在默認(rèn)情況下是無法點擊圖片查看大圖的,更無法保存圖片。本文將就這一系列問題的實現(xiàn)進(jìn)行說明。
圖示:

項目的知識點:
加載網(wǎng)頁后如何捕捉網(wǎng)頁中的圖片點擊事件;
獲取點擊的圖片資源后進(jìn)行圖片顯示,獲取整個頁面所有的圖片;
支持查看上下一張的圖片以及對圖片縮放顯示;
對圖片進(jìn)行保存;
其他:圖片緩存的處理(不用每次都重新加載已查看過的圖片)
項目代碼結(jié)構(gòu):

前期準(zhǔn)備(添加權(quán)限、依賴和混淆設(shè)置):
添加權(quán)限:
<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'
混淆文件設(shè)置:
-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的類用來實現(xiàn)js調(diào)用本地的方法;2.自定義MyWebViewClient來實現(xiàn)對WebView的監(jiān)聽管理。
MyWebViewClient代碼如下:
public class MyWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
view.getSettings().setJavaScriptEnabled(true);
super.onPageFinished(view, url);
addImageClickListener(view);//待網(wǎng)頁加載完全后設(shè)置圖片點擊的監(jiān)聽方法
}
@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代碼找到標(biāo)簽為img的代碼塊,設(shè)置點擊的監(jiān)聽方法與本地的openImage方法進(jìn)行連接
" } " +
"}" +
"})()");
}
}
該類繼承自WebViewClient,在onPageFinished方法中設(shè)置addImageClickListener的監(jiān)聽方法——>當(dāng)整個WebView頁面加載完畢后,為每張圖片設(shè)置監(jiān)聽事件——>這意味著,整個頁面未加載完畢時,點擊是無效的。
addImageClickListener的代碼實現(xiàn)也很簡單,通過js找到相應(yīng)的img標(biāo)簽,這樣就知道是圖片了,然后為這些圖片設(shè)置點擊監(jiān)聽事件——>每當(dāng)點擊時調(diào)用自定義的openImage(url)方法。這個openImage(url)方法與MJavascriptInterface中對應(yīng)的方法交相輝映,這樣就形成了js調(diào)用本地的方法。
MJavascriptInterface代碼(主要為與js對應(yīng)的本地方法的實現(xiàn)):
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)方法實現(xiàn)的邏輯是:通過傳遞當(dāng)前圖片的url與該WebView整個頁面的圖片列表(imageUrls)進(jìn)行跳轉(zhuǎn)至PhotoBrowserActivity中。PhotoBrowserActivity就是用來顯示大圖的圖片列表的頁面。
此處的疑問:imageUrls怎么獲得呢?
方式:1.服務(wù)器端直接將WebView中所有的圖片按照順序組合成String數(shù)組傳遞過來;2.或者直接將所有含img標(biāo)簽的html代碼傳遞過來,從而讓客戶端自己解析出所有圖片地址組合成的String數(shù)組。(此處是采用的第二種,具體如何解析,可以下載源碼查看。)
OK,到了這里算是完成了項目知識點的第1點:1.加載網(wǎng)頁后如何捕捉網(wǎng)頁中的圖片點擊事件;
接下來就說明后面的幾點:
2.獲取點擊的圖片資源后進(jìn)行圖片顯示,獲取整個頁面所有的圖片;
3.支持查看上下一張的圖片以及對圖片縮放顯示;
4.對圖片進(jìn)行保存;
其他所有的幾點實現(xiàn)均在PhotoBrowserActivity中,代碼如下:主要就是將圖片放進(jìn)ViewPager中進(jìn)行顯示:
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) {//如果當(dāng)前頁面未加載完畢,則顯示加載動畫,反之相反;
showLoadingAnimation();
}
photoOrderTv.setText((curPosition + 1) + "/" + imageUrls.length);//設(shè)置頁面的編號
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) {//如果當(dāng)前頁面未加載完畢,則顯示加載動畫,反之相反;
showLoadingAnimation();
} else {
hideLoadingAnimation();
}
curPosition = position;
photoOrderTv.setText((position + 1) + "/" + imageUrls.length);//設(shè)置頁面的編號
mPager.setTag(position);//為當(dāng)前view設(shè)置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方法來獲得用戶點擊的是哪一張圖片的位置并設(shè)置當(dāng)前是哪一個page——>通過遍歷當(dāng)前url與所有url來匹配獲??;
2.通過addOnPageChangeListener來實現(xiàn)對頁面滑動事件的監(jiān)聽——>此處主要用來處理設(shè)置當(dāng)前頁面的position、動畫、頁面序號顯示的邏輯;
3.PagerAdapter的實現(xiàn)——>每一頁內(nèi)容的初始化,主要為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來實現(xiàn)圖片的伸縮顯示;2.通過Glide來加載圖片等處理;
PhotoView是什么——>就是圖片組件,對圖片的伸縮、動效、緩存等方面進(jìn)行了處理,點擊地址查看GitHub介紹>>:
Gilde是什么——>Google推薦的圖片加載庫,此處用它的理由是好用、簡單,點擊地址查看GitHub介紹>>:
Glide的簡化形式——>Glide.with(...).load(圖片地址).override(加載圖片的大小).listener(設(shè)置監(jiān)聽方法).into(某個一個組件,此處是PhotoView),此處使用的是原圖加載,監(jiān)聽方法中有兩個回調(diào)方法:
onException和onResourceReady,此處在onResourceReady做的處理是:當(dāng)資源加載完畢時調(diào)用——>此時取消加載動畫的顯示。
頁面中的“頁面編號”和“保存”的組件顯示是通過寫在整個Activity的布局文件中實現(xiàn)的,而不是通過在每一頁中寫入這些組件。以下為獲取圖片資源對象的代碼:
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();
}
});
}
});
}
}
因為下載圖片需要知道當(dāng)前處于哪一頁,所以在ViewPager初始化顯示和滑動時都給每一頁設(shè)置了tag,此時就派上了用場——>mPager.findViewWithTag獲取當(dāng)前page中的布局對象,然后獲得對應(yīng)的PhotoView對象,從而經(jīng)過處理最終獲取到Bitmap對象。這樣已經(jīng)很簡單了,接下來只要將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");//設(shè)置以當(dāng)前時間格式為圖片名稱
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();
}
//保存圖片后發(fā)送廣播通知更新數(shù)據(jù)庫
Uri uri = Uri.fromFile(file);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
}).start();
}
public interface SaveResultCallback{
void onSavedSuccess();
void onSavedFailed();
}
}
圖片如何保存已經(jīng)如代碼所示,但要注意的是需要將已經(jīng)保存的圖片進(jìn)行廣播通知數(shù)據(jù)庫更新——>這樣立馬進(jìn)入微信或者扣扣點擊發(fā)送圖片,就可以看到剛剛保存的圖片。
緩存的處理:
使用Glide其中的一個好處是會將圖片默認(rèn)緩存,在需要清除緩存時,只需要執(zhí)行下面的代碼(此處是放在MainActivity中,退出頁面即清除緩存):
@Override
protected void onDestroy() {
new Thread(new Runnable() {
@Override
public void run() {
Glide.get(MainActivity.this).clearDiskCache();//清理磁盤緩存需要在子線程中執(zhí)行
}
}).start();
Glide.get(this).clearMemory();//清理內(nèi)存緩存可以在UI主線程中進(jìn)行
super.onDestroy();
}
特別注意:
1.若項目配置中將targetSdkVersion 指定為22以上,則要加入動態(tài)權(quán)限申請的模塊,否則在進(jìn)行保存操作時則會提示失??!
2.項目中暴露的js接口類:MJavascriptInterface不能混淆,其調(diào)用的方法的聲明也不能混淆,所以還要添加如下混淆設(shè)置代碼(代碼因包名而變化):
-keepclassmembers class com.example.administrator.webviewpagescannerapp.other.MJavascriptInterface{
public *;
}
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*
源碼已經(jīng)上傳至GitHub,點擊此處查看>>
以上所述是小編給大家介紹的Android 實現(xiàn)WebView點擊圖片查看大圖列表及圖片保存功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
android開發(fā)教程之系統(tǒng)資源的使用方法 android資源文件
這篇文章主要介紹了android中的系統(tǒng)資源的使用方法,包括顏色資源 、字符串資源、尺寸資源、XML資源文件,需要的朋友可以參考下2014-02-02
Android Room數(shù)據(jù)庫多表查詢的使用實例
這篇文章主要介紹了Android Room數(shù)據(jù)庫多表查詢的使用實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
A10_DatePicker的對話框設(shè)置(使用OnDateSetListener監(jiān)聽器)
本文主要彌補(bǔ)A07_TimePicker & DatePicker & AnalogClock & DigitalClock 的設(shè)置,具體實現(xiàn)代碼如下,感興趣的朋友可以參考下哈2013-06-06
Android手機(jī)(設(shè)備)連接掃描槍掃碼遇到的問題
這篇文章給大家分享了Android手機(jī)(設(shè)備)連接掃描槍掃碼遇到的問題以及解決辦法,有需要的參考下。2018-07-07

