MPAndroid框架入门

MPAndroid框架入门

  • ----知其形,方知其意*

Tips:

本文使用的MPAndroid版本:3.0.1

文内不会有太多的API介绍,更多的是对MPAndroid图表绘制的理解,希望能帮助到大家

本文主要内容:

1,图表的组成分析

2,MPAndroid中折线图的组成

3,MPAndroid关键简介

一.图表的组成分析

如果某天拿到一个需求,搞一个收益走势图,大概长这个鬼样子:

原图.png

(图比较丑,来自百度,将就一下)

第一眼看上去什么感觉?
是这样:一脸懵逼,这线怎么画的,这标注位置又是怎么确定的?
还是这样:这玩意简单得很,canvas.draw两下就出来.
于是打开AS,搞一个自定义View,吧啦吧啦吧开敲

最后大概是这样:


public void onDarw(Canvas canvas) {

    super.onDraw(canvas);

    drawHorizontalGrids(canvas);

    drawVerticalLabels(canvas);

    drawVerticalGrids(canvas);

    drawHorizontalLeftLabels(canvas);

    drawLeftChartLine(canvas);

}

(瞎编的代码,只是个栗子)

最后这个View里,各种逻辑交错,定义一大堆控制变量,各种计算参数,以至于代码臃肿(血泪教训),分分钟赶超View.Java的节奏.

不管你是哪种情况,咱们都来分析一波这个图上,到底都有些啥

第一步:分解看得到的元素

image

先不管这类图有多复杂,由显示内容大致都可以分为这四个部分:

1,左侧Y轴:左侧数值标注,左侧轴线

2,折线(也可以看做是数据点的链接)

3,X轴:日期标注(或其它的标识),X轴轴线

4,右侧Y轴:右侧数值标注,右侧轴线

小结:看得到的元素,其实就这么多.先别急着搞.接下来分析一下哪些真正影响图表的,看不见的要素.

第二步:分解看不到的元素:

1,每一个点对象的构成:由X轴坐标+Y轴标注决定其位置,点之间的连接构成整条线段.

2,Y轴(左右):基于Y轴依赖的每像素单位,即每像素所占据的数值单位比,为什么说是基于Y轴依赖,从上面的图表,我们可以看出,这两条不同的走势线,所对应的Y轴标注也是不一样的,由此可知,左右两侧Y轴所对应的具体数值比例,一般也可以看做Y max value不同.

3,X轴:基于X轴展示的每单位像素,两点,第一,为什么叫X轴所展示的,一般而言,我们拿到数据之后,会选择直接把所有Data绘制到图表上,也就是X轴总单位长度(不是View的总长度)为Data的长度.但是如果这个图表是可以左右滑动的呢,这时候所展示的就可能是Data的一段位置,例如从下标为20的数据到80的数据.第二,为什么叫每单位像素,因为在X轴上,我们可能要绘制的内容会跟每一个数据点对象有关,侧重点在于对象上故而这样称呼(另外还有单位粒度,就不在这里多做解释了).

4,Draw Boundary Constraint:绘制范围约束:

这里的Draw Boundary Constraint(以下简称为DBC)类似,但是不同于一个View的宽高约束.为什么需要单独再抽出一个DBC,因为它不仅仅是对数据绘制范围的约束,更进一步而言,它也是各个组件之间联结的中心点.通过这样一个DBC的定义,也可以让我们的代码更清晰简洁.不至于到后期为了增加一个偏移量offset之类的就把代码搞乱.

小结:

基于以上几点的简单分析.我们可以大概得到一张这样的结构图:

image

二.MPAndroid中折线图的组成(LineChart)

为什么之前要画那么多篇幅向各位介绍图表的组件概念,因为这样理一遍思路,方便我们能进一步理解Philjay大神在MPAndroid中的一些设计,类似的构思也可以在其它类库中得到体现,比如lecho的HelloChart,也是一个比较出名的Android图表库.

2.1 LineChart的参与绘制的主要成员.

LineChart,折线图,从哪开始看?自然是从自定义控件三大核心方法:onMeasure(),onLayout(),onDraw()走起.在LineChart的直接继承类中,onLayout()(对子View进行了Layout);onMeasure()(仅做了一个最小高度的约束50dp).里面代码不多,这里就不做详细分析了.

重点部分在onDraw()方法中(基类Chart逻辑比较简单,这里主要介绍的是BarLineChartBase.java中的onDraw()).

// execute all drawing commands
    drawGridBackground(canvas);

    if (mAutoScaleMinMaxEnabled) {
        autoScale();
    }

    if (mAxisLeft.isEnabled())
        mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted());

    if (mAxisRight.isEnabled())
        mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted());

    if (mXAxis.isEnabled())
        mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);

    mXAxisRenderer.renderAxisLine(canvas);
    mAxisRendererLeft.renderAxisLine(canvas);
    mAxisRendererRight.renderAxisLine(canvas);

    mXAxisRenderer.renderGridLines(canvas);
    mAxisRendererLeft.renderGridLines(canvas);
    mAxisRendererRight.renderGridLines(canvas);

    if (mXAxis.isEnabled() && mXAxis.isDrawLimitLinesBehindDataEnabled())
        mXAxisRenderer.renderLimitLines(canvas);

    if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLimitLinesBehindDataEnabled())
        mAxisRendererLeft.renderLimitLines(canvas);

    if (mAxisRight.isEnabled() && mAxisRight.isDrawLimitLinesBehindDataEnabled())
        mAxisRendererRight.renderLimitLines(canvas);

    // make sure the data cannot be drawn outside the content-rect
    int clipRestoreCount = canvas.save();
    canvas.clipRect(mViewPortHandler.getContentRect());

    mRenderer.drawData(canvas);

    // if highlighting is enabled
    if (valuesToHighlight())
        mRenderer.drawHighlighted(canvas, mIndicesToHighlight);

    // Removes clipping rectangle
    canvas.restoreToCount(clipRestoreCount);

    mRenderer.drawExtras(canvas);

    if (mXAxis.isEnabled() && !mXAxis.isDrawLimitLinesBehindDataEnabled())
        mXAxisRenderer.renderLimitLines(canvas);

    if (mAxisLeft.isEnabled() && !mAxisLeft.isDrawLimitLinesBehindDataEnabled())
        mAxisRendererLeft.renderLimitLines(canvas);

    if (mAxisRight.isEnabled() && !mAxisRight.isDrawLimitLinesBehindDataEnabled())
        mAxisRendererRight.renderLimitLines(canvas);

    mXAxisRenderer.renderAxisLabels(canvas);
    mAxisRendererLeft.renderAxisLabels(canvas);
    mAxisRendererRight.renderAxisLabels(canvas);

    if (isClipValuesToContentEnabled()) {
        clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());

        mRenderer.drawValues(canvas);

        canvas.restoreToCount(clipRestoreCount);
    } else {
        mRenderer.drawValues(canvas);
    }

    mLegendRenderer.renderLegend(canvas);

    drawDescription(canvas);

    drawMarkers(canvas);

可以看到,在onDraw里面,作者把整个图表的绘制大致拆成了很多子模块,View(Chart extend View)本身处理的主要就是一些公共方法,例如自动缩放,canvas 旋转,背景绘制等.组件之间的关系大致如下:

image

轴:

Axis负责对该轴的行为进行约束管理,例如轴线,限制线,标注等.

Renderer负责具体绘制行为,同时从Axis中拿到对应信息

Chart提供Canvas,DBS.询问Axis是否需要进行绘制.

Data:

DataRender:数据绘制,Axis就是Chart本身.

Legend: LegendRender:Legend绘制,无具体Axis.

其它:

description,markers,Log等等

这里简单整理了一下大致流程图:

image

对了,作者本身提供了一个Log来统计图表的绘制时间,在项目中如果要调试这一部分的性能,是可以把这个Log打开的

3.一些关键类的介绍

3.1 ViewPortHandler

这玩意,就是我在上面说的DBC了.

先来看看作者的描述:

    *Class that contains information about the charts current viewport settings, including offsets, scale & translation** L**evels,*

这个类包含了一些关于视图的信息和设置,有offsert(偏移量),scale(缩放比),translation(位移信息)等.

由此可知,通过ViewPortHandler,我们可以得到这个图表很多重要信息,例如图表的宽高(同样也有初始化时机问题,否则会为0),缩放比(缩放也是通过该类进行实现的),偏移量(这玩意终于有一个比较好的解决方案了,以往在自定义View中很容易玩脱)等.

3.2 Axis(轴信息封装类)

对应Chart(饼图,雷达图除外)中X,Y轴,每一个轴都有自己的封装.值得注意的是,要区分XAxis和YAxis.

Axis轴中,主要封装的信息:

ValueFormatter轴格式化器,会将对应轴上的点传递下来,用于展示轴上的标注信息,如X轴可以看做是传递的数据索引值,可以用来从原始数据中获取到时间,或者其它相关信息.Y轴获取的是数值信息,可以用来做单位格式化信息等.

Color,Width,轴线宽度,颜色.绘制与否等.

Entry:Label的数量,是否绘制等.

Space最小单位间隔(让每个数据点之间的距离更宽阔,或更密集)

AxisMaxNum(最大值,同样的,还有最小值,不设置就是数据的最大最小值,同样也可以自定义最大最小值)

Tips:可以通过一些设置来让标签不重叠,不超出边界.

3.3 Renderer渲染器

Tips:如果需要自定义,记得把Chart的Axis,Renderer,Transformer传递下去,否则会出现各种数据绘制不统一的问题.

主要是根据Axis的信息,在Chart需要的时候进行内容绘制.

喜闻乐见的canvas.drawXXX()

由于篇幅问题,这里不对Chart的绘制多做分析了,MPAndroid的绘制可以说是一个非常细致的过程,作者在对数据的处理(Buffer等),对象的回收(MPPoint),缩放;等方面做了很多努力.下次如果有时间,再单独写一篇关于它在绘制中,对数据处理这些方面的分析

以上,能阅读到这里,十分感谢,希望本文有帮到你.
....看在编辑文本搞了半个多小时的情况下,不点个赞再走?

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

推荐阅读更多精彩内容