Android自定義控件:動態(tài)繪制圖表

之前寫過一個簡單的圖表繪制demo:Android圖表繪制产舞,但是實(shí)際應(yīng)用中只是簡單的繪制數(shù)據(jù)并不能達(dá)到最好的效果易猫【咦常考慮到實(shí)際體驗(yàn)嘴办,在之前的demo基礎(chǔ)上增加動態(tài)繪制動畫以及圖表各項(xiàng)屬性設(shè)置。
下面看下的靜態(tài)效果圖

這里寫圖片描述

整個布局分為三個部分贯被,即上方的四個按鈕區(qū)域彤灶,下方的線條說明區(qū)域幌陕,以及我們本次需要開發(fā)的圖表區(qū)域搏熄。上部分的是四個自定義按鈕心例,代碼比較簡單鞋囊,此處不多做說明。

我們總體需要繪制2條折線和若干數(shù)量的柱狀圖瓜喇,首先是繪制折線歉糜,參考一篇博文的方法:http://blog.csdn.net/tianjian4592/article/details/47067161现恼,這篇博文通過PathMeasure和屬性動畫結(jié)合的方式繪制一個動態(tài)路徑特效叉袍,由于我們這里并非是要動態(tài)繪制圖表而非根據(jù)路徑動態(tài)繪制一個圓刽酱,所以針對原博文做了一些修改棵里。

首先是獲取整個折線的path,然后將這個path設(shè)置給一個PathMeasure對象典蝌。

    // 獲取room temp繪線Path數(shù)據(jù)
    private void initRoomTempPath(float[] data) {
        mRoomTempPath.reset();
        Path path = new Path();
        float pointX;
        float pointY;
        mRoomTempPath.moveTo(Xpoint + xFirstPointOffset,
                getDataY(data[0], Ylabel));
        path.moveTo(Xpoint + xFirstPointOffset,
                getDataY(data[0], Ylabel));
        for (int i = 0; i < Xlabel.length; i++) {
            float startX = Xpoint + i * Xscale + xFirstPointOffset;
            if (i != 0) {
                pointX = Xpoint + (i - 1) * Xscale + xFirstPointOffset;
                pointY = getDataY(data[i - 1], Ylabel);
                path.lineTo(pointX, pointY);
            }
            if (i == Xlabel.length - 1) {
                pointX = startX;
                pointY = getDataY(data[i], Ylabel);
                path.lineTo(pointX, pointY);
            }
        }
        mRoomTempPathMeasure = new PathMeasure(path, false);
    }

然后創(chuàng)建一個ValueAnimator骏掀,在這個ValueAnimator中我們根據(jù)ValueAnimator計(jì)算出的當(dāng)前point值來動態(tài)的更新一個新的path截驮。

private void startRoomTempAnimation() {
        if (roomTempValueAnimator != null) {
            roomTempValueAnimator.cancel();
        }
        roomTempValueAnimator = ValueAnimator.ofFloat(0,
                mRoomTempPathMeasure.getLength());
        roomTempValueAnimator.setDuration(duration);
        // 減速插值器
        roomTempValueAnimator.setInterpolator(new DecelerateInterpolator());
        roomTempValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (Float) animation.getAnimatedValue();
                // 獲取當(dāng)前點(diǎn)坐標(biāo)封裝到mCurrentPosition
                mRoomTempPathMeasure.getPosTan(value, mCurrentPosition, null);
                mRoomTempPath.lineTo(mCurrentPosition[0], mCurrentPosition[1]);
                postInvalidate();
            }
        });
        roomTempValueAnimator.start();
    }

最后再onDraw里面繪制path葵袭。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);

        drawGreyXLine(canvas, linePaint);

        drawXLine(canvas, linePaint);

        drawUnit(canvas);

        canvas.drawPath(mRoomTempPath, roomTempPaint);

        if (roomTempDataArray.length > 1) {
            drawData(canvas, roomTempDataArray, roomTempLineColor);
        }
    }

然后我們看到了如下的效果

這里寫圖片描述

看起來好像一切正常,似乎達(dá)到了我們需要的效果窒所,但是將duration的值設(shè)置小一些之后墩新,整個路徑繪制都出錯了。

這里寫圖片描述

具體原因是什么呢绵疲,其實(shí)也很簡單盔憨,當(dāng)duration值設(shè)置的比較大的時候,ValueAnimator中取到的點(diǎn)坐標(biāo)自然比較密集婿奔,這樣mRoomTempPath的路徑值也比較平均萍摊,繪制起來自然看起來比較完善如叼。然而當(dāng)duration值設(shè)置的比較小的時候笼恰,ValueAnimator取到點(diǎn)坐標(biāo)跨度比較大,如果是一條直線還沒什么問題逼龟,但是一旦路徑是折線腺律,自然就出現(xiàn)問題了辽俗,因?yàn)槲覀兊穆窂蕉际莾牲c(diǎn)兩點(diǎn)之間lineTo連接的崖飘。

由于上面的這種方法存在很大的問題,不得不想一個新的方法吊圾,一番查閱之后项乒,發(fā)現(xiàn)了一種新的思路梁沧,通過DashPathEffect來實(shí)現(xiàn),具體的思路也比較簡單栓辜,可參考原博主:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0907/3429.html垛孔,稍微做了一些修整之后周荐,我們的代碼修改如下。

    // 獲取room temp繪線Path數(shù)據(jù)
    private void initRoomTempPath(float[] data) {
        mRoomTempPath.reset();
        //Path path = new Path();
        float pointX;
        float pointY;
        // 橫向
        mRoomTempPath.moveTo(Xpoint + xFirstPointOffset,
                getDataY(data[0], Ylabel));
        mRoomTempPath.moveTo(Xpoint + xFirstPointOffset, getDataY(data[0], Ylabel));
        for (int i = 0; i < Xlabel.length; i++) {
            float startX = Xpoint + i * Xscale + xFirstPointOffset;
            if (i != 0) {
                pointX = Xpoint + (i - 1) * Xscale + xFirstPointOffset;
                pointY = getDataY(data[i - 1], Ylabel);
                mRoomTempPath.lineTo(pointX, pointY);
            }
            if (i == Xlabel.length - 1) {
                pointX = startX;
                pointY = getDataY(data[i], Ylabel);
                mRoomTempPath.lineTo(pointX, pointY);
            }
        }
        mRoomTempPathMeasure = new PathMeasure(mRoomTempPath, false);
    }
    
    private void startAnimation() {
        if (mValueAnimator != null) {
            mValueAnimator.cancel();
        }
        final float targetTempLength = mTargetTempPathMeasure.getLength();
        final float roomTempLength = mRoomTempPathMeasure.getLength();
        mValueAnimator = ValueAnimator.ofFloat(1, 0);
        mValueAnimator.setDuration(duration);
        // 減速插值器
        mValueAnimator.setInterpolator(new DecelerateInterpolator());
        mValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = (Float) animation.getAnimatedValue();
                //更新mtargetTempEffect
                mtargetTempEffect = new DashPathEffect(new float[]{targetTempLength,
                        targetTempLength}, fraction * targetTempLength);
                targetTempPaint.setPathEffect(mtargetTempEffect);
                //更新mRoomTempEffect
                mRoomTempEffect = new DashPathEffect(new float[]{roomTempLength, roomTempLength},
                        fraction * roomTempLength);
                roomTempPaint.setPathEffect(mRoomTempEffect);
                //更新rect繪制fraction進(jìn)度
                mRectFration = 1 - fraction;//fraction是1->0 我們需要的柱形圖繪制比例是0->1
                postInvalidate();
            }
        });
        mValueAnimator.start();
    }

     @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);

        drawGreyXLine(canvas, linePaint);

        drawUnit(canvas);

        if (powerOnTimeDataArray.length > 1) {
            drawRect(canvas, powerOnTimeDataArray, powerTimeLineColor);
        }

        canvas.drawPath(mRoomTempPath, roomTempPaint);

        if (roomTempDataArray.length > 1) {
            drawData(canvas, roomTempDataArray, roomTempLineColor);
        }

        canvas.drawPath(mTargetTempPath, targetTempPaint);

        if (targetTempDataArray.length > 1) {
            drawData(canvas, targetTempDataArray, targetTempLineColor);
        }

        drawXLine(canvas, linePaint);
    }

最后很完美的實(shí)現(xiàn)了我們想要的折線動態(tài)繪制效果仆嗦。

接下來是柱形圖的動態(tài)繪制瘩扼,一樣根據(jù)valueAnimator中onAnimationUpdate的fraction來動態(tài)的更新每個rect的top值并繪制就可以了:

    // 繪制矩形圖
    private void drawRect(Canvas canvas, float[] data, int dataColor) {
        Paint p = new Paint();
        float left;
        float top;
        float right;
        float bottom;
        float stopY = getDataY(Float.parseFloat(Ylabel[Ylabel.length - 1]),
                Ylabel);// 灰色線Y軸位置
        float rectYScale = (Ypoint - stopY) / 100;

        p.setAntiAlias(true);
        p.setStrokeWidth(dataStrokeWidth);
        p.setColor(dataColor);

        // 橫向
        for (int i = 0; i < Xlabel.length; i++) {
            // 繪制柱形圖
            if (i != 0) {
                left = Xpoint + (i - 1) * Xscale + xFirstPointOffset + Xscale
                        / 6;
                top = Ypoint - data[i - 1] * rectYScale + lineStrokeWidth;//要繪制的rect最終top值
                //起點(diǎn)top + (起點(diǎn)top - 終點(diǎn)top) * mRectFration
                rectCurrentTops[i] = Ypoint - (Ypoint - top) * mRectFration;//根據(jù)fraction動態(tài)更新top值
                right = left + Xscale * 4 / 6;
                bottom = Ypoint;
                canvas.drawRect(left, rectCurrentTops[i], right, bottom, p);//每次valueAnimator更新時重繪最新top值
            }
        }
    }

值得注意的一點(diǎn)是,由于本次demo是根據(jù)數(shù)據(jù)長度動態(tài)繪制谆棺,所以需要在view onLayout之后才能設(shè)置數(shù)據(jù)給view并正確的繪制,因此在view暴露一個接口給Activity碍岔,Activity實(shí)現(xiàn)這個接口蔼啦,在View onLayout之后設(shè)置數(shù)據(jù)仰猖。

    public interface OnViewLayoutListener {
        public void onLayoutSuccess();
    }

    public void setOnViewLayoutListener(
            OnViewLayoutListener onViewLayoutListener) {
        this.onViewLayoutListener = onViewLayoutListener;
    }

    private OnViewLayoutListener onViewLayoutListener;

根據(jù)上面的方法將整個demo完成后饥侵,效果如下。


這里寫圖片描述

完整demo下載鏈接:https://github.com/Horrarndoo/animChartView.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子藕赞,更是在濱河造成了極大的恐慌斧蜕,老刑警劉巖砚偶,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件染坯,死亡現(xiàn)場離奇詭異,居然都是意外死亡掀宋,警方通過查閱死者的電腦和手機(jī)劲妙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門镣奋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀愧,“玉大人,你說我怎么就攤上這事哈垢】覆Γ” “怎么了鬼癣?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵待秃,是天一觀的道長。 經(jīng)常有香客問我枉氮,道長,這世上最難降的妖魔是什么聊替? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任春叫,我火速辦了婚禮泣港,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呛每。我一直安慰自己坡氯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布手形。 她就那樣靜靜地躺著叁幢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳞骤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天篙梢,我揣著相機(jī)與錄音渤滞,去河邊找鬼榴嗅。 笑死,一個胖子當(dāng)著我的面吹牛绪励,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播停做,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蛉腌,長吁一口氣:“原來是場噩夢啊……” “哼只厘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜀变,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤库北,失蹤者是張志新(化名)和其女友劉穎们陆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杂腰,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喂很,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年少辣,在試婚紗的時候發(fā)現(xiàn)自己被綠了漓帅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痴怨。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浪藻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弓乙,到底是詐尸還是另有隱情,我是刑警寧澤暇韧,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布懈玻,位于F島的核電站,受9級特大地震影響艺栈,放射性物質(zhì)發(fā)生泄漏湾盒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一毅人、第九天 我趴在偏房一處隱蔽的房頂上張望尖殃。 院中可真熱鬧,春花似錦送丰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春茅茂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背令杈。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工碴倾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掉丽,地道東北人捶障。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓项炼,卻偏偏與公主長得像示绊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子面褐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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