基于MPAndroidChart實(shí)現(xiàn)股票K線(xiàn)圖之自定義HighLight樣式及源碼淺析

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的自定義。

初始的HighLight效果

項(xiàng)目需求的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:

  1. BarChart
public class BarChart extends BarLineChartBase<BarData> implements BarDataProvider {

里面并沒(méi)有關(guān)于繪制的邏輯,只有一些set/get方法。
于是我們把目標(biāo)放在父類(lèi)BarLineChartBase

  1. 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é):

chart

到這一步,大家應(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);
    }
  1. 把目光重新放到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

  1. 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)是:


Rect
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ò)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,555評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,900評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,976評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評(píng)論 3 448
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,139評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,233評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,567評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,362評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容