背景
金融软件现在的k线图功能强大,支持各种各样的指标。但是指数的计算和绘制其实是有点复杂的。我不炒股也不炒币,所以对指标背后蕴含的市场含义不懂,也不太感兴趣。前不久做了一个相关的需求,今天总结一下。
MPAndroidChart
https://github.com/PhilJay/MPAndroidChart 这是github地址。笔者使用的版本为v2.2.5
,做了很多定制。 MPAndroidChart
功能很强大,使用者一些特殊的需求也可以自己定制实现,因为该库的设计很灵活,性能也不错。其有付费版本,但我觉得基于开源版本就可以在性能和功能上满足开发者的需求。下面我想先捋一下它的结构设计,然后结合实际例子讲一下它的使用。
MPAndroidChart的设计
image根据上图简明扼要叙述一下各包的作用:
-
animation -- 动画
image -
buffer 数据类,用来提高绘制效率,可以看成是缓存。 举个例子,我们要绘制柱状图,其中每个数据从点转换成柱状(四个点,矩形柱状每个角对应一个点),
BarBuffer
类是怎么做的呢?根据Entry
数据的value(点)转成矩形图像。
... start fori
float left = x - barWidth + barSpaceHalf;
float right = x + barWidth - barSpaceHalf;
float bottom, top;
if (mInverted) {
bottom = y >= 0 ? y : 0;
top = y <= 0 ? y : 0;
} else {
top = y >= 0 ? y : 0;
bottom = y <= 0 ? y : 0;
}
// multiply the height of the rect with the phase
if (top > 0)
top *= phaseY;
else
bottom *= phaseY;
addBar(left, top, right, bottom);
... end fori
protected void addBar(float left, float top, float right, float bottom) {
if (index >= buffer.length - 1) {
return;
}
buffer[index++] = left;
buffer[index++] = top;
buffer[index++] = right;
buffer[index++] = bottom;
}
- chart -- 包里面包含各种图表类
- components -- 图表的其它组件,例如描述/轴/限制线/legend 等等
- data -- 原始数据类,与
buffer
有所不同,这个包里根据不同图表封装了不同的数据类型。 - formatter -- 要绘制的文字的格式(例如x轴的刻度值的格式)
- highlight -- 高亮线(选中图表上某个点时出现的高亮状态)
- interfaces -- 项目中全局的接口定义(主要是数据相关的的接口定义)
- jobs -- chart的滑动缩放处理工作
- listener -- 各种监听器
- render -- 渲染器, 所有的绘制工作(各种图表的绘制,轴的绘制,背景分割线,legend/highlight等等)
- util -- 工具类,最重要的有
ViewPortHandler
,Transformer
uml类图结构
项目支持的图表很多,uml全部呈现显得比较繁杂,我们就以LineChart(线图)为例。尽量用最少的信息来理解该库的设计,所以我们只包含线图,不包含x轴/y轴/legend/markview等。
[图片上传失败...(image-eb4cc9-1617104235932)]
chart
chart包里面都是图表相关的类,这里以LineChart
为例,剖析它的继承层次,以及每一个父类的职责。
首先是Chart.class
这个最基础的基类。
public abstract class Chart<T extends ChartData<? extends IDataSet<? extends Entry>>> extends
ViewGroup
implements ChartInterface {
...
}
Chart
继承自ViewGroup
,其重写了onMeasure
以及onLayout
方法。还定义了一些抽象的模版方法。Chart
的主要职责就是进行measure和layout,以及一些公共行为定义,其它相关工作交给了对应的对象。例如绘制交给了#mRender
,手势监听交给了#mChartTouchListener
,缩放/移动处理交给了#mViewPortHandler
。
BarLineChartBase
继承自Chart
,从类名就可看出它抽象了线图和柱状图的一些公共行为。
LineChart
继承自BarLineChartBase
,初始化#mRender
render
渲染器,绘制职责。LineChartRenderer
实现的drawData
方法如下:
@Override
public synchronized void drawData(Canvas c) {
int width = (int) mViewPortHandler.getChartWidth();
int height = (int) mViewPortHandler.getChartHeight();
if (mDrawBitmap == null
|| (mDrawBitmap.get().getWidth() != width)
|| (mDrawBitmap.get().getHeight() != height)) {
if (width > 0 && height > 0) {
mDrawBitmap = new WeakReference<>(Bitmap.createBitmap(width, height, mBitmapConfig));
if (mDrawBitmap.get() == null) {
return;
}
mBitmapCanvas = new Canvas(mDrawBitmap.get());
} else
return;
}
mDrawBitmap.get().eraseColor(Color.TRANSPARENT);
LineData lineData = mChart.getLineData();
for (ILineDataSet set : lineData.getDataSets()) {
if (set.isVisible() && set.getEntryCount() > 0)
drawDataSet(c, set);
}
c.drawBitmap(mDrawBitmap.get(), 0, 0, mRenderPaint);
}
drawDataSet
方法就不深究了。
Transformer
把数据值映射成屏幕上的像素点,用matrix实现, path的映射主要用到下面俩个方法。
public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) {
float scaleX = (float) (mViewPortHandler.contentWidth() / deltaX);
float scaleY = (float) (mViewPortHandler.contentHeight() / deltaY);
if (Float.isInfinite(scaleX)) {
scaleX = 0;
}
if (Float.isInfinite(scaleY)) {
scaleY = 0;
}
// setup all matrices
mMatrixValueToPx.reset();
mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin);
mMatrixValueToPx.postScale(scaleX, -scaleY);
}
public void pathValueToPixel(Path path) {
path.transform(mMatrixValueToPx);
path.transform(mViewPortHandler.getMatrixTouch());
path.transform(mMatrixOffset);
}
手势处理
image上图是手势move操作的时序图,然后修改matrix, ViewPortHandler
根据matrix更新视图,Chart再重新绘制;
下面是修改matrix的代码:
private void performDrag(MotionEvent event) {
mLastGesture = ChartGesture.DRAG;
mMatrix.set(mSavedMatrix);
OnChartGestureListener l = mChart.getOnChartGestureListener();
float dX, dY;
// check if axis is inverted
if (mChart.isAnyAxisInverted() && mClosestDataSetToTouch != null
&& mChart.getAxis(mClosestDataSetToTouch.getAxisDependency()).isInverted()) {
// if there is an inverted horizontalbarchart
if (mChart instanceof HorizontalBarChart) {
dX = -(event.getX() - mTouchStartPoint.x);
dY = event.getY() - mTouchStartPoint.y;
} else {
dX = event.getX() - mTouchStartPoint.x;
dY = -(event.getY() - mTouchStartPoint.y);
}
} else {
dX = event.getX() - mTouchStartPoint.x;
dY = event.getY() - mTouchStartPoint.y;
}
mMatrix.postTranslate(dX, dY);
if (l != null)
l.onChartTranslate(event, dX, dY);
}
动画
动画的实现是使用属性动画进行实现的,以线图为例,属性动画的值从0到1,每个绘制周期根据属性动画的值计算要绘制的线图范围,这样就实现了动画效果。以包分析时的gif图片所展示的动画为例,时序图如下:
image
MPAndroidChart的优化
这个库性能优化方便后续再补。
上面从这个库的各个方面分析了它的设计,实现原理。下一篇会介绍基于该库实现不同的指标,有的指标只是纯粹的线图,只做数值计算即可,有的指标的展现形式有点特殊,就需要对这个库进行定制化。
网友评论