仿人民日报客户端报纸版面,首先来看下人民日报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代码连接