最近项目中需要实现,点击新闻详情页查看大图列表并实现保存功能,今天写本篇博客总结梳理一下,一方面对知识点加深印象,
另一方面希望能对有需要的朋友提供些许帮助,如下图,我们以新闻列表中的第二个条目为例进行说明。
该新闻条目路径:http://mini.eastday.com/mobile/170830155812023.html
该新闻条目源码:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta name="format-detection" content="email=no">
<meta name="author" content="Cleam Lee">
<meta name="keywords" content="东方头条,头条新闻,头条,今日新闻头条,头条网,头条新闻,今日头条新闻">
<meta name="description" content="东方头条网-东方网旗下《东方头条》是一款会自动学习的资讯软件,它会分析你的兴趣爱好,为你推荐喜欢的内容,并且越用越懂你.就要你好看,东方头条新闻网!">
<title>伊莎贝莉水中上演湿身诱惑 穿透视纱裙性感风骚</title>
<script type="text/javascript" src="https://mini.eastday.com/toutiaoh5/js/responsive.min.js"></script>
<link rel="stylesheet" href="https://mini.eastday.com/toutiaoh5/css/photoswipe/photoswipe.min.css">
<link rel="stylesheet" href="https://mini.eastday.com/toutiaoh5/css/common.min.css">
<link rel="stylesheet" href="https://mini.eastday.com/toutiaoh5/css/page_details_v4.min.css">
</head>
<body>
<input type="hidden" value="yule" id="newstype">
<input id="datetime_forapp" type="hidden" value="2017-08-30 15:58">
<input id="uid_forapp" type="hidden" value="200000000006426">
<input id="avatar_forapp" type="hidden" value="https://00.imgmini.eastday.com/dcminisite/portrait/84ab8437dbb57f6b4641f169da22d176.jpg">
<input id="nickname_forapp" type="hidden" value="国际在线">
<article id="J_article" class="J-article article">
<div id="title">
<div class="article-title">
<h1 class="title">伊莎贝莉水中上演湿身诱惑 穿透视纱裙性感风骚</h1>
</div>
<div class="article-src-time">
<span class="src">2017-08-30 15:58 来源:国际在线</span>
</div>
</div>
<div id="content" class="J-article-content article-content">
<figure class="section img">
<a class="img-wrap" style="padding-bottom: 147.55%;" href="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_78b1c712dd30e212a7c052f9ca9b4868_1.jpeg" data-size="694x1024"><img width="100%" alt="" src="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_78b1c712dd30e212a7c052f9ca9b4868_1.jpeg" data-weight="694" data-width="694" data-height="1024"></a>
</figure>
<p class="section txt">当地时间2017年8月29日,意大利威尼斯,伊莎贝莉-芳塔娜(Isabeli Fontana)第74届威尼斯电影节记者会。下海玩湿身,她身穿水墨色的透视纱裙大玩湿身诱惑,风情万种性感娇躯诱惑人心。David FisherREXShutterstock/东方IC</p>
<figure class="section img">
<a class="img-wrap" style="padding-bottom: 136.90%;" href="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_2a97d2c718656775a0bb5106ff11d0cf_2.jpeg" data-size="748x1024"><img width="100%" alt="" src="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_2a97d2c718656775a0bb5106ff11d0cf_2.jpeg" data-weight="748" data-width="748" data-height="1024"></a>
</figure>
<p class="section txt">当地时间2017年8月29日,意大利威尼斯,伊莎贝莉-芳塔娜(Isabeli Fontana)第74届威尼斯电影节记者会。下海玩湿身,她身穿水墨色的透视纱裙大玩湿身诱惑,风情万种性感娇躯诱惑人心。David FisherREXShutterstock/东方IC</p>
<figure class="section img">
<a class="img-wrap" style="padding-bottom: 60.44%;" href="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_e3268fa157ae755b8a5ddb6c3217f867_3.jpeg" data-size="900x544"><img width="100%" alt="" src="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_e3268fa157ae755b8a5ddb6c3217f867_3.jpeg" data-weight="900" data-width="900" data-height="544"></a>
</figure>
<p class="section txt">当地时间2017年8月29日,意大利威尼斯,伊莎贝莉-芳塔娜(Isabeli Fontana)第74届威尼斯电影节记者会。下海玩湿身,她身穿水墨色的透视纱裙大玩湿身诱惑,风情万种性感娇躯诱惑人心。David FisherREXShutterstock/东方IC</p>
</div>
</article>
<div id="news_check">
<div id="J_interest_news" class="interest-news"></div>
<div id="J_hot_news" class="hot-news"></div>
</div>
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
<div class="pswp__bg"></div>
<div class="pswp__scroll-wrap">
<div class="pswp__container">
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div><div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<div class="pswp__counter"></div>
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div class="pswp__share-tooltip"></div>
</div>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
<script src="https://mini.eastday.com/toutiaoh5/js/photoswipe/photoswipe.min.js"></script>
<script src="https://mini.eastday.com/toutiaoh5/js/common.min.js"></script>
<script src="https://mini.eastday.com/toutiaoh5/js/gg_details_v2.min.js"></script>
<script src="https://mini.eastday.com/toutiaoh5/js/page_details_v2.min.js"></script>
</body>
</html>
其中最为关键的是:
<a class="img-wrap" style="padding-bottom: 147.55%;" href="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_78b1c712dd30e212a7c052f9ca9b4868_1.jpeg" data-size="694x1024"><img width="100%" alt="" src="https://02.imgmini.eastday.com/mobile/20170830/20170830155812_78b1c712dd30e212a7c052f9ca9b4868_1.jpeg" data-weight="694" data-width="694" data-height="1024"></a>
锚点<a></a>中包含<img>标签,点击img会跳转到href链接中,href中即是图片的链接,我们对跳转事件进行拦截即可拿到图片的URL。因此要查看大图列表,首先要拿到所有图片的URL,我们分为如下几步完成大图列表查看、保存功能:
一、根据新闻URL链接,获取HTML源码
二、在HTML源码中获取所有的<img>标签,在<img>标签中找到<src>节点,从而获取图片的URL
三、点击图片跳转时,拦截URL
四、利用viewPager+PhotoView+Glide完成图片浏览
五、保存当前图片
下面我们分步骤实现,首先是获取HTML源码:
public static String getHtmlSourceCode(String path) {
String sourceCode = null;
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
InputStream inStream = conn.getInputStream();
byte[] data = readInputStream(inStream);
sourceCode = new String(data, "utf-8");
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "getHtmlSourceCode Exception");
}
Log.i(TAG, "path:" + path);
return sourceCode;
}
public static byte[] readInputStream(InputStream inStream) {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
inStream.close();
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "readInputStream Exception");
}
return outStream.toByteArray();
}
获取HTML源码后,利用正则表达式,通过<img>标签以及其<src>节点找到所有图片的URL:
public static void getImagesUrlFromHtml(final String path) {
new Thread() {
@Override
public void run() {
List<String> imageSrcList = new ArrayList<String>();
String htmlCode = getHtmlSourceCode(path);
//<img/>标签正则表达式
Pattern p = Pattern.compile("<img\\b[^>]*\\bsrc\\b\\s*=\\s*('|\")?([^'\"\n\r\f>]+(\\.jpg|\\.bmp|\\.eps|\\.gif|\\.mif|\\.miff|\\.png|\\.tif|\\.tiff|\\.svg|\\.wmf|\\.jpe|\\.jpeg|\\.dib|\\.ico|\\.tga|\\.cut|\\.pic)\\b)[^>]*>", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(htmlCode);
String quote = null;
String src = null;
while (m.find()) {
quote = m.group(1);
src = (quote == null || quote.trim().length() == 0) ? m.group(2).split("//s+")[0] : m.group(2);
imageSrcList.add(src);
}
if (imageSrcList == null || imageSrcList.size() == 0) {
Log.i(TAG, "新闻中未匹配到图片链接");
}
EventBus.getDefault().post(new NewsPhotoUrlsEvent(imageSrcList.toArray(new String[imageSrcList.size()])));
}
}.start();
}
注意:获取HTML源码以及遍历工作要在子线程中执行。
获取到所有图片的URL后,我们要对图片的跳转事件进行拦截。再点击图片后,WebView会进行跳转,跳转的URL即是被点击图片的URL。想要自己处理跳转事件,我们需要给WebView设置WebViewClient并重写shouldOverrideUrlLoading方法:
public class NewsWebViewClient extends WebViewClient {
private static final String TAG = "NewsWebViewClient";
private boolean isPageFinished;
private String[] mImagesUrl;
private Context mContext;
public NewsWebViewClient(Context context) {
mContext = context;
}
public void setImagesUrl(String[] imagesUrl) {
mImagesUrl = imagesUrl;
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
isPageFinished = true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
/**
* 点击图片时拦截URL
*
* @param view
* @param url
* @return true表明,针对点击请求的URL,不执行跳转,WebViewClient自己处理点击请求的URL
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (isPageFinished) {
if (mImagesUrl != null) {
for (String imageUrl : mImagesUrl) {
if (!TextUtils.isEmpty(imageUrl)) {
if (imageUrl.equals(url)) {
new PhotoBrowserDialog(mContext, imageUrl, mImagesUrl).show();
}
}
}
}
}
return true;
}
}
mImagesUrl即是所有图片的链接,点击某个新闻图片时,shouldOverrideUrlLoading(WebView view, String url)会被回调,参数url即是要跳转的链接,也就是该图片的URL。
shouldOverrideUrlLoading返回true表明WebView不执行跳转,WebViewClient自己处理点击请求的URL,此处我们弹出图片浏览对话框。isPageFinished表示WebView是否加载完成,加载完成时,点击图片才能弹出图片浏览对话框。还要对mImagesUrl进行判null,mImagesUrl的获取是在子线程中(异步执行),如果WebView加载完成,mImagesUrl还没有获取到的话,会crash,所以要判null。
接下来利用ViewPager、PhotoView、Glide完成大图浏览。新闻中有多少图片,ViewPager中就包含多少PhotoView,每个PhotoView显示一张图片,并且全屏显示,可放大可缩小,点击PhotoView,大图浏览对话框消失。
public class PhotoBrowserDialog implements View.OnClickListener {
private static final String TAG = "PhotoBrowserDialog";
private ViewPager mViewPager;
private ImageView mLoading;
private TextView mCurrentPhoto;
private TextView mSaveBtn;
private String mCurImageUrl;
private String[] mImageUrls;
private List<PhotoView> mPhotoViewList;
private int mCurrentPosition = -1;
private DialogView mDialogView;
private Context mContext;
private View.OnClickListener mOnClickListener;
public PhotoBrowserDialog(Context context, String currentUrl, String[] imageUrls) {
mContext = context;
mCurImageUrl = currentUrl;
mImageUrls = imageUrls;
initData();
initView();
}
private void initView() {
View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_photo_browser, null);
mViewPager = (ViewPager) view.findViewById(R.id.view_pager_news_photo_browser);
mViewPager.setAdapter(new NewsPhotoPagerAdapter(mPhotoViewList));
mViewPager.setCurrentItem(mCurrentPosition);
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurrentPhoto.setText(position + 1 + "/" + mImageUrls.length);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
mViewPager.setPageTransformer(true, new ZoomInTransform());
mLoading = (ImageView) view.findViewById(R.id.loading_news_photo);
mCurrentPhoto = (TextView) view.findViewById(R.id.current_positon_photo);
mCurrentPhoto.setText(mCurrentPosition + 1 + "/" + mImageUrls.length);
mSaveBtn = (TextView) view.findViewById(R.id.save_phonto_btn);
mSaveBtn.setOnClickListener(this);
mDialogView = new DialogView(mContext, view);
mDialogView.setFullScreen(true);
mDialogView.setCancelable(true);
mDialogView.setOnDialogDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (mOnClickListener != null) {
mOnClickListener = null;
}
if (mDialogView != null) {
mDialogView = null;
}
if (mPhotoViewList != null) {
mPhotoViewList = null;
}
}
});
}
private void initData() {
mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
};
mPhotoViewList = new ArrayList<>(mImageUrls.length);
for (int i = 0; i < mImageUrls.length; i++) {
//确定当前图片的position
if (mCurImageUrl.equals(mImageUrls[i])) {
mCurrentPosition = i;
}
final PhotoView photoView = getPhotoView();
Glide.with(mContext).load(mImageUrls[i]).transition(DrawableTransitionOptions.withCrossFade()).into(new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
photoView.setImageDrawable(resource);
}
@Override
public void onLoadFailed(Drawable errorDrawable) {
}
});
mPhotoViewList.add(photoView);
Log.i(TAG, "imageUrl-->" + mImageUrls[i]);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.save_phonto_btn:
savePhoto();
break;
}
}
private void savePhoto() {
PhotoView currentPhotoView = mPhotoViewList.get(mViewPager.getCurrentItem());
BitmapDrawable bitmapDrawable = (BitmapDrawable) currentPhotoView.getDrawable();
FileUtils.savePhoto(mContext, bitmapDrawable.getBitmap(), new FileUtils.SaveResultCallback() {
@Override
public void onSavedSuccess() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//主线程更新UI
ToastUtil.toastInCenter(mContext, R.string.news_detail_save_photo_toast_success);
}
});
}
@Override
public void onSavedFailed() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//主线程更新UI
ToastUtil.toastInCenter(mContext, R.string.news_detail_save_photo_toast_failed);
}
});
}
});
}
public void show() {
if (mDialogView != null) {
mDialogView.showDialog();
}
}
public void dismiss() {
if (mDialogView != null) {
mDialogView.dismissDialog();
}
}
/**
* 获取自适应的PhotoView,宽度填满屏幕,高度按比例填充
*
* @return
*/
private PhotoView getPhotoView() {
PhotoView photoView = new PhotoView(mContext);
photoView.enable();//允许缩放
photoView.setMaxWidth(ViewGroup.LayoutParams.MATCH_PARENT);
photoView.setMaxHeight(3 * ViewGroup.LayoutParams.MATCH_PARENT);
photoView.setScaleType(ImageView.ScaleType.FIT_XY);//填充整个屏幕
photoView.setAdjustViewBounds(true);//填充时,保持宽高比例
photoView.setOnClickListener(mOnClickListener);
return photoView;
}
}
这里我们重点说明以下几项:
一 、mCurrentPosition
mCurrentPosition 表示当前图片的位置,初始时通过NewsWebViewClient传递过来的mCurImageUrl,遍历mImageUrls得到;ViewPager滑动时,通过onPageSelected(int position)确定。
二、ViewPager滑动时动画
mViewPager.setPageTransformer(true, new ZoomInTransform());
三、获取自适应的PhotoView,宽度填满屏幕,高度按比例填充
通过getPhotoView()实现
四、PhotoView设置监听器,点击图片时,对话框消失
PhotoBrowserDialog的布局:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/photo_browser_dialog_bg">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager_news_photo_browser"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/current_positon_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:padding="10dp"
android:textColor="@color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/save_phonto_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="16dp"
android:layout_marginRight="16dp"
android:padding="10dp"
android:text="@string/news_detail_save_photo"
android:textColor="@color/white"
android:textSize="14sp" />
<ImageView
android:id="@+id/loading_news_photo"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerInParent="true"
android:src="@drawable/news_photo_loading"
android:visibility="gone" />
</RelativeLayout>
</FrameLayout>
接下来就是最后一步,保存当前图片(压缩后)到系统相册中:
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(), "topNews");
if (!appDir.exists()) {
appDir.mkdir();
}
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置以当前时间格式为图片名称
String fileName = df.format(new Date()) + ".JPEG";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, 70, 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();
}
到这里我们已经实现了所有的功能,代码只贴出了关键的部分, 项目源码:https://github.com/xiyy/TopNews,这是一款新闻客户端,并提供直播功能,个人独自开发完成,欢迎大家关注,谢谢!
后续:
1 也可通过执行JS代码,对图片点击事件进行拦截,实现该功能,参考:http://blog.csdn.net/ganshenml/article/details/55050983?ref=myread
2 PhotoView宽度填满屏幕,高度自适应,参考:http://www.cnblogs.com/bcbr/articles/4268276.html 、http://www.jianshu.com/p/c9424615e99d
3 保存的图片先压缩,再保存,关于有损压缩(JPEG)、无损压缩(png),参考:http://www.jianshu.com/p/e9e1db845c21