Android自定义View - 仿支付宝月账单折线图

前言

支付宝有个查看月账单的功能,最近一直在学习自定义View,于是就尝试着自己实现了一个类似的折线图。

下面是支付宝消费分析功能截图和自己实现的折线效果截图:

支付宝消费分析折线图.jpg
效果1.jpg
效果2.jpg
效果3.jpg

确定绘制内容

在绘制折线图之前首先要分析折线图需要绘制哪些部分以及如何绘制这些部分,确定了各绘制部分及绘制方法之后再开始具体的绘制。

支付宝消费分析折线图_v10.0.15.jpg

1.确定需要绘制的区域

将折线图分为四个绘制区域:

  • 月份文字
  • 折线的边
  • 折线数据点处的圆
  • 消费金额文字

2.如何绘制相应的区域

public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
  • 折线的边
    折线的边使用 Canvas.drawPath方法绘制,如果有不了解这个方法中第二个参数Path含义和用法可以参考这篇文章 Path从懵逼到精通——基本操作,读完这篇文章基本可以掌握Path的相关操作。
public void drawPath(@NonNull Path path, @NonNull Paint paint)
  • 折线数据点处的圆
    Android提供下面的方法用于绘制圆形,如果有不了解这个方法的同学还请自行搜索学习。
public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

现在已经知道了折线需要绘制哪些部分以及各部分的绘制方法,接下来要做的就是确定绘制折线各部分所需要的点的坐标。

计算绘制点坐标

1.确定折线数据点x坐标

从下面绘制分析图可以直观地看到月份文字中间点x坐标,数据点x坐标,数据点处圆的圆心x坐标以及消费金额文字中间点x坐标的值是相同的,所以只需要确定数据点的x坐标就行了。

支付宝折线图绘制分析.png

观察支付宝的折线图可以看到第一个数据点距离屏幕左侧和最后一个数据点距离屏幕右侧的宽度是相等的,我们将这个宽度定义为基准宽度mBaseWidth。

将相邻两个数据点之间的宽度定义为mItemWidth,仔细观察可以看出这个宽度大概为 3*mBaseWidth,因为折线图固定显示5个月份的数据,所以mBaseWidth值就确定了:

// mWidth为当前整个View的显示宽度
// mWidth = 2 * mBaseWidth + 4 * mItemWidth = 14 * mBaseWidth;
mBaseWidth = mWidth / 14;
mItemWidth = mBaseWidth * 3;

得到mBaseWidth值后各个数据点的x坐标也就确定了:

for (int i = 0; i < pointCount; i++) {
    float dx = mBaseWidth + mItemWidth * i;
}

2.确定各绘制点y坐标

从支付宝折线图上可以看到月份的y坐标是固定的,数据点的y坐标是通过消费金额确定的,圆心y坐标和消费金额文字y坐标都依赖于数据点的y坐标。

下面我们就来分别确定月份的y坐标和数据点的y坐标。在绘制分析图中定义了几个参考变量,各变量的含义如下:

/**
 * 数据点最低y轴坐标
 */
private float mMinLineHeight;

/**
 * 数据点最高y轴坐标
 */
private float mMaxLineHeight;

/**
 * 折线最大可占用空间的高度,即折线波峰和波谷的差值,用来控制折线的陡峭程度
 */
private float mMaxLineSpace;
月份文字y坐标

从绘制分析图可以看到月份的y坐标范围在区间 [3/4*mHeight, mHeight] 之间,只要以 3/4*mHeight 为基准值加上一个偏移量就可得到月份的y轴坐标,那么如何确定这个偏移量的大小呢?

首先通过下面的方法计算月份文字的高度:

Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float fontHeight = fontMetrics.descent - fontMetrics.ascent;
......
float dy = mMaxLineHeight + fontHeight * 3 / 2;

计算出文字高度后选取 fontHeight * 3 / 2 作为偏移量,让月份文字的顶部距离 3/4*mWidth 基准线的高度刚好为 fontHeight。这个偏移量的值不是固定的,如果对最后绘制效果不满意,可以调整该值到自己满意的效果。

数据点y坐标

依然先定义一些控制参数:

/**
  * 已出账单月份中最低消费金额
  */
private double mMinBillValue;

/**
  * 已出账单月份中最高消费金额
  */
private double mMaxBillValue;

/**
  * 消费金额的极差
  */
private double mBillRange;

mMaxBillValue = Utils.getMaxBillValue(mBillValues);
mMinBillValue = Utils.getMinBillValue(mBillValues);
mBillRange = mMaxBillValue - mMinBillValue;
mBillRange = (mBillRange == 0 ? 1 : mBillRange);

先说数据点y坐标的确定方法,接下来再给出分析:

float dy = (float) (mMaxLineHeight - (mBillValues[i] - mMinBillValue) / mBillRange * mMaxLineSpace);

从计算公式可以看出数据点y坐标是通过mMaxLineHeight减去一个偏移量得到的,只需要确定偏移量的计算方法就可以了。

首先计算出当前月消费金额和最低消费金额的差值,然后用这个差值除以消费金额的极差mBillRange得到一个比例值,再用这个比例值去乘以mMaxLineSpace就是偏移值,然后用mMaxLineHeight减去偏移值就是数据点的y轴坐标。

数据点y坐标确定之后其它几个点的坐标也就确定了。圆心的y坐标就是数据点y坐标,消费金额文字y坐标通过数据点y坐标减去消费金额文字的 fontHeight * 3 / 2得到。

上面得到的仅仅是在账单金额已经出来情况下数据点的y坐标,那么月份的消费金额数还没出来时数据点y坐标该如何确定呢?因为只是为了演示效果,所以我采用下面的计算方法来得到未出账单月份的数据点y坐标。

for (int i = pointCount; i < totalMonths; i++) {
    float dx = mBaseWidth + mItemWidth * i;
    float dy = mMinLineHeight + mMaxLineSpace * (i - pointCount + 1) / (totalMonths - pointCount);
    mPath.lineTo(dx, dy);
}

绘制细节

确定绘制点的坐标之后,剩下的就是一些需要注意的细节,比如已出账单月份数据点处的圆是实心的,未出账单月份数据点处的圆是空心的。还有数据点之间的连线有虚线和实线之分,实线的颜色也有不同,绘制的时候注意这些细节就可以了。

顺带介绍一下Android中虚线绘制是通过设置画笔的路径效果来实现的:

DashPathEffect dashPathEffect = new DashPathEffect(new float[]{20, 20}, 1);
mLinePaint.setPathEffect(dashPathEffect);

关于DashPathEffect可以参考我的另一篇文章Android PathEffect - DashPathEffect 了解一下。

总结

折线图的目的是学习绘制自定义View,跟支付宝的实际逻辑肯定是完全不同的。另外目前项目并没有并没有在onMeasure进行特殊的绘制处理,等以后再优化改进。

项目地址:https://github.com/IvanRich/MonthBill

更新

2017-06-20 增加onMeasure测量过程的处理 diff

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,442评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,604评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,576评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,652评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,495评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,370评论 1 274
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,792评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,435评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,735评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,777评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,553评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,399评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,806评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,038评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,330评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,766评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,964评论 2 337

推荐阅读更多精彩内容