新闻详情页查看大图列表以及保存图片

来源:互联网 发布:淘宝客服需要会什么 编辑:程序博客网 时间:2024/05/22 06:56

    最近项目中需要实现,点击新闻详情页查看大图列表并实现保存功能,今天写本篇博客总结梳理一下,一方面对知识点加深印象,

另一方面希望能对有需要的朋友提供些许帮助,如下图,我们以新闻列表中的第二个条目为例进行说明。

该新闻条目路径: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