MPAndroidChart結(jié)構(gòu)和源碼分析

使用

在分析之前,先了解如何使用該庫(kù)戒职。然后根據(jù)使用流程開枝散葉般去分析其中的具體分支劲绪。首先MPAndroidChart的使用大致可以總結(jié)為下面的使用流程圖:

使用流程

下面根據(jù)流程圖來(lái)一步步的粗略介紹下代碼中的使用步驟

  • 假設(shè)我們從后臺(tái)接口中成功獲取到圖表中繪制所需的數(shù)據(jù)集
  • 然后將數(shù)據(jù)包裝成MPAndroidChart能夠識(shí)別的數(shù)據(jù)類型爷抓。
  • 最后將數(shù)據(jù)傳給Chart,并通知其刷新繪制

包裝過程的代碼大致如下:

   //可以通俗的理解一個(gè)Entry對(duì)應(yīng)于圖表中的一個(gè)點(diǎn)
   Entry c1e1 = new Entry(0f, 100000f);    
   Entry c1e2 = new Entry(1f, 140000f);
    ...

   //將Entry轉(zhuǎn)換成DateSet(可以理解為圖表中的一條線)
   List<Entry> valsComp1 = new ArrayList<Entry>();
   valsComp1.add(c1e1);
   valsComp1.add(c1e2);
   ...
   LineDataSet setComp1 = new LineDataSet(valsComp1, "Company 1")
   
    //將所有的數(shù)據(jù)集DateSet包裝成ChartData  然后set給Chart
    List<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
    dataSets.add(setComp1);
    dataSets.add(setComp2);
    ...
    LineData data = new LineData(dataSets);
    mLineChart.setData(data);
    mLineChart.invalidate(); // refresh

更多使用方法詳解查看官網(wǎng)Wiki

結(jié)構(gòu)分析

如果仔細(xì)的去看MPAndroidChart中的每一個(gè)圖表或者細(xì)節(jié)的實(shí)現(xiàn)是非常枯燥和耗時(shí)間的步淹。最好的方法是先選取一個(gè)分支了解其大致結(jié)構(gòu)从隆,然后了解細(xì)節(jié),最后再了解其他分支缭裆。這里根據(jù)分析LineChart的源碼键闺,大致的總結(jié)整理出一張流程圖。

MPAndroidChart結(jié)構(gòu)圖.png

根據(jù)上面的結(jié)構(gòu)圖幼驶,可以直觀的發(fā)現(xiàn)艾杏。其實(shí)Chart的作用主要是中心調(diào)度.具體的實(shí)現(xiàn)由其他部分實(shí)現(xiàn),達(dá)到各部分解耦的效果盅藻。下面我們遵循由點(diǎn)到面的思路去分析整個(gè)結(jié)構(gòu)购桑。

1.先看組件部分,該部分主要是包括X氏淑、Y軸線勃蜘,限制線、標(biāo)注等等假残。同樣的選取AxisBase進(jìn)行源碼分析缭贡,其主要是包括軸線寬度炉擅、最大最小值、顏色阳惹、是否繪制網(wǎng)格線等等谍失,并提供相對(duì)于的Set和Get方法。我們也可以根據(jù)自己想需求在它的基礎(chǔ)上去進(jìn)一步設(shè)置莹汤。

2.再看數(shù)據(jù)部分快鱼,大致使用過程在開始就介紹過了。主要就是對(duì)數(shù)據(jù)進(jìn)行一層一層的裝換纲岭。同時(shí)抹竹,其內(nèi)部提供了相關(guān)的增刪查減操作。
3.第三部分?jǐn)?shù)據(jù)渲染止潮。其主體實(shí)現(xiàn)主要是Render及其子類窃判。觀察源碼可以清楚的看到Render的作用可以總結(jié)為兩點(diǎn),一是計(jì)算喇闸,二是繪制袄琳。Chart通過Render先進(jìn)行圖表數(shù)據(jù)和參數(shù)的準(zhǔn)備,既計(jì)算操作仅偎。比如計(jì)算出Y軸上的最大最小值跨蟹,以此可以算出X軸之前的間距雳殊。比如如下代碼:

/**
     * Sets up the axis values. Computes the desired number of labels between the two given extremes.
     *
     * @return
     */
    protected void computeAxisValues(float min, float max) {

        float yMin = min;
        float yMax = max;

        int labelCount = mAxis.getLabelCount();
        double range = Math.abs(yMax - yMin);

        if (labelCount == 0 || range <= 0 || Double.isInfinite(range)) {
            mAxis.mEntries = new float[]{};
            mAxis.mCenteredEntries = new float[]{};
            mAxis.mEntryCount = 0;
            return;
        }

        // Find out how much spacing (in y value space) between axis values
        double rawInterval = range / labelCount;
        double interval = Utils.roundToNextSignificant(rawInterval);
        ......
        
   }

對(duì)于圖表是如何控制Render計(jì)算和繪制的橘沥,這里整理出一張時(shí)序圖方便理解:

MPAndroidChart數(shù)據(jù)渲染時(shí)序圖.png

大致的解釋一下就是,當(dāng)我們通過Chart.SetData傳入數(shù)據(jù)到圖表中夯秃,會(huì)調(diào)用到內(nèi)部的notifyDataSetChanged()方法座咆,然后會(huì)通知Render進(jìn)行相關(guān)的計(jì)算操作。值得一提的是仓洼,在這個(gè)時(shí)候會(huì)調(diào)用Transformer完成"value-touch-offset"的轉(zhuǎn)換過程介陶,其內(nèi)部是通過操作Matrix實(shí)現(xiàn)的。而在圖表第一次顯示或者后續(xù)發(fā)生移動(dòng)色建、縮放哺呜、平移等操作的時(shí)候,會(huì)調(diào)用到Chart.OnDraw方法進(jìn)行繪制箕戳。而在Chart.OnDraw會(huì)重新進(jìn)行計(jì)算操作某残,同時(shí)會(huì)調(diào)用Render的renderXXX方法進(jìn)行具體的繪制×晡可以通過觀看如下代碼更加直觀明白:

Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mData == null)
            return;

        long starttime = System.currentTimeMillis();

        // execute all drawing commands
        drawGridBackground(canvas);

        if (mAutoScaleMinMaxEnabled) {
            autoScale();
        }

        if (mAxisLeft.isEnabled())
            mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted());

        if (mAxisRight.isEnabled())
            mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted());

        if (mXAxis.isEnabled())
            mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);

        mXAxisRenderer.renderAxisLine(canvas);
        mAxisRendererLeft.renderAxisLine(canvas);
        mAxisRendererRight.renderAxisLine(canvas);

        mXAxisRenderer.renderGridLines(canvas);
        mAxisRendererLeft.renderGridLines(canvas);
        mAxisRendererRight.renderGridLines(canvas);

        if (mXAxis.isEnabled() && mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

        if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);

        if (mAxisRight.isEnabled() && mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);

        // make sure the data cannot be drawn outside the content-rect
        int clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());

        mRenderer.drawData(canvas);

        // if highlighting is enabled
        if (valuesToHighlight())
            mRenderer.drawHighlighted(canvas, mIndicesToHighlight);

        // Removes clipping rectangle
        canvas.restoreToCount(clipRestoreCount);

        mRenderer.drawExtras(canvas);

        if (mXAxis.isEnabled() && !mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

        if (mAxisLeft.isEnabled() && !mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);

        if (mAxisRight.isEnabled() && !mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);

        mXAxisRenderer.renderAxisLabels(canvas);
        mAxisRendererLeft.renderAxisLabels(canvas);
        mAxisRendererRight.renderAxisLabels(canvas);

        if (isClipValuesToContentEnabled()) {
            clipRestoreCount = canvas.save();
            canvas.clipRect(mViewPortHandler.getContentRect());

            mRenderer.drawValues(canvas);

            canvas.restoreToCount(clipRestoreCount);
        } else {
            mRenderer.drawValues(canvas);
        }

        mLegendRenderer.renderLegend(canvas);

        drawDescription(canvas);

        drawMarkers(canvas);

        if (mLogEnabled) {
            long drawtime = (System.currentTimeMillis() - starttime);
            totalTime += drawtime;
            drawCycles += 1;
            long average = totalTime / drawCycles;
            Log.i(LOG_TAG, "Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: "
                    + drawCycles);
        }
    }

4.結(jié)構(gòu)圖中將ViewPortHandler單獨(dú)放出的原因是因?yàn)樗贑hart和Render之間擔(dān)任一個(gè)“信使”的作用(這里的理解不知道是否有誤),Chart將一些數(shù)據(jù)和數(shù)據(jù)告訴ViewPortHandler玻墅,比如最大最小縮放,Chart的長(zhǎng)度和寬度壮虫,偏移量等等澳厢,而Render則從其中獲取這部分信息。對(duì)于MPAndroidChart中的其他部分,沒做過多的分析剩拢,比如數(shù)據(jù)格式器线得、動(dòng)畫等需進(jìn)一步了解分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徐伐,一起剝皮案震驚了整個(gè)濱河市框都,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呵晨,老刑警劉巖魏保,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異摸屠,居然都是意外死亡谓罗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門季二,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)檩咱,“玉大人,你說(shuō)我怎么就攤上這事胯舷】舔牵” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵桑嘶,是天一觀的道長(zhǎng)炊汹。 經(jīng)常有香客問我,道長(zhǎng)逃顶,這世上最難降的妖魔是什么讨便? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮以政,結(jié)果婚禮上霸褒,老公的妹妹穿的比我還像新娘。我一直安慰自己盈蛮,他們只是感情好废菱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抖誉,像睡著了一般殊轴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寸五,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天梳凛,我揣著相機(jī)與錄音,去河邊找鬼梳杏。 笑死韧拒,一個(gè)胖子當(dāng)著我的面吹牛淹接,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叛溢,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼塑悼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了楷掉?” 一聲冷哼從身側(cè)響起厢蒜,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烹植,沒想到半個(gè)月后斑鸦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡草雕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年巷屿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墩虹。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘱巾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诫钓,到底是詐尸還是另有隱情旬昭,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布问拘,位于F島的核電站慢味,受9級(jí)特大地震影響纯路,放射性物質(zhì)發(fā)生泄漏寞忿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一叫编、第九天 我趴在偏房一處隱蔽的房頂上張望霹抛。 院中可真熱鬧,春花似錦霞篡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赁豆,卻和暖如春冗美,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墩衙。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留心铃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓去扣,卻偏偏與公主長(zhǎng)得像樊破,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哲戚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 1顺少、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,980評(píng)論 3 119
  • 月懸枝頭疑地霜脆炎, 了星四綴許羽傍。 忽聞犬吠十里傳秒裕, 原為天狗食月亮几蜻。 天寒足涼渾不知体斩, 霜消星現(xiàn)人心涼响蓉。 天涯此...
    寶政閱讀 271評(píng)論 0 0
  • 回顧上一章 天空湛藍(lán)枫甲,白云在天上悠閑地飄著,陽(yáng)光透過窗簾撒在顧小煙的臉上想幻,有一種溫暖的感覺脏毯。 一切闹究,都顯得剛剛好食店。...
    黑貓by西瓷閱讀 1,706評(píng)論 0 1