仿人民日报客户端app报纸版面-Android

仿人民日报客户端报纸版面,首先来看下人民日报app的效果图,Demo代码地址:https://github.com/wangguifa/paper
在这里插入图片描述
如图所示,进入报纸页面会显示报纸的缩略图,每篇文章是一个版块,当点击该文章时,文章所在区域会被阴影覆盖,模拟用户选中效果,松开手指会进入文章详情页,本篇文章我们主要来介绍这个阴影效果是如何实现的。
接口数据分析:
在这里插入图片描述

注:该数据通过抓包获取,只用作学习使用,本文中不公布接口,只使用其数据。

以2019年4月13日数据为例,data为包含8个报纸版面的数组,由于数据太长,这里我只展示了第一个报纸版面,后面7个版面折叠了,每个版面中又包含了一定数量的item数组,每个item为一篇文章,其中points字段展示的数据就是每篇文章的阴影区域的坐标,我们只需要将这些坐标首位顺次连接就能得到该阴影区域,下面来看代码实现。

首先要自定义一个阴影View,来显示用户点击后的阴影区域,代码如下:

package com.guifa.paper;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.view.View;

import java.util.List;

/**
 * 阴影View
 */
public class CoverView extends View {

    /**
     * 阴影View的坐标,首尾顺次连接即可得到该阴影区域
     */
    private List<PointF> ps;
    /**
     * 文章ID,为了进入详情页获取该文章内容
     */
    private String newsId;
    /**
     * 阴影View默认的透明度
     */
    private int alpha = 0;

    public CoverView(Context context) {
        super(context);
    }

    public CoverView(Context context, List<PointF> ps, String newsId) {
        super(context);
        this.ps = ps;
        this.newsId = newsId;
    }

    @Override
    @SuppressLint("DrawAllocation")
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        for (int i = 0; i < ps.size(); i++) {
            PointF p = ps.get(i);
            if (p == null) {
                continue;
            }
            if (i == 0) {
                // 设置阴影View的坐标起点
                path.moveTo(p.x, p.y);
            } else {
                // 依次连接各个坐标得到该阴影区域
                path.lineTo(p.x, p.y);
            }
        }
        path.close();
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.TRANSPARENT);
        paint.setAlpha(alpha);
        canvas.drawPath(path, paint);
    }

    public List<PointF> getPs() {
        return ps;
    }

    public String getNewsId() {
        return newsId;
    }

    public void setAlpha(int alpha) {
        this.alpha = alpha;
    }
}

在构造方法中我们接收了ps和newsId两个参数,ps是阴影View的各个点坐标,我们根据坐标来顺次连接即可得到该阴影区域,newId是文章id当我们点击文章进入文章详情页的时候需要时候,用此来获取文章详情。

package com.guifa.paper;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;

import java.util.ArrayList;
import java.util.List;

public class PaperImageView extends FrameLayout {

    /**
     * 阴影View未被选中时的透明度
     */
    private static final int ALPHA_DEFAULT = 0;
    /**
     * 阴影View被选中时的透明度
     */
    private static final int ALPHA_CLICKED = 80;
    /**
     * 上下文
     */
    private Context context;
    /**
     * 用户点击后的回调
     */
    private CallBackAction callBack;
    /**
     * 加载进度条
     */
    private ProgressBar progressBar;
    /**
     * 版面是否加载成功
     */
    private boolean loadComplete;
    private CoverView selectView = null;
    private float lastX;

    public PaperImageView(Context context) {
        super(context);
        this.context = context;
    }

    public PaperImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    /**
     * 加载报纸版面图
     */
    public void loadingPaperImage(PaperImage image) {
        if (progressBar == null) {
            // 显示加载进度条
            showProgressBar();
        }
        Glide.with(this).load(image.getPage_pic()).into(new SimpleTarget<Drawable>() {
            @Override
            public void onResourceReady(@NonNull Drawable resource, Transition<? super Drawable> transition) {
                // 加载报纸版面缩略图
                setBackgroundDrawable(resource);
            }
        });
        PaperImageView.this.removeAllViews();
        // 每版报纸的文章List
        List<PaperImage.ItemsBean> items = image.getItems();
        for (int i = 0; i < items.size(); i++) {
            List<PointF> ps = new ArrayList<>();
            // 每篇文章的newsId
            String newsId = items.get(i).getArticleid() + "";
            // 每篇文章的点阵("points":"1047,139;1047,7;538,7;538,139;538,238;1047,238")
            String points = items.get(i).getPoints();
            // 根据";"分割成单个坐标
            String[] split = points.split(";");
            for (String aSplit : split) {
                // 根据","分割成x、y坐标
                String[] s = aSplit.split(",");
                if (TextUtils.isEmpty(s[0]) || TextUtils.isEmpty(s[1])) {
                    return;
                }
                // 将x、y坐标加入List<PointF>中
                ps.add(new PointF(Float.parseFloat(s[0]), Float.parseFloat(s[1])));
            }
            // 将文章的坐标和newsId传入CoverView,加载阴影View
            CoverView cImageView = new CoverView(context, ps, newsId);
            // addView
            PaperImageView.this.addView(cImageView);
        }
        // 隐藏加载进度条
        hideProgressBar();
        loadComplete = true;
    }

    /**
     * 正在加载报纸版面时的加载进度条
     */
    public void showProgressBar() {
        loadComplete = false;
        setBackgroundDrawable(null);
        PaperImageView.this.removeAllViews();
        progressBar = new ProgressBar(context);
        LayoutParams layout = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        layout.gravity = Gravity.CENTER;
        this.addView(progressBar, layout);
    }

    /**
     * 隐藏加载进度条
     */
    public void hideProgressBar() {
        if (progressBar != null) {
            this.removeView(progressBar);
        }
    }

    /**
     * 用户点击事件
     *
     * @param event
     * @return
     */
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!loadComplete) {
            return true;
        }
        switch (event.getAction()) {
            // 用户手指按下
            case MotionEvent.ACTION_DOWN:
                lastX = event.getX();
                PointF pointF = new PointF();
                pointF.set(lastX, event.getY());
                for (int i = 0; i < getChildCount(); i++) {
                    CoverView cImageView = (CoverView) getChildAt(i);
                    List<PointF> ps = cImageView.getPs();
                    // 判断用户点击的坐标是否在该阴影View范围中,若在,则该阴影View显示阴影效果
                    if (isPolygonContainPoint(pointF, ps)) {
                        cImageView.setAlpha(ALPHA_CLICKED);
                        cImageView.invalidate();
                        selectView = cImageView;
                    }
                }
                return true;
            // 用户手指抬起
            case MotionEvent.ACTION_UP:
                if (selectView != null) {
                    selectView.setAlpha(ALPHA_DEFAULT);
                    selectView.invalidate();
                    if (Math.abs(lastX - event.getX()) < 12) {
                        // 当用户手指移动距离的绝对值小于12时,认为它是点击事件,触发点击操作
                        callBack.doAction(selectView.getNewsId());
                    }
                    selectView = null;
                }
                return true;
            // 用户取消操作
            case MotionEvent.ACTION_CANCEL:
                if (selectView != null) {
                    selectView.setAlpha(ALPHA_DEFAULT);
                    selectView.invalidate();
                    selectView = null;
                }
                return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 判断用户点击的坐标是否在阴影View范围中
     *
     * @param pointF        用户点击的坐标
     * @param vertexPointFs 阴影View的坐标
     * @return true / false
     */
    private boolean isPolygonContainPoint(PointF pointF, List<PointF> vertexPointFs) {
        int nCross = 0;
        for (int i = 0; i < vertexPointFs.size(); i++) {
            PointF p1 = vertexPointFs.get(i);
            PointF p2 = vertexPointFs.get((i + 1) % vertexPointFs.size());
            if (p1.y == p2.y) {
                continue;
            }
            if (pointF.y < Math.min(p1.y, p2.y)) {
                continue;
            }
            if (pointF.y >= Math.max(p1.y, p2.y)) {
                continue;
            }
            double x = (double) (pointF.y - p1.y) * (double) (p2.x - p1.x) / (double) (p2.y - p1.y) + p1.x;
            if (x > pointF.x) {
                nCross++;
            }
        }
        return (nCross % 2 == 1);
    }

    public void setCallBack(CallBackAction callBack) {
        this.callBack = callBack;
    }

    /**
     * 用户点击事件
     */
    public interface CallBackAction {
        public void doAction(String newsId);
    }
}

在PaperImageView中,为其显示了版面图片,并在PaperImageView中循环加载CoverView,其中定义了一个CallBackAction接口,用来实现用户点击事件,当用户点击屏幕时,会判断用户点击的坐标位置在哪个CoverView的范围中,并让该CoverView改变透明度,模拟用户点击后的效果。
补充一下PaperImage代码:

package com.guifa.paper;

import java.io.Serializable;
import java.util.List;

public class PaperImage implements Serializable {

    /**
     * page_name : 要闻
     * page_num : 01
     * period_num : 2019-04-13
     * page_pic : https://rmrbcmsonline.peopleapp.com/upload/image/201904/201904130450041113.jpg?x-oss-process=image/resize,w_500
     * page_time : 1555270132
     * items : [{"points":"1047,139;1047,7;538,7;538,139;538,238;1047,238","title":"就任朝鲜国务委员会委员长","id":4052556,"articleid":3909557,"category_id":"115","authors":[],"news_timestamp":1555084800,"news_datetime":"2019-04-13","comment_count":0,"view_type":"paper_2","title_style":null,"description":null,"copyfrom":null,"news_link":null,"medias":null,"content":null},{},{},{},{},{},{},{}]
     */

    private String page_name;
    private String page_num;
    private String period_num;
    private String page_pic;
    private int page_time;
    private List<ItemsBean> items;

    public String getPage_name() {
        return page_name;
    }

    public void setPage_name(String page_name) {
        this.page_name = page_name;
    }

    public String getPage_num() {
        return page_num;
    }

    public void setPage_num(String page_num) {
        this.page_num = page_num;
    }

    public String getPeriod_num() {
        return period_num;
    }

    public void setPeriod_num(String period_num) {
        this.period_num = period_num;
    }

    public String getPage_pic() {
        return page_pic;
    }

    public void setPage_pic(String page_pic) {
        this.page_pic = page_pic;
    }

    public int getPage_time() {
        return page_time;
    }

    public void setPage_time(int page_time) {
        this.page_time = page_time;
    }

    public List<ItemsBean> getItems() {
        return items;
    }

    public void setItems(List<ItemsBean> items) {
        this.items = items;
    }

    public static class ItemsBean {
        /**
         * points : 1047,139;1047,7;538,7;538,139;538,238;1047,238
         * title : 这是标题
         * id : 4052556
         * articleid : 3909557
         * category_id : 115
         * authors : []
         * news_timestamp : 1555084800
         * news_datetime : 2019-04-13
         * comment_count : 0
         * view_type : paper_2
         * title_style : null
         * description : null
         * copyfrom : null
         * news_link : null
         * medias : null
         * content : null
         */

        private String points;
        private String title;
        private int id;
        private int articleid;
        private String category_id;
        private int news_timestamp;
        private String news_datetime;
        private int comment_count;
        private String view_type;

        public String getPoints() {
            return points;
        }

        public void setPoints(String points) {
            this.points = points;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public int getArticleid() {
            return articleid;
        }

        public void setArticleid(int articleid) {
            this.articleid = articleid;
        }

        public String getCategory_id() {
            return category_id;
        }

        public void setCategory_id(String category_id) {
            this.category_id = category_id;
        }

        public int getNews_timestamp() {
            return news_timestamp;
        }

        public void setNews_timestamp(int news_timestamp) {
            this.news_timestamp = news_timestamp;
        }

        public String getNews_datetime() {
            return news_datetime;
        }

        public void setNews_datetime(String news_datetime) {
            this.news_datetime = news_datetime;
        }

        public int getComment_count() {
            return comment_count;
        }

        public void setComment_count(int comment_count) {
            this.comment_count = comment_count;
        }

        public String getView_type() {
            return view_type;
        }

        public void setView_type(String view_type) {
            this.view_type = view_type;
        }
    }
}

最后在Activity中调用即可使用。
最终代码放在了GitHub:GitHub代码连接

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值