MPAndroid框架入门
- ----知其形,方知其意*
Tips:
本文使用的MPAndroid版本:3.0.1
文内不会有太多的API介绍,更多的是对MPAndroid图表绘制的理解,希望能帮助到大家
本文主要内容:
1,图表的组成分析
2,MPAndroid中折线图的组成
3,MPAndroid关键简介
一.图表的组成分析
如果某天拿到一个需求,搞一个收益走势图,大概长这个鬼样子:
(图比较丑,来自百度,将就一下)
第一眼看上去什么感觉?
是这样:一脸懵逼,这线怎么画的,这标注位置又是怎么确定的?
还是这样:这玩意简单得很,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的节奏.
不管你是哪种情况,咱们都来分析一波这个图上,到底都有些啥
第一步:分解看得到的元素
先不管这类图有多复杂,由显示内容大致都可以分为这四个部分:
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之类的就把代码搞乱.
小结:
基于以上几点的简单分析.我们可以大概得到一张这样的结构图:
二.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 旋转,背景绘制等.组件之间的关系大致如下:
轴:
Axis负责对该轴的行为进行约束管理,例如轴线,限制线,标注等.
Renderer负责具体绘制行为,同时从Axis中拿到对应信息
Chart提供Canvas,DBS.询问Axis是否需要进行绘制.
Data:
DataRender:数据绘制,Axis就是Chart本身.
Legend: LegendRender:Legend绘制,无具体Axis.
其它:
description,markers,Log等等
这里简单整理了一下大致流程图:
对了,作者本身提供了一个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),缩放;等方面做了很多努力.下次如果有时间,再单独写一篇关于它在绘制中,对数据处理这些方面的分析
以上,能阅读到这里,十分感谢,希望本文有帮到你.
....看在编辑文本搞了半个多小时的情况下,不点个赞再走?