1 前言
之前的文章提到過(guò)如何基于MPAndroidChart來(lái)實(shí)現(xiàn)股票K線(xiàn)圖的效果,顯然這個(gè)開(kāi)源框架十分強(qiáng)大,連縮放、拖曳、坐標(biāo)自動(dòng)適配數(shù)據(jù)等交互細(xì)節(jié)都很高效率地完善了。但畢竟是別人寫(xiě)的框架,再?gòu)?qiáng)大也未必能夠滿(mǎn)足老板和產(chǎn)品經(jīng)理的需求。所以下面就從源碼入手 進(jìn)行手指選中時(shí)HighLight的自定義。
框架并沒(méi)有提供諸如
setHighLight
的方法給我們?nèi)ピO(shè)置這個(gè)選中效果。而BarChart
的默認(rèn)HighLight效果就是被選中的條形覆蓋了一層半透明的陰影(這點(diǎn)在源碼中可以發(fā)現(xiàn)),我們需要的效果是上圖二中的一條白色的直線(xiàn)效果。所以只能從源碼入手,研究繪制HighLight效果的邏輯,看看能不能找出倪端,達(dá)到我們想要的效果。
2 源碼梳理
我們研究的對(duì)象是BarChart
:
- BarChart
public class BarChart extends BarLineChartBase<BarData> implements BarDataProvider {
里面并沒(méi)有關(guān)于繪制的邏輯,只有一些set/get方法。
于是我們把目標(biāo)放在父類(lèi)BarLineChartBase
。
- BarLineChartBase
......
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
this.mXAxisRenderer.renderAxisLine(canvas);
this.mAxisRendererLeft.renderAxisLine(canvas);
this.mAxisRendererRight.renderAxisLine(canvas);
...
this.mXAxisRenderer.renderGridLines(canvas);
this.mAxisRendererLeft.renderGridLines(canvas);
this.mAxisRendererRight.renderGridLines(canvas);
if(this.mXAxis.isDrawLimitLinesBehindDataEnabled()) {
this.mXAxisRenderer.renderLimitLines(canvas);
}
if(this.mAxisLeft.isDrawLimitLinesBehindDataEnabled()) {
this.mAxisRendererLeft.renderLimitLines(canvas);
}
if(this.mAxisRight.isDrawLimitLinesBehindDataEnabled()) {
this.mAxisRendererRight.renderLimitLines(canvas);
}
...
this.mXAxisRenderer.renderAxisLabels(canvas);
this.mAxisRendererLeft.renderAxisLabels(canvas);
this.mAxisRendererRight.renderAxisLabels(canvas);
this.mRenderer.drawValues(canvas);
this.mLegendRenderer.renderLegend(canvas);
this.drawMarkers(canvas);
this.drawDescription(canvas);
...
}
一下子就看了我們想要的onDraw(Canvas canvas)
方法,在Android中,一般控件即View的繪制都是在這里面完成。
很容易就發(fā)現(xiàn),onDraw(Canvas canvas)
里的邏輯基本上都是一些“Renderer”。這個(gè)單詞的字面意思就是渲染器,從源碼里面可以看到,不同的Renderer負(fù)責(zé)繪制整個(gè)Chart的不同部分,下圖大概地展示了Renderer的職責(zé):
到這一步,大家應(yīng)該都恍然大悟,
MPAndroidChart
提供了多達(dá)八種圖形,這些圖表形態(tài)各異,但是渲染圖形的行為都有一定的共性。框架的開(kāi)發(fā)者Philipp Jahoda,對(duì)這些渲染行為進(jìn)行抽象,把繪制的共同行為分別定義在不同的Renderer
抽象類(lèi)中里,從而將繪制的邏輯代碼從View中分離開(kāi)來(lái),然后根據(jù)不同的Chart
對(duì)這些Renderer進(jìn)行具體的實(shí)現(xiàn)。因此,整個(gè)框架變得十分靈活,不同類(lèi)型的Chart繪制邏輯得到解耦,代碼的可擴(kuò)展性和易維護(hù)性十分高。面向?qū)ο蟮乃枷氲玫搅芾毂M致的體現(xiàn),實(shí)在是我輩楷模。膜拜完以后,言歸正傳,我們終于發(fā)現(xiàn)
drawHighLight
的蛛絲馬跡。在DataRenderer中:
public abstract class DataRenderer extends Renderer {.
......
public abstract void drawHighlighted(Canvas var1, Highlight[] var2);
}
- 把目光重新放到
BarChart
上:
構(gòu)造函數(shù)中執(zhí)行了init()
進(jìn)行一些成員變量的初始化。
public class BarChart extends BarLineChartBase<BarData> implements BarDataProvider {
/** * object responsible for rendering the data */
protected DataRenderer mRenderer;
protected void init() {
super.init();
this.mRenderer = new BarChartRenderer(this, this.mAnimator, this.mViewPortHandler);
this.mXAxisRenderer = new XAxisRendererBarChart(this.mViewPortHandler, this.mXAxis, this.mLeftAxisTransformer, this);
this.setHighlighter(new BarHighlighter(this));
this.mXAxis.mAxisMinimum = -0.5F;
}
可以看到,在BarChart中,DataRenderer 的具體實(shí)現(xiàn)是BarChartRenderer。
- BarChartRender
終于找到源碼繪制HighLight的邏輯。
public class BarChartRenderer extends DataRenderer {
....
public void drawHighlighted(Canvas c, Highlight[] indices) {
int setCount = this.mChart.getBarData().getDataSetCount();
for (int i = 0; i < indices.length; ++i) {
Highlight h = indices[i];
int index = h.getXIndex();
int dataSetIndex = h.getDataSetIndex();
IBarDataSet set = (IBarDataSet) this.mChart.getBarData().getDataSetByIndex(dataSetIndex);
......
BarEntry e = (BarEntry) set.getEntryForXIndex(index);
if (e != null && e.getXIndex() == index) {
float groupspace = this.mChart.getBarData().getGroupSpace();
float x = (float) (index * setCount + dataSetIndex) + groupspace / 2.0F + groupspace * (float) index;
float y1 = e.getVal();
float y2 = 0.0F;
this.prepareBarHighlight(x, y1, y2, barspaceHalf, trans);
c.drawRect(this.mBarRect, this.mHighlightPaint);
}
}
}
}
最后一行代碼是關(guān)鍵,很明顯就是繪制一個(gè)矩形。所以選中直方的HighLight效果就是整個(gè)矩形變暗了。
而我們只需要把drawRect
換成drawLine
,控制好繪制的位置即可。十分簡(jiǎn)單,可以說(shuō)只是修改了一行代碼就實(shí)現(xiàn)了文章開(kāi)頭想要的效果!
繼續(xù)往下看:
this.prepareBarHighlight(x, y1, y2, barspaceHalf, trans);
這個(gè)方法進(jìn)行了位置的計(jì)算:
/**
* Prepares a bar for being highlighted.
*
* @param x the x-position
* @param y1 the y1-position
* @param y2 the y2-position
* @param barspaceHalf the space between bars
* @param trans
*/
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());
}
3 我們要修改的代碼
上面mBarRect
就是原本HighLight繪制陰影的區(qū)域。
我們要把陰影變成矩形中間的一條直線(xiàn),這個(gè)實(shí)在太好辦了,把c.drawRect(this.mBarRect, this.mHighlightPaint);
換成以下代碼。
這條線(xiàn)與X軸垂直,其坐標(biāo)是:
float xValue = mBarRect.left + (mBarRect.right - mBarRect.left) / 2;
最后開(kāi)始drawLine:
c.drawLine(xValue, //startX
this.mViewPortHandler.contentTop(), //startY
xValue, //stopX
this.mViewPortHandler.contentBottom(), //stopY
this.mHighlightPaint); //paint
大功告成!
4 總結(jié)
想要改變MPAndroidChart的繪制渲染邏輯,從對(duì)應(yīng)的Renderer下手準(zhǔn)沒(méi)錯(cuò)。