Android 進階自定義View(4)圖表統(tǒng)計LineChartView曲線圖的實現

接著上篇,今天介紹一下曲線圖 / 折線圖的實現方法可训,先上效果圖:


image.png

曲線圖很簡單了昌妹,坐標軸跟刻度線跟上篇柱狀圖的繪制一樣一樣滴,繪制曲線圖握截,關鍵就是確定好Y軸的每個點飞崖,然后用繪制曲線的方法,把點連起來就OK了谨胞。

(1)確定數據在坐標軸上對應的每個點

 /**
     * 根據傳入的數據固歪,確定繪制的點
     *
     * @return
     */
    private Point[] initPoint() {
        Point[] points = new Point[mDatas.size()];
        for (int i = 0; i < mDatas.size(); i++) {
            Integer ybean = mDatas.get(i);
            int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
            int startx = startX + xAxisSpace * i;
            points[i] = new Point(startx, drawHeight);
        }
        Log.e("TAG", "startX=" + startX + "---startY=" + startY);
        return points;
    }

(2)將點連接起來,這里使用cubicTo繪制貝塞爾曲線胯努。


image.png

    /**
     * 繪制曲線-曲線圖
     *
     * @param canvas
     */
    private void drawScrollLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            int wt = (startp.x + endp.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startp.y;
            p3.x = wt;
            p4.y = endp.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startp.x, startp.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
            canvas.drawPath(path, mPaint);
        }
    }


 /**
     * 繪制直線-折線圖
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint);
        }
    }

完整代碼:

package com.example.jojo.learn.customview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.example.jojo.learn.R;
import com.example.jojo.learn.utils.DP2PX;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by JoJo on 2018/8/3.
 * wechat:18510829974
 * description: 曲線圖/折線圖
 */

public class LineChartView extends View {
    private Context mContext;
    //繪制坐標軸的畫筆
    private Paint mAxisPaint;
    //繪制曲線的畫筆
    private Paint mPaint;
    //繪制X軸上方的畫筆
    private Paint mXAxisLinePaint;
    private Paint mPaintText;
    //向上的曲線圖的繪制起點(px)
    private int startX;
    private int startY;
    //向下的曲線圖的繪制起點(px)
    private int downStartX;
    private int downStartY;
    //上方Y軸每單位刻度所占的像素值
    private float YAxisUpUnitValue;
    //下方Y軸每單位刻度所占的像素值
    private float YAxisDownUnitValue;
    //根據具體傳入的數據牢裳,在坐標軸上繪制點
    private Point[] mPoints;
    //傳入的數據,決定繪制的縱坐標值
    private List<Integer> mDatas = new ArrayList<>();
    //Y軸刻度集合
    private List<Integer> mYAxisList = new ArrayList<>();
    //X軸刻度集合
    private List<String> mXAxisList = new ArrayList<>();
    //X軸的繪制距離
    private int mXAxisMaxValue;
    //Y軸的繪制距離
    private int mYAxisMaxValue;
    //Y軸刻度間距(px)
    private int yAxisSpace = 120;
    //X軸刻度間距(px)
    private int xAxisSpace = 200;
    //Y軸刻度線寬度
    private int mKeduWidth = 20;
    private float keduTextSize = 20;
    //刻度值距離坐標的padding距離
    private int textPadinng = 10;
    //Y軸遞增的實際值
    private int yIncreaseValue;
    //true:繪制曲線 false:折線
    private boolean isCurve = true;
    private Rect mYMaxTextRect;
    private Rect mXMaxTextRect;
    private int mMaxTextHeight;
    private int mMaxTextWidth;

    public LineChartView(Context context) {
        this(context, null);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initData();
        initView();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = (mYAxisList.size() - 1) * yAxisSpace + mMaxTextHeight * 2 + textPadinng * 2;
        }
        if (widthMode == MeasureSpec.AT_MOST) {

            widthSize = startX + (mDatas.size() - 1) * xAxisSpace + mMaxTextWidth;
        }
        //保存測量結果
        setMeasuredDimension(widthSize, heightSize);
    }

    private void initView() {
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_efaf34));
        mPaint.setStrokeWidth(2);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        //繪制X,Y軸坐標的畫筆
        mAxisPaint = new Paint();
        mAxisPaint.setColor(ContextCompat.getColor(mContext, R.color.color_274782));
        mAxisPaint.setStrokeWidth(2);
        mAxisPaint.setAntiAlias(true);
        mAxisPaint.setStyle(Paint.Style.STROKE);
        //繪制坐標軸上方的橫線的畫筆
        mXAxisLinePaint = new Paint();
        mXAxisLinePaint.setColor(ContextCompat.getColor(mContext, R.color.color_274782));
        mXAxisLinePaint.setStrokeWidth(1);
        mXAxisLinePaint.setAntiAlias(true);
        mXAxisLinePaint.setStyle(Paint.Style.STROKE);

        //繪制刻度值文字的畫筆
        mPaintText = new Paint();
        mPaintText.setTextSize(keduTextSize);
        mPaintText.setColor(ContextCompat.getColor(mContext, R.color.color_a9c6d6));
        mPaintText.setAntiAlias(true);
        mPaintText.setStrokeWidth(1);


        mYMaxTextRect = new Rect();
        mXMaxTextRect = new Rect();
        mPaintText.getTextBounds(Integer.toString(mYAxisList.get(mYAxisList.size() - 1)), 0, Integer.toString(mYAxisList.get(mYAxisList.size() - 1)).length(), mYMaxTextRect);
        mPaintText.getTextBounds(mXAxisList.get(mXAxisList.size() - 1), 0, mXAxisList.get(mXAxisList.size() - 1).length(), mXMaxTextRect);
        //繪制的刻度文字的最大值所占的寬高
        mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width();
        mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height();


        //指定繪制的起始位置
        startX = mMaxTextWidth + textPadinng + mKeduWidth;
        //坐標原點Y的位置(+1的原因:X軸畫筆的寬度為2 ; +DP2PX.dip2px(mContext, 5)原因:為刻度文字所占的超出的高度 )——>解決曲線畫到最大刻度值時叶沛,顯示高度不夠蒲讯,曲線顯示扁扁的問題
        startY = yAxisSpace * (mYAxisList.size() - 1) + mMaxTextHeight;

        if (mYAxisList.size() >= 2) {
            yIncreaseValue = mYAxisList.get(1) - mYAxisList.get(0);
        }
        //X軸繪制距離
        mXAxisMaxValue = (mDatas.size() - 1) * xAxisSpace;
        //Y軸繪制距離
        mYAxisMaxValue = (mYAxisList.size() - 1) * yAxisSpace;

        //坐標起始點Y軸高度=(startY+mKeduWidth)  下方文字所占高度= DP2PX.dip2px(mContext, keduTextSize)
        int viewHeight = startY + 2 * mKeduWidth + DP2PX.dip2px(mContext, keduTextSize);
        //viewHeight=121
        Log.e("TAG", "viewHeight=" + viewHeight);
    }

    /**
     * 根據傳入的數據,確定繪制的點
     *
     * @return
     */
    private Point[] initPoint() {
        Point[] points = new Point[mDatas.size()];
        for (int i = 0; i < mDatas.size(); i++) {
            Integer ybean = mDatas.get(i);
            int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
            int startx = startX + xAxisSpace * i;
            points[i] = new Point(startx, drawHeight);
        }
        Log.e("TAG", "startX=" + startX + "---startY=" + startY);
        return points;
    }

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

        mPoints = initPoint();

        for (int i = 0; i < mYAxisList.size(); i++) {
            //Y軸方向遞增的高度
            int yAxisHeight = startY - yAxisSpace * i;
            //繪制X軸和上方橫線
            canvas.drawLine(startX - mKeduWidth, yAxisHeight, startX + (mDatas.size() - 1) * xAxisSpace, yAxisHeight, mXAxisLinePaint);
            //繪制左邊Y軸刻度線
//                canvas.drawLine(startX, yAxisHeight, startX - mKeduWidth, yAxisHeight, mAxisPaint);
            //繪制文字時,Y軸方向遞增的高度
            int yTextHeight = startY - yAxisSpace * i;
            //繪制Y軸刻度旁邊的刻度文字值,10為刻度線與文字的間距
            mPaintText.setTextAlign(Paint.Align.RIGHT);
            canvas.drawText(mYAxisList.get(i) + "", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText);
        }
        //繪制Y軸
        canvas.drawLine(startX, startY, startX, startY - mYAxisMaxValue, mAxisPaint);

        //繪制X軸下面顯示的文字
        for (int i = 0; i < mXAxisList.size(); i++) {
            int xTextWidth = startX + xAxisSpace * i - mKeduWidth;
            //設置從起點位置的左邊對齊繪制文字
            mPaintText.setTextAlign(Paint.Align.LEFT);
            Rect rect = new Rect();
            mPaintText.getTextBounds(mXAxisList.get(i), 0, mXAxisList.get(i).length(), rect);
            canvas.drawText(mXAxisList.get(i), startX - rect.width() / 2 + xAxisSpace * i, startY + rect.height() + textPadinng, mPaintText);
        }
        //連接所有的數據點,畫曲線

        if (isCurve) {
            //畫曲線
            drawScrollLine(canvas);
        } else {
            //畫折線
            drawLine(canvas);
        }
    }

    /**
     * 繪制曲線-曲線圖
     *
     * @param canvas
     */
    private void drawScrollLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            int wt = (startp.x + endp.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startp.y;
            p3.x = wt;
            p4.y = endp.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startp.x, startp.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
            canvas.drawPath(path, mPaint);
        }
    }

    /**
     * 繪制直線-折線圖
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint);
        }
    }

    private void initData() {
        //外界傳入的數據灰署,即為繪制曲線的每個點
        mDatas.add(0);
        mDatas.add(10);
        mDatas.add(5);
        mDatas.add(20);
        mDatas.add(15);

        int[] mYAxisData = new int[]{0, 10, 20, 30, 40};
        for (int i = 0; i < mYAxisData.length; i++) {
            mYAxisList.add(mYAxisData[i]);
        }

        //X軸數據
        mXAxisList.add("01月");
        mXAxisList.add("02月");
        mXAxisList.add("03月");
        mXAxisList.add("04月");
        mXAxisList.add("05月");
    }

    /**
     * 傳入數據判帮,重新繪制圖表
     *
     * @param datas
     * @param yAxisData
     */
    public void updateData(List<Integer> datas, List<String> xAxisData, List<Integer> yAxisData) {
        this.mDatas = datas;
        this.mXAxisList = xAxisData;
        this.mYAxisList = yAxisData;
        initView();
        postInvalidate();
    }
}

參考學習:
Android中moveTo、lineTo氓侧、quadTo脊另、cubicTo、arcTo詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末约巷,一起剝皮案震驚了整個濱河市偎痛,隨后出現的幾起案子,更是在濱河造成了極大的恐慌独郎,老刑警劉巖踩麦,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異氓癌,居然都是意外死亡谓谦,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門贪婉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來反粥,“玉大人,你說我怎么就攤上這事〔哦伲” “怎么了莫湘?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長郑气。 經常有香客問我幅垮,道長,這世上最難降的妖魔是什么尾组? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任忙芒,我火速辦了婚禮,結果婚禮上讳侨,老公的妹妹穿的比我還像新娘呵萨。我一直安慰自己,他們只是感情好爷耀,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布甘桑。 她就那樣靜靜地躺著拍皮,像睡著了一般歹叮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铆帽,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天咆耿,我揣著相機與錄音,去河邊找鬼爹橱。 笑死萨螺,一個胖子當著我的面吹牛,可吹牛的內容都是我干的愧驱。 我是一名探鬼主播慰技,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼组砚!你這毒婦竟也來了吻商?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糟红,失蹤者是張志新(化名)和其女友劉穎艾帐,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體盆偿,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡柒爸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了事扭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捎稚。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出今野,到底是詐尸還是另有隱情晰奖,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布腥泥,位于F島的核電站匾南,受9級特大地震影響,放射性物質發(fā)生泄漏蛔外。R本人自食惡果不足惜蛆楞,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夹厌。 院中可真熱鬧豹爹,春花似錦、人聲如沸矛纹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽或南。三九已至孩等,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間采够,已是汗流浹背肄方。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蹬癌,地道東北人权她。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像逝薪,于是被迫代替她去往敵國和親隅要。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容

  • 【Android 自定義View之繪圖】 基礎圖形的繪制 一董济、Paint與Canvas 繪圖需要兩個工具步清,筆和紙。...
    Rtia閱讀 11,676評論 5 34
  • 系列文章之 Android中自定義View(一)系列文章之 Android中自定義View(二)系列文章之 And...
    YoungerDev閱讀 4,405評論 3 11
  • 上一篇內容自定義View(Canvas)http://www.reibang.com/p/d25fb10ad34e...
    fcott閱讀 687評論 0 1
  • 自定義控件教程: 1感局,http://blog.csdn.net/aigestudio/article/detail...
    CoderGC閱讀 1,664評論 1 11
  • 我總是控制不住去想我不要的一面尼啡,讓自己鉆進死循環(huán),讓自己情緒不好询微,覺得生活得不幸福崖瞭。 我如果老是想自己不要的,我怎...
    幸福魔法師閱讀 177評論 0 0