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

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

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

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




阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29078329/article/details/77717597
个人分类: Android
上一篇Android动画全面剖析-属性动画高级用法
下一篇Android中Vitamio全屏播放
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭