android之自定義View和ViewGroup(六)(代碼篇,實現簡單的走勢圖,柱狀圖同理)

項目需要走勢圖,什麼柱狀圖、走勢圖,網上都有一堆現成的框架,但是我項目中只需要走勢圖,我用第三方框架簡直是累贅,畢竟佔大小,而且走勢圖也簡單,所以就自己寫了,好久沒寫博客了,就記下吧,供大家參考下,其實很簡單,大佬勿噴。先不廢話,看效果圖吧:
這裏寫圖片描述
記錄了七天的走勢,原點的x座標不是0,因爲我們不是用來做座標軸,而是記錄數據,原點的座標根據實際需求情況而定的。

好了,廢話說完了,開始動手吧,動手之前我們得先想想怎麼實現,思路是怎樣的。

首先,我們需要知道我們需要畫什麼。
1.我們需要畫x,y軸;
2.我們需要畫x,y軸的刻度和刻度值;
3.我們需要畫y軸上的每個刻度的橫線;
4.我們需要畫數據的點;
5.數據的點需要連線;
大致就是這些,還有些文字的大小啊,顏色啊等等,都可以自己設置;

然後,我們需要知道怎麼畫。
大家都知道在視圖座標系中,一個view,原點是它的左上角,所以我們需要實際的原點,真實的原點,即走勢圖中x,y軸的交接點。所以我們得先確定實際原點,假設實際原點的座標是(xo,yo),averageX代表x軸每個刻度間的距離,那圖片中x軸的11.2所在的座標就是相對於原點的(averageX,0),所以實際座標就是(xo+averageX,yo+0)。那麼y軸的1000所在的座標就是相對於原點的(0,-averageY),所以實際座標是(0+xo,-averageY+yo)注意,android中,x軸向右爲正,y軸向下爲正,所以1000所在的座標是負的。哎,語文沒學好,可能表達有點不清楚?總之呢,就是爲了確定每個點的算法才這樣計算的。

首先,我們自定義View繼承自View,然後相關屬性:

private Context context;
    //x,y軸的畫筆,刻度文字的畫筆,數據點的畫筆,數據連線的畫筆,y軸刻度橫線的畫筆,數據點文字的畫筆
    private Paint xyPaint, textPaint, pointPaint, pointslinePaint, yLinePaint, pointTextPaint;
    private int padding;  //內部距離,設置個默認的padding值
    private int width, height;  //控件實際高度和寬度
    private float xSize, ySize;//x和y軸的長度(因爲控件留了內部距離,不然x,y軸就貼着控件邊緣導致看不清,有內部距離,所以長度和控件的寬高不一樣了)
    private int defaultWidth;  //默認寬度
    private int defaultHeight;  //默認高度
    private int xOrigin, yOrigin;  //相對原點的座標
    private String[] xPoints;  //x軸顯示的座標
    private String[] yPoints;  //y軸顯示的座標
    private List<Float> xPointList, yPointList; //數據點的x座標集合,y座標集合;

然後初始化畫筆,默認高寬等等:

private void init() {
        xPointList = new ArrayList<>();
        yPointList = new ArrayList<>();
        defaultWidth = (DisplayUtils.width > 480 ? 480 : DisplayUtils.width);
        defaultHeight = 320;
        padding = DisplayUtils.dip2px(context, 35);

        xyPaint = new Paint();
        xyPaint.setStyle(Paint.Style.FILL);
        xyPaint.setAntiAlias(true);

        textPaint = new Paint();
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(DisplayUtils.dip2px(context, 10));

        pointPaint = new Paint();
        pointPaint.setStyle(Paint.Style.FILL);
        pointPaint.setAntiAlias(true);

        pointslinePaint = new Paint();
        pointslinePaint.setStyle(Paint.Style.FILL);
        pointslinePaint.setAntiAlias(true);
        pointslinePaint.setStrokeWidth(6);

        yLinePaint = new Paint();
        yLinePaint.setStyle(Paint.Style.FILL);
        yLinePaint.setAntiAlias(true);

        pointTextPaint = new Paint();
        pointTextPaint.setStyle(Paint.Style.FILL);
        pointTextPaint.setAntiAlias(true);
        pointTextPaint.setTextSize(DisplayUtils.dip2px(context, 6));
    }

爲了簡單,我就不再attr中設置屬性了,直接在代碼裏面設置顏色值之類的,大家知道思路就好。

設置一個public方法供外部調用來設置數據點的座標,我們用Map來保存座標:

public void setData(String[] xPoints, String[] yPoints, Map<Float, Float> showPointsMap) {
        this.xPoints = xPoints;
        this.yPoints = yPoints;
        for (float x : showPointsMap.keySet()) {
            xPointList.add(x);
            yPointList.add(showPointsMap.get(x));
        }
    }

設置一個public方法供外部調用,來設置畫筆的顏色:

//x,y軸和刻度文字的畫筆,數據點的畫筆,數據連線的畫筆,y軸刻度橫線的畫筆,數據點文字的畫筆
    public void setColor(int xyColor, int pointColor, int lineColor, int yLineColor, int pointTextColor) {
        xyPaint.setColor(xyColor);
        textPaint.setColor(xyColor);
        pointPaint.setColor(pointColor);
        pointslinePaint.setColor(lineColor);
        yLinePaint.setColor(yLineColor);
        pointTextPaint.setColor(pointTextColor);
    }

然後接下來開始自定義的方法,先onMeasure()方法確定大小,如果測量模式不是EXACYLY,那麼就使用默認的高寬值:

if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = defaultWidth;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = defaultHeight;
        }
        ySize = height - padding - 60;  //y軸總長度,y軸的paddingTop我只留60px,paddingButtom留padding大小,可以自己定義
        xSize = width - padding * 2;  //x軸總長度,x方向的左右padding都設置爲padding大小
        xOrigin = padding;  //實際原點的x座標
        yOrigin = height - padding;  //實際原點的y座標
        setMeasuredDimension(width, height);

然後重要的onDraw()方法:

float averageHeight = ySize / 5;  //y軸每個刻度之間的長度,設置6個刻度
        float averageWidth = xSize / 6;  //x軸每個刻度之間的長度,設置7個刻度
        float averagePx = ySize / 5000;
        canvas.drawLine(xOrigin, yOrigin, xOrigin + xSize + 15, yOrigin, xyPaint);  //畫x軸,多畫了15px讓x軸右邊可以出來,不然頂部正好是最後一個刻度線
        canvas.drawLine(xOrigin, yOrigin, xOrigin, yOrigin - ySize - 15, xyPaint);  //畫y軸,多畫了15px讓y軸上邊可以出來,不然頂部正好是最後一個刻度線
        for (int i = 0; i < yPoints.length; i++) {  //畫y軸上的刻度,y軸刻度的橫線,y軸刻度文字
            canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin - 15, yOrigin - averageHeight * (i + 1), xyPaint);
            canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin + xSize + 15, yOrigin - averageHeight * (i + 1), yLinePaint);
            canvas.drawText(yPoints[i], 10, yOrigin - averageHeight * (i + 1), textPaint);
        }
        for (int i = 0; i < xPoints.length; i++) {  //畫x軸上的刻度和文字
            canvas.drawLine(xOrigin + averageWidth * i, yOrigin, xOrigin + averageWidth * i, yOrigin + 15, xyPaint);
            canvas.drawText(xPoints[i], xOrigin + averageWidth * (i), height - 30, textPaint);
        }
        for (int i = 0; i < xPointList.size(); i++) {  //畫走勢連線
            if (i < xPointList.size() - 1) {
                canvas.drawLine(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i),
                        xOrigin + averageWidth * (i + 1), yOrigin - averagePx * yPointList.get(i + 1), pointslinePaint);
            }
        }
        for (int i = 0; i < xPointList.size(); i++) {  //畫數據點,先畫線是爲了讓點可以蓋住線
            canvas.drawCircle(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i), 10, pointPaint);  //數據點
            canvas.drawText(yPointList.get(i) + "", xOrigin + averageWidth * (i) + 20, yOrigin - averagePx * yPointList.get(i), pointTextPaint); //數據文字
        }

思路其實很簡單,也沒任何難點,就是畫點,畫線,畫刻度。需要說的都在代碼中寫明瞭,就不囉嗦了,主要是知道思路,剩下的就簡單了。

其實還有很多可以優化的地方,比如自定義屬性顏色值,字體大小,線條粗細,數據點的大小,刻度值的位置等等,都可以自定義,這些比較簡單我就不囉嗦了,有興趣的朋友可以去試試。對於柱狀圖,其實一樣的原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章