前言
一直在使用MPAndroidChart但对其内部机制却没有做多少了解,自己之前还修改过MPAndroidChart的源码,某次面试被问到,MPAndroidChart是怎样进行绘制的,瞬间一脸懵逼,回答了个大概,但是被看出其实不是很了解。算亡羊补牢吧,今天抽了点时间看了MPAndroidChart 3.0的源码部分。
直接进入正题吧。
Chart基类
这边顺便讲解一下Chart这个类,它是所有图表类的抽象基类,继承自ViewGroup,实现了图表接口ChartInterface(这个接口用来实现图表的大小,边界,和范围的获取。),Chart里面存放了一些公共的配置和一些共有的抽象方法,数据等。
|
|
它是所有图表的基类,里面是一些基础的方法,包括数据,高亮,动画,描述,空数据等公用方法的实现和抽象。保存当前图表信息等…
继承结构如下
- Chart(图表类基类)
- BarLineChartBase(柱状图折线图抽象类)
- BubbleChart(气泡图)
- CandleStickChart(烛状图)
- CombinedChart(复合图表)
- BarChart(柱状图)
- LineChart(折线图)
- ScatterChart(散点图)
- PieRadarChartBase(饼状图雷达图抽象类)
- PieChart(饼状图)
- RadarChart(雷达图)
- BarLineChartBase(柱状图折线图抽象类)
根据不同的图表做了不同的实现,比如说BarLineChartBase都有一个共同的属性是XY轴,在onDraw方法中对XY轴做了绘制,BarLineChartBase还支持缩放操作。PieRadarBase是没有XY轴且不支持缩放操作,但支持旋转。所以将这两个图单独抽象了一层。
onDraw算是共有的主要方法。因为数据绘制都是在onDraw方法中的canvas上面。
下面来讲解一下MPAndroidChart的绘制过程
MPAndroidChart绘制过程
所有的绘制都做了抽象在这边基本能重用的方法就做一层抽象。
我们来看一下最底层的抽象Renderer类
Chart的绘制是经由Renderer类之手,看一下Renderer类的实现。
Render
|
|
这个类里面的方法用来确定当前视图可显示的大小,所有的Render类都继承自它。包括AxisRenderer(轴和轴值绘制),LegendRender(图例绘制),DataRender(图表图形绘制)。
AxisRenderer,和LegendRender的实现都大同小异,DataRender属于图表绘制的抽象,因为图表的样式比较多,它扩展了一些可以供图表使用的方法,接下来主要拿DataRender来讲。
DataRender
|
|
DataRender里面的方法大致如下
- 图表的绘制抽象方法 (drawData)
- 数值绘制的抽象方法 (drawValues)
- 数值绘制方法(drawValue)
- 图表高亮抽象方法(drawHighlighted)
- 动画对象和画笔对象的初始化(构造函数)
看一下BarChartRender的实现
BarChartRender
public class BarChartRenderer extends DataRenderer {
/**
* BarDataProvider这个类中存放了所有的barData,还有一些类似于阴影,数值位置,高亮箭头等。
*/
protected BarDataProvider mChart;
/** the rect object that is used for drawing the bars
* 这个是用来设置每条bar的大小。主要是用做高亮绘制
*/
protected RectF mBarRect = new RectF();
/**
* 声明buffer的数组
*/
protected BarBuffer[] mBarBuffers;
/**
* 阴影画笔
*/
protected Paint mShadowPaint;
/**
* 边框画笔
*/
protected Paint mBarBorderPaint;
/**
* 构造函数传入了BarDataProvider,动画类,显示位置控制类,初始化了绘制所需的画笔
*/
public BarChartRenderer(BarDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(animator, viewPortHandler);
this.mChart = chart;
mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHighlightPaint.setStyle(Paint.Style.FILL);
mHighlightPaint.setColor(Color.rgb(0, 0, 0));
// set alpha after color
mHighlightPaint.setAlpha(120);
mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mShadowPaint.setStyle(Paint.Style.FILL);
mBarBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarBorderPaint.setStyle(Paint.Style.STROKE);
}
/**
* 初始化Buffer
*/
public void initBuffers() {
BarData barData = mChart.getBarData();
mBarBuffers = new BarBuffer[barData.getDataSetCount()];
for (int i = 0; i < mBarBuffers.length; i++) {
IBarDataSet set = barData.getDataSetByIndex(i);
mBarBuffers[i] = new BarBuffer(set.getEntryCount() * 4 * (set.isStacked() ? set.getStackSize() : 1),
barData.getGroupSpace(),
barData.getDataSetCount(), set.isStacked());
}
}
/**
* 绘制图表
* @param c
*/
public void drawData(Canvas c) {
BarData barData = mChart.getBarData();
for (int i = 0; i < barData.getDataSetCount(); i++) {
IBarDataSet set = barData.getDataSetByIndex(i);
if (set.isVisible() && set.getEntryCount() > 0) {
drawDataSet(c, set, i);
}
}
}
/**
* 根据每个dataset绘制图表
*/
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
...略
}
/**
* 准备高亮的区域
*/
protected void prepareBarHighlight(float x, float y1, float y2, float barspaceHalf,
Transformer trans) {
float barWidth = 0.5f;
float left = x - barWidth + barspaceHalf;
float right = x + barWidth - barspaceHalf;
float top = y1;
float bottom = y2;
mBarRect.set(left, top, right, bottom);
trans.rectValueToPixel(mBarRect, mAnimator.getPhaseY());
}
/**
* 绘制数值 一系列的计算
*/
public void drawValues(Canvas c) {
略... }
/**
* 绘制高亮 一系列的计算
*/
public void drawHighlighted(Canvas c, Highlight[] indices) {
略...
}
public float[] getTransformedValues(Transformer trans, IBarDataSet data,
int dataSetIndex) {
return trans.generateTransformedValuesBarChart(data, dataSetIndex,
mChart.getBarData(),
mAnimator.getPhaseY());
}
/**
* 检查是否为可显示的值
* @return
*/
protected boolean passesCheck() {
return mChart.getBarData().getYValCount() < mChart.getMaxVisibleCount()
* mViewPortHandler.getScaleX();
}
/**
* 绘制其他
* @param c
*/
public void drawExtras(Canvas c) { }
}
可以看到里面就是图表项和文本绘制的具体实现了,主要方法是drawvalues方法,大致就是一些通过一些方法计算每条bar的值,然后进行绘制。
总结
在这边简单讲解一下设计的方法。因为所有的Chart都继承了ViewGroup,实现了View的onMeasure,onDraw,onLayout,onSizeChanged方法,所以它是可以像自定义控件一样来使用。
View的绘制都再Render中实现,不同图表实现了不同的Render,继承结构大概如下:
- Render基类
- AxisRender(轴绘制)
- DataRender(图表绘制抽象类)
- CombinedChartRender(复合图表绘制,这个类是3.0版本添加的,可以展示折线图,柱状图,散点图等混合)
- BubbleChartRenderer(气泡图绘制)
- BarChartRender(柱状图绘制)
- LineScatterCandleRadarRenderer(折线图,散点图,烛状图,雷达图抽象类)
- LineRadarRenderer(折线图,雷达图抽象)
- LineChartRender(折线图绘制类)
- RadarChartRender(雷达图绘制类)
- ScatterChartRender(散点图绘制)
- CandleStickChartRenderer(烛状图绘制)
- LineRadarRenderer(折线图,雷达图抽象)
- LegendRender(图例绘制)
在不同的图表构造中初始化不同的将mRender对象初始化成不同的图表的Render对象,这里传入的参数有
- 不同图表的DataProvider(DataProvider是一个接口,实现了获取Y轴方向(左或右),和获取数据的方法)。
- ChartAnimator这是一个动画类,执行动画效果
- ViewPortHandler 图表信息类,包括边距,大小,转换等级(缩放)
之后这些Render类就根据自己的实现在canvas上面绘制东西了。
其他补充
由于很好奇它的点击事件是怎么实现的,这边也看了一下它的点击事件。
每个图表写了自己的TouchListener
在构造中需要传入的参数有
- 图表大小的矩阵(用来计算缩放等级,还有当前点击事件位置)
- 图表对象
之后根据Touch事件判断相应的手势或者点击,触摸事件作出反应(点击,手势缩放,移动等)。调用View的postInvalidate 方法通知刷新。
By Xiaolong,每一天都值得被认真对待!