背景
金融軟件現(xiàn)在的k線圖功能強(qiáng)大瞪醋,支持各種各樣的指標(biāo)成黄。但是指數(shù)的計(jì)算和繪制其實(shí)是有點(diǎn)復(fù)雜的玻墅。我不炒股也不炒幣介牙,所以對(duì)指標(biāo)背后蘊(yùn)含的市場(chǎng)含義不懂,也不太感興趣澳厢。前不久做了一個(gè)相關(guān)的需求环础,今天總結(jié)一下。
MPAndroidChart
https://github.com/PhilJay/MPAndroidChart 這是github地址剩拢。筆者使用的版本為v2.2.5
线得,做了很多定制。 MPAndroidChart
功能很強(qiáng)大徐伐,使用者一些特殊的需求也可以自己定制實(shí)現(xiàn)贯钩,因?yàn)樵搸?kù)的設(shè)計(jì)很靈活,性能也不錯(cuò)办素。其有付費(fèi)版本魏保,但我覺得基于開源版本就可以在性能和功能上滿足開發(fā)者的需求。下面我想先捋一下它的結(jié)構(gòu)設(shè)計(jì)摸屠,然后結(jié)合實(shí)際例子講一下它的使用谓罗。
MPAndroidChart的設(shè)計(jì)
根據(jù)上圖簡(jiǎn)明扼要敘述一下各包的作用:
-
animation -- 動(dòng)畫
image buffer 數(shù)據(jù)類,用來提高繪制效率季二,可以看成是緩存檩咱。 舉個(gè)例子揭措,我們要繪制柱狀圖,其中每個(gè)數(shù)據(jù)從點(diǎn)轉(zhuǎn)換成柱狀(四個(gè)點(diǎn)刻蚯,矩形柱狀每個(gè)角對(duì)應(yīng)一個(gè)點(diǎn))绊含,
BarBuffer
類是怎么做的呢?根據(jù)Entry
數(shù)據(jù)的value(點(diǎn))轉(zhuǎn)成矩形圖像炊汹。
... 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 -- 原始數(shù)據(jù)類,與
buffer
有所不同讨便,這個(gè)包里根據(jù)不同圖表封裝了不同的數(shù)據(jù)類型充甚。 - formatter -- 要繪制的文字的格式(例如x軸的刻度值的格式)
- highlight -- 高亮線(選中圖表上某個(gè)點(diǎn)時(shí)出現(xiàn)的高亮狀態(tài))
- interfaces -- 項(xiàng)目中全局的接口定義(主要是數(shù)據(jù)相關(guān)的的接口定義)
- jobs -- chart的滑動(dòng)縮放處理工作
- listener -- 各種監(jiān)聽器
- render -- 渲染器, 所有的繪制工作(各種圖表的繪制霸褒,軸的繪制伴找,背景分割線,legend/highlight等等)
- util -- 工具類废菱,最重要的有
ViewPortHandler
,Transformer
uml類圖結(jié)構(gòu)
項(xiàng)目支持的圖表很多技矮,uml全部呈現(xiàn)顯得比較繁雜,我們就以LineChart(線圖)為例殊轴。盡量用最少的信息來理解該庫(kù)的設(shè)計(jì)衰倦,所以我們只包含線圖,不包含x軸/y軸/legend/markview等旁理。
[圖片上傳失敗...(image-eb4cc9-1617104235932)]
chart
chart包里面都是圖表相關(guān)的類樊零,這里以LineChart
為例,剖析它的繼承層次韧拒,以及每一個(gè)父類的職責(zé)淹接。
首先是Chart.class
這個(gè)最基礎(chǔ)的基類十性。
public abstract class Chart<T extends ChartData<? extends IDataSet<? extends Entry>>> extends
ViewGroup
implements ChartInterface {
...
}
Chart
繼承自ViewGroup
叛溢,其重寫了onMeasure
以及onLayout
方法。還定義了一些抽象的模版方法劲适。Chart
的主要職責(zé)就是進(jìn)行measure和layout楷掉,以及一些公共行為定義,其它相關(guān)工作交給了對(duì)應(yīng)的對(duì)象霞势。例如繪制交給了#mRender
,手勢(shì)監(jiān)聽交給了#mChartTouchListener
烹植,縮放/移動(dòng)處理交給了#mViewPortHandler
。
BarLineChartBase
繼承自Chart
愕贡,從類名就可看出它抽象了線圖和柱狀圖的一些公共行為草雕。
LineChart
繼承自BarLineChartBase
,初始化#mRender
render
渲染器,繪制職責(zé)。LineChartRenderer
實(shí)現(xiàn)的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
把數(shù)據(jù)值映射成屏幕上的像素點(diǎn)墩虹,用matrix實(shí)現(xiàn), path的映射主要用到下面?zhèn)z個(gè)方法嘱巾。
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);
}
手勢(shì)處理
上圖是手勢(shì)move操作的時(shí)序圖,然后修改matrix, ViewPortHandler
根據(jù)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);
}
動(dòng)畫
動(dòng)畫的實(shí)現(xiàn)是使用屬性動(dòng)畫進(jìn)行實(shí)現(xiàn)的,以線圖為例菌湃,屬性動(dòng)畫的值從0到1问拘,每個(gè)繪制周期根據(jù)屬性動(dòng)畫的值計(jì)算要繪制的線圖范圍,這樣就實(shí)現(xiàn)了動(dòng)畫效果惧所。以包分析時(shí)的gif圖片所展示的動(dòng)畫為例骤坐,時(shí)序圖如下:
MPAndroidChart的優(yōu)化
這個(gè)庫(kù)性能優(yōu)化方便后續(xù)再補(bǔ)。
上面從這個(gè)庫(kù)的各個(gè)方面分析了它的設(shè)計(jì),實(shí)現(xiàn)原理。下一篇會(huì)介紹基于該庫(kù)實(shí)現(xiàn)不同的指標(biāo)谬返,有的指標(biāo)只是純粹的線圖低矮,只做數(shù)值計(jì)算即可,有的指標(biāo)的展現(xiàn)形式有點(diǎn)特殊色难,就需要對(duì)這個(gè)庫(kù)進(jìn)行定制化。