6柱狀圖數(shù)據(jù)的繪制(MPAndroidChart源碼解析)

Chart中數(shù)據(jù)的繪制時通過一系列的Renderer類來實(shí)現(xiàn)的黍特,類結(jié)構(gòu)如下:


Renderer.png

下面以BarChartRenderer為例講解蛙讥。

1. BarBuffer(防止對象重復(fù)創(chuàng)建)

由于Renderer中方法的執(zhí)行大部分都是在onDraw方法中,為了防止對象的頻繁創(chuàng)建而引起內(nèi)存抖動問題(會造成卡段現(xiàn)象)灭衷,這里使用了Buffer的方式次慢。
BarBuffer中維護(hù)了一個float[],用來存儲所繪制的所有的點(diǎn)信息(Bar就是一個矩形,大小由left翔曲,top迫像,right,bottom四個點(diǎn)所決定)瞳遍,BarBuffer保存了所有數(shù)據(jù)的原始value值闻妓,當(dāng)我們縮放或者平移之后,會遍歷buffer中的每一個數(shù)據(jù)掠械,先判斷這個數(shù)據(jù)是否在可見范圍內(nèi)才繪制由缆,這樣至始至終,無論是動畫還是縮放或者是拖拽份蝴,我們都是從buffer中取數(shù)據(jù)犁功,然后根據(jù)Utils中的matrix的信息映射出px值進(jìn)行繪制氓轰,防止每次繪制都要重新創(chuàng)建一系列的點(diǎn)婚夫。
先來看一下BarBuffer中的主要字段:

 /** float-buffer that holds the data points to draw, order: x,y,x,y,... */
    public final float[] buffer;
//設(shè)置為1每一個bar會充滿它所在的x-index組,如果設(shè)置為0.9署鸡,相鄰兩個bar之間就會有一個0.1的間隔
    protected float mBarWidth = 1f;

BarBuffer中最重要的一個方法是feed(IBarDataSet data)案糙,它會遍歷傳入的BarDataSet中所有的BarEntry限嫌,根據(jù)BarEntry的x,y生成當(dāng)前bar的left时捌,top怒医,right,bottom奢讨,然后存如buffer中:

 @Override
    public void feed(IBarDataSet data) {
        //phaseX 是x軸動畫的進(jìn)度稚叹,可以先認(rèn)為是1
        float size = data.getEntryCount() * phaseX;
        //1.根據(jù)mBarWidth計(jì)算出bar的一半寬度,注意此時是百分比值
        float barWidthHalf = mBarWidth / 2f;

        for (int i = 0; i < size; i++) {

            BarEntry e = data.getEntryForIndex(i);

            if(e == null)
                continue;

            float x = e.getX();
            float y = e.getY();
            float[] vals = e.getYVals();
            //非堆疊圖
            if (!mContainsStacks || vals == null) {
                //2.以barEntry的x為中心點(diǎn)拿诸,計(jì)算出left扒袖,top,right亩码,bottom
                float left = x - barWidthHalf;
                float right = x + barWidthHalf;
                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;
                //3.添加到buffer中
                addBar(left, top, right, bottom);
            } else {//堆疊圖
             ...
            }
        }
        //重置index
        reset();
    }

2.BarBuffer的初始化

在BarLineChartBase的中會調(diào)用Renderer的initBuffers()方法

 @Override
    public void notifyDataSetChanged() {
        ...
        if (mRenderer != null)
            mRenderer.initBuffers();
     ...
    }
   @Override
    public void initBuffers() {
        BarData barData = mChart.getBarData();
        mBarBuffers = new BarBuffer[barData.getDataSetCount()];

        for (int i = 0; i < mBarBuffers.length; i++) {
            IBarDataSet set = barData.getDataSetByIndex(i);
            mBarBuffers[i] = new BarBuffer(set.getEntryCount() * 4 * (set.isStacked() ? set.getStackSize() : 1),
                    barData.getDataSetCount(), set.isStacked());
        }
    }

上面的代碼只是初始化了Barbuffer中長度季率,向其填充內(nèi)容實(shí)在繪制每一個DataSet中進(jìn)行的。

 protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
        Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
        float phaseX = mAnimator.getPhaseX();
        float phaseY = mAnimator.getPhaseY();
        ...
         BarBuffer buffer = mBarBuffers[index];
        buffer.setPhases(phaseX, phaseY);
        buffer.setDataSet(index);
        buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
        buffer.setBarWidth(mChart.getBarData().getBarWidth());
        //調(diào)用feed方法描沟,填充一系列的point
        buffer.feed(dataSet);
        //上面的buffer只是原始值value飒泻,需要通過Transformer映射成px值
        trans.pointValuesToPixel(buffer.buffer);
        ...
 }

通過上面的操作,Barbuffer中就填充好了所有要繪制的數(shù)據(jù)(px值)吏廉,剩下的就是遍歷這個buffer泞遗,四個點(diǎn)為一組繪制矩形了。
下面來看一下具體的繪制過程席覆。

3.具體繪制流程

首先會遍歷BarData繪制所有的DataSet:

 @Override
    public void drawData(Canvas c) {
        BarData barData = mChart.getBarData();
        for (int i = 0; i < barData.getDataSetCount(); i++) {
            IBarDataSet set = barData.getDataSetByIndex(i);
            if (set.isVisible()) {
                drawDataSet(c, set, i);
            }
        }
    }

在drawDataSet()中會先想BarBuffer中填充數(shù)據(jù)刹孔,然后遍歷buffer繪制矩形,需要注意的是,只會繪制在可見區(qū)域中的數(shù)據(jù)娜睛,可見區(qū)域就是ViewPortHandler中的mContentRect:

 protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {

        Transformer trans = mChart.getTransformer();
        //TODO 動畫暫不處理
//        float phaseX = mAnimator.getPhaseX();
//        float phaseY = mAnimator.getPhaseY();

        float phaseX = 1f;
        float phaseY = 1f;
        
        //1.初始化buffer
        BarBuffer buffer = mBarBuffers[index];
        buffer.setPhases(phaseX, phaseY);
        buffer.setDataSet(index);
        buffer.setInverted(false);
        buffer.setBarWidth(mChart.getBarData().getBarWidth());
        buffer.feed(dataSet);
        trans.pointValuesToPixel(buffer.buffer);

        final boolean isSingleColor = dataSet.getColors().size() == 1;
        if (isSingleColor) {
            mRenderPaint.setColor(dataSet.getColor());
        }
        
        //2.遍歷buffer繪制矩形
        for (int j = 0; j < buffer.size(); j += 4) {
            //如果這個bar的right 小于 content的left就不繪制
            if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))
                continue;
            //如果這個bar的left大于content的right就不繪制
            if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
                break;

            if (!isSingleColor) {
                mRenderPaint.setColor(dataSet.getColor(j / 4));
            }
            c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                    buffer.buffer[j + 3], mRenderPaint);
        }
    }

4.繪制柱狀圖上面的文字

文字的繪制是在drawValues方法中髓霞,類似于矩形的繪制,也是遍歷BarBuffer進(jìn)行繪制畦戒,我們只看單獨(dú)繪制一個dataSet中文字的流程:

 //0.獲取要繪制的dataSet
                IBarDataSet dataSet = datasets.get(i);
                //1.是否要繪制文字
                if (!shouldDrawValues(dataSet)) {
                    continue;
                }
//                mValuePaint.setTextSize(dataSet.getValueTextSize());
//                boolean isInverted = mChart.isInverted(dataSet.getAxisDependency());
                boolean isInverted = false;
                //2.計(jì)算文字的高度
                float valueTextHeight = Utils.calcTextHeight(mValuePaint, "8");
                posOffset = (drawValueAboveBar ? -valueOffsetPlus : valueTextHeight + valueOffsetPlus);
                negOffset = (drawValueAboveBar ? valueTextHeight + valueOffsetPlus : -valueOffsetPlus);

                if (isInverted) {
                    posOffset = -posOffset - valueTextHeight;
                    negOffset = -negOffset - valueTextHeight;
                }
                //3.獲取buffer
                BarBuffer buffer = mBarBuffers[i];
                //TODO 動畫相關(guān)方库,暫不考慮
//                final float phaseY = mAnimator.getPhaseY();
                final float phaseY = 1;

                //4.遍歷屬于該dataSet下的buffer繪制文字
                if (!dataSet.isStacked()) {
                    // TODO 動畫相關(guān),暫不考慮
//                    for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
                    //
                    for (int j = 0; j < buffer.buffer.length; j += 4) {
                        //5.文字的中心位置在bar的中心
                        float x = (buffer.buffer[j] + buffer.buffer[j + 2]) / 2f;
                        //6.從左向右繪制障斋,如果某個value的x超過了content的right則不繪制
                        if (!mViewPortHandler.isInBoundsRight(x))
                            break;
                        //7.不在可見區(qū)域中的數(shù)據(jù)不會只文字
                        if (!mViewPortHandler.isInBoundsY(buffer.buffer[j + 1])
                                || !mViewPortHandler.isInBoundsLeft(x))
                            continue;
                        BarEntry entry = dataSet.getEntryForIndex(j / 4);
                        //8.獲取繪制的文字原始值纵潦,如果設(shè)置了valueFormatter,就會格式化一下這個原始值
                        float val = entry.getY();

                        if (dataSet.isDrawValuesEnabled()) {
                            //9.開始繪制文字
                            drawValue(c, dataSet.getValueFormatter(), val, entry, i, 
                                    x,//繪制文字的x中心點(diǎn)
                                    val >= 0 ? (buffer.buffer[j + 1] + posOffset) : (buffer.buffer[j + 3] + negOffset),//文字的top點(diǎn)垃环,就是bar的top加上文字的高度
                                    dataSet.getValueTextColor()
                            );
                        }
                    }

drawValue方法如下邀层,這里就不講解了:

 public void drawValue(Canvas c, IValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) {
        mValuePaint.setColor(color);
        c.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler), x, y, mValuePaint);
    }

到這里,數(shù)據(jù)的繪制就分析完了遂庄,后面將會分析Highlight的處理寥院。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涛目,隨后出現(xiàn)的幾起案子秸谢,更是在濱河造成了極大的恐慌凛澎,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件估蹄,死亡現(xiàn)場離奇詭異塑煎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)臭蚁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門最铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垮兑,你說我怎么就攤上這事炭晒。” “怎么了甥角?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵网严,是天一觀的道長。 經(jīng)常有香客問我嗤无,道長震束,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任当犯,我火速辦了婚禮垢村,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚎卫。我一直安慰自己嘉栓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布拓诸。 她就那樣靜靜地躺著侵佃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奠支。 梳的紋絲不亂的頭發(fā)上馋辈,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音倍谜,去河邊找鬼迈螟。 笑死,一個胖子當(dāng)著我的面吹牛尔崔,可吹牛的內(nèi)容都是我干的答毫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼季春,長吁一口氣:“原來是場噩夢啊……” “哼洗搂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚕脏,失蹤者是張志新(化名)和其女友劉穎侦副,沒想到半個月后侦锯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驼鞭,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年尺碰,在試婚紗的時候發(fā)現(xiàn)自己被綠了挣棕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡亲桥,死狀恐怖洛心,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情题篷,我是刑警寧澤词身,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站番枚,受9級特大地震影響法严,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葫笼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一深啤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧路星,春花似錦溯街、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至友绝,卻和暖如春韩肝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背九榔。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工哀峻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哲泊。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓剩蟀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親切威。 傳聞我的和親對象是個殘疾皇子育特,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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

  • 假如有兩個線程A與線程B,線程A對集合C進(jìn)行迭代操作時,線程B改變了集合C的數(shù)據(jù)結(jié)構(gòu)缰冤,此時會報(bào)出Concurren...
    采風(fēng)JS閱讀 494評論 0 41
  • 現(xiàn)在互聯(lián)網(wǎng)談的最多的一話題犬缨,那就是微商!不管是PC端,還是移動網(wǎng)絡(luò)棉浸,微商處處可見怀薛。微商就像病毒一樣蔓延,滲透了互聯(lián)...
    幾葉到寒閱讀 807評論 0 0
  • “熵”熱力學(xué)中表征物質(zhì)狀態(tài)的參量之一迷郑,用符號S表示枝恋,其物理意義是體系混亂程度的度量。 一個孤立的系統(tǒng)嗡害,其內(nèi)部的混...
    蘇城姑姑Ivy閱讀 757評論 0 3