自定義 折線圖

自定義一個簡單的折線圖,足夠展示萬條數據,(千條以上數據在1070的屏幕寬度上 只會是一片茫茫,顏色會擠到一塊兒。。。。)


簡單說一下實現思路,

1、獲取當前View在當前屏幕中的位置(Android中屏幕的左上角爲整個屏幕座標的原點往右 、往下遞增,我們自己的座標系是符合數學邏輯的二維座標系,原點在整個折線圖的左下角)

2、首先計算座標點(比如,我們自己的座標系的Y軸長度爲畫布的高度canasWidth減去50[我們默認距頂50],我們把獲取到  的高度數值定爲原點的Y軸座標,X的走向與Android原生一致,  所以想讓原點距離屏幕左邊多遠 ,就設置所多大的值爲原點在屏幕的X座標,記得留出足夠的空間,需要去繪製Y軸刻度)

3、然後先繪製出X軸、Y軸,並根據數據的長度計算Y、X軸刻度(有十條數據,X軸長度除以10就是X周刻度,Y軸同理,根據       所有數據裏的最大數值計算Y軸刻度)

4、別忘了繪製刻度數據,位置的計算在上一步中有相同之處,可以獲取上邊的值使用

5、繪製折線與虛線(X軸刻度)使用drawLines去繪製,會節省資源,虛線也要用這個去繪製,若使用(drawLine)繪製,會很慢

 

以下爲自定義View核心類,在java代碼中添加,目前沒有做在佈局中直接使用,另:提供demo供大家參考(點擊此處),若有問題歡迎添加QQ  :1017726485.共同討論




import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.ColorRes;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;


import com.jjf.customchartline.R;
import com.jjf.customchartline.utils.Arith;
import com.jjf.customchartline.been.chartline.ChartResultData;
import com.jjf.customchartline.been.chartline.LineBeen;
import com.jjf.customchartline.been.chartline.LinePain;

import java.math.BigDecimal;
import java.util.ArrayList;

/**
 * @author: jjf
 * @date: 2018/4/19
 * @describe:
 */
public abstract class MYChartLine extends View {
    private float canasWidth;                 //畫布寬
    private float canasHeight;
    private float XPoint;                  //定義原點
    private float YPoint;
    private double XScale;                  //刻度間距
    private int YScale;
    private int topDim = 30;//距離頂部
    private int surplusHeight = 50;//Y軸超出最大刻度距離(計算Y軸時總長減去超出距離)
    private int Ylenth = 6;//Y軸刻度數量
    private int surplusWhith = 50;//X軸超出最大刻度距離
    private String TAG = "MYChartLine";
    private Context context;
    //容器寬高
    private int viewGroupWidth;
    private int viewGroupHeight;
    //虛線間隙
    int dottedspace = 3;//虛線間隙
    int dottedLength = 10;//虛線長度

    //座標軸寬度
    private int axisWidth = 1;
    //座標軸顏色
    private @ColorRes
    int axisColor = R.color.zuobiao;//#a6a6a6
    Resources resources;

    int color;//
    int colorBlue;

    public int getYlenth() {
        return Ylenth;
    }

    public void setYlenth(int ylenth) {
        Ylenth = ylenth;
    }

    //座標軸線的寬度
    public void setAxisWidth(int axisWidth) {
        this.axisWidth = axisWidth;
    }

    //軸顏色
    public void setAxisColor(@ColorRes int axisColor) {
        this.axisColor = axisColor;
        color = context.getResources().getColor(axisColor);
    }

    boolean isRefresh = true;//  false   刷新數據   true   添加數據

    Activity activity;

    public MYChartLine(Context context, ViewGroup viewGroup) {
        super(context);
        this.context = context;
        if (context instanceof Activity) {
            activity = (Activity) context;
        }
        resources = context.getResources();
        //獲取屏幕寬(自適應屏幕寬度)
        WindowManager wm = ((Activity) context).getWindowManager();
        viewGroupWidth = wm.getDefaultDisplay().getWidth();
        // 取父控件高度(爲了在佈局中可以設置高度,不獲取屏幕)
        LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) viewGroup
                .getLayoutParams();
        viewGroupHeight = linearParams.height;

        //畫布寬高
        canasWidth = viewGroupWidth - 10;
        canasHeight = viewGroupHeight;
        //原點座標
        XPoint = canasWidth / 12.0F;
        YPoint = canasHeight - surplusHeight;

        color = context.getResources().getColor(axisColor);
        colorBlue = context.getResources().getColor(R.color.zhexian_blue);


        //初始化畫筆
        //標記線(隨手勢移動)
        paintYTag = new Paint();
        paintYTag.setColor(colorBlue);
        paintYTag.setStrokeWidth(2);
        paintYTag.setAntiAlias(true);
        paintYTag.setStyle(Paint.Style.STROKE);//設置畫直線格式
        paintYTag.setPathEffect(new DashPathEffect(new float[]{dottedLength, dottedspace}, 0));//畫虛線關鍵代碼
        //軸畫筆
        paintYX = new Paint();
        paintYX.setColor(color);
        paintYX.setTextSize(30);
        paintYX.setStrokeWidth(axisWidth);
        paintYX.setAntiAlias(true);
        //垂直虛線
        paintDottenY = new Paint();
        paintDottenY.setColor(color);
        paintDottenY.setTextSize(30);
        paintDottenY.setStrokeWidth(axisWidth);
        paintDottenY.setAntiAlias(true);
        paintDottenY.setStyle(Paint.Style.STROKE);//設置畫直線格式
        paintDottenY.setPathEffect(new DashPathEffect(new float[]{5, 3}, 1));//畫虛線關鍵代碼


        //X軸數據
        paintXText = new Paint();
        paintXText.setColor(color);
        paintXText.setTextSize(30);
        paintXText.setAntiAlias(true);

        YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y軸刻度間距
        effectiveLenthX = (int) (canasWidth - XPoint - surplusWhith);
    }

    /**
     * 桌布
     */
    private Canvas canvas;
    //Y軸
    Paint paintYX;
    //Y軸數據
    Paint paintYText;
    //X軸數據
    Paint paintXText;
    //垂直虛線
    Paint paintDottenY;

    //與Y軸平行的、可移動標記線
    Paint paintYTag;
    //標記線
    Path tagPath = new Path();

    //清除畫筆

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

        //標記線(隨手勢移動)

        canvas.drawPath(tagPath, paintYTag);
        //軸畫筆
        //Y軸
        canvas.drawLine(XPoint, topDim, XPoint, YPoint, paintYX);
        //X軸
        canvas.drawLine(XPoint, YPoint, canasWidth, YPoint, paintYX);
        //X軸數據

        YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y軸刻度間距
        this.onMDraw();

    }

    //畫筆集合  記錄每一條折線 //VALUE==折線與折線標註字體
    ArrayList<Paint> painLines = new ArrayList<>();

    //是否設置Y軸數據爲10、100、1000倍數
    private boolean isYDataToMultiple = true;
    private LinePain maxLenthlinePain;//折線圖最長的折線

    /**
     * 控制Y軸數據爲10、100、1000的倍數(只有Y軸當前數據大與10倍纔會抹去尾數  如 12 不會變成10    112會變成110,1234會變成1200,12345會變成12000)
     */
    public void setYDataToMultiple(boolean isYDataToMultiple) {
        this.isYDataToMultiple = isYDataToMultiple;
    }

    int xuLineNum = 0; //虛線個數、、X軸分割區數
    LinePain[] dataList;//所有條線的數據
    double maxValue = 0;

    /**
     * 添加折線
     * isRefresh true 刷新數據 全部重新繪製
     * false 添加數據  增加畫筆
     */
    public void addLine(LinePain... linePain) {

        System.out.println("開始---");
        //onDraw 方法會運行兩次,這裏控制一段代碼 只運行一次
        if (isRefresh) {
            chartResultData.setDatasY(chartYList);
            dataList = new LinePain[linePain.length];
        }
        try {
            //虛線個數、、X軸分割區數
            xuLineNum = 0;
            //所有Y軸數據中最大一條數據值--用最大值計算Y軸刻度平均值
            maxValue = 0;
            painLines.clear();
            //初始化、創建數據
            createData(linePain);
            //四捨五入求最大位數的整數
            //返回數據
            chartResultData.setDataX(maxLenthlinePain.getLineBeens().get(maxLenthlinePain.getLineBeens().size() - 1).getX() + "");

            //單位像素 代表的數據大小(Y軸)
            //Y軸實際單位長度(對應數據)
            double heightY = Arith.div(maxY - minY, maxValue, 10).doubleValue();

            //X軸刻度間距
            if (xuLineNum > 1) {
                XScale =   ( canasWidth - XPoint - surplusWhith)/(xuLineNum-1);
            } else {
                XScale = 1;
            }
            System.out.println("刻畫Y軸---");
            //畫 Y軸數據
            drawYData(linePain);
            System.out.println("刻畫Y軸結束---");
             //計算日期間隔(最多顯示7個日期,計算相鄰兩個日期中間間隔的單位個數)
            int inDex = xuLineNum / 6;
            System.out.println("計算虛線,折線開始---");

            //折線位置
            ArrayList<float[]> lineschart = new ArrayList<>();

            //計算虛線有多少截
            int dottedNum = (int) ((YPoint - topDim) / (dottedspace + dottedLength));


            //(每一條虛線的每一截都要計算出來)虛線位置
            float[] dottedLine = new float[xuLineNum * dottedNum * 4];
            //虛線個數--代表單條折線數據長度
            for (int j = 0; j < xuLineNum; j++) {
                int X = (int) (XPoint + j * XScale);  //X軸刻度位置
                //折線上數值 以及X軸刻度
                if (xuLineNum > 6) {
                    if (j % inDex == 0) {
                        //X軸座標
                        canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
                                (int) (XPoint + (j * XScale) - surplusWhith),
                                YPoint + 30, paintXText);
                    }
                } else {
                    //X軸座標
                    canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
                            (int) (XPoint + (j * XScale) - maxLenthlinePain.getLineBeens().get(j).getX().length() * 8),
                            YPoint + 30, paintXText);
                }

                //每條虛線的每段虛線節點
                double dottedPosition = 0;
                if (j < xuLineNum - 1) {
                    //計算每條虛線的每一節
                    for (int i = 0; i < dottedNum; i++) {
                        //Y軸平行虛線
                        dottedLine[j * dottedNum * 4 + i * 4] = (float) (X + XScale);
                        dottedLine[j * dottedNum * 4 + i * 4 + 1] = (float) (topDim + dottedPosition);
                        dottedLine[j * dottedNum * 4 + i * 4 + 2] = (float) (X + XScale);
                        dottedLine[j * dottedNum * 4 + i * 4 + 3] = (float) (topDim + dottedPosition + dottedLength);
                        dottedPosition += (dottedLength + dottedspace);
                    }
                }
                for (int c = 0; c < linePain.length; c++) {
                    if (lineschart.size() < c + 1) {//添加一條折線的容器(有幾條折線,會添加幾次)
                        lineschart.add(new float[xuLineNum * 4]);
                    }

                    //   折線Y點位置
                    double startY = 0;
                    //當前折線長度是否到達  j  腳標(折線長度不一定相等 對比是否達到最長折線)
                    if (linePain[c].getLineBeens().size() > j) {
                        // 折線Y點位置
                        startY = YPoint - Arith.mul(heightY + "", linePain[c].getLineBeens().get(j).getY() + "");

                        //折線上數值 以及X軸刻度
                        if (xuLineNum > 6) {
                            if (j % inDex == 0) {
                                //查看本折線是否展示連接點值,true顯示折線上的值
                                if (linePain[c].isShowPrompt()) {
                                    canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 10, (float) (startY - 20), painLines.get(c));
                                }
                            }
                        } else {
                            if (linePain[c].isShowPrompt()) {
                                //顯示線上的值
                                canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 15, (float) (startY - 20), painLines.get(c));
                            }
                        }
                        //折線(與虛線)在倒數第二次已經完成繪製----折線數據計算
                        if (j < linePain[c].getLineBeens().size() - 1) {
                            //實際高度
                            double z1 = Arith.mul(heightY + "", linePain[c].getLineBeens().get(j + 1).getY() + "");

                            lineschart.get(c)[j * 4] = X;
                            lineschart.get(c)[j * 4 + 1] = (float) startY;
                            lineschart.get(c)[j * 4 + 2] = (float) (XPoint + (j + 1) * XScale);
                            lineschart.get(c)[j * 4 + 3] = YPoint - ((float) z1);
                        }
                    }
                }
            }
            System.out.println("渲染虛線,折線開始---");
            //畫虛線
            canvas.drawLines(dottedLine, paintDottenY);
            //  折線
            for (int i = 0; i < painLines.size(); i++) {
                canvas.drawLines(lineschart.get(i), painLines.get(i));
            }
            System.out.println("渲染虛線,折線結束---" + System.currentTimeMillis());
            this.postInvalidate();
            isRefresh = false;
        } catch (Exception e) {
            Log.i(TAG, "---------" + e.toString());
            e.printStackTrace();
        }
    }

    private int lastX = 0;//記錄標記線的位置
    private int lastY = 0;
    private int startYLineX = 0;//Y軸的X座標
    private int effectiveLenthY = 0;//Y軸有效長度
    private int effectiveLenthX = 0;//X軸有效長度

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        //獲取到手指處的橫座標和縱座標
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                try {
                    getParent().requestDisallowInterceptTouchEvent(false);
                } catch (Exception e) {
                }
            case MotionEvent.ACTION_MOVE:
                System.out.println("effectiveLenthX=="+effectiveLenthX);
                if ((x > XPoint && x < effectiveLenthX + XPoint) && (y < YPoint && y > topDim)) {
                    lastX = x;
                    tagPath.reset();
                    tagPath.moveTo(lastX, topDim - 5);
                    tagPath.lineTo(lastX, YPoint);
//                    postInvalidate();
//
                    setToData(x);
                }
                break;
            case MotionEvent.ACTION_UP:
                try {
                    getParent().requestDisallowInterceptTouchEvent(true);
                } catch (Exception e) {
                }
                break;
        }

        return true;
    }

    //計算集合中最大值與最小值
    //最大值

    public double ArrayListMax(ArrayList<LineBeen> sampleList) {
        try {
            double maxDevation = 0.0;
            int totalCount = sampleList.size();
            if (totalCount >= 1) {
                double max = strToDouble(sampleList.get(0).getY());
                for (int i = 0; i < totalCount; i++) {
                    double temp = strToDouble(sampleList.get(i).getY());
                    if (temp > max) {
                        max = temp;
                    }
                }
                maxDevation = max;
            }
            return maxDevation;
        } catch (Exception ex) {
            throw ex;
        }
    }

    //調用本類的其他 需要實現本方法、在本方法去調用
    public abstract void onMDraw();

    private int minY = 0;
    private int maxY = 0;


    //監聽手勢 返回觸摸到位置的值
    private ChartResultData chartResultData = new ChartResultData();
    //所有折線的Y值  添加到 chartResultData 中
    ArrayList<String> chartYList = new ArrayList();

    /**
     * \
     * <p>
     * 返回手勢選中數據
     *
     * @return
     */
    public abstract ChartResultData setData(ChartResultData chartResultData);

    /**
     * 初始化數據
     * 創建、計算相關變量
     */
    private void createData(LinePain... linePain) throws IllegalAccessException {
        int x = 0;
        for (int i = 0; i < linePain.length; i++) {
            dataList[i] = linePain[i];
            Paint paint = new Paint();
            paint.setStrokeWidth(linePain[i].getLineWidth());
            paint.setAntiAlias(true);
            paint.setColor(resources.getColor(linePain[i].getColorId()));
            paint.setTextSize(linePain[i].getNumberPromptSize());

            if (linePain[i].getLineBeens() != null && linePain[i].getLineBeens().size() > 0) {
                //找出最多數據的折線
                if (xuLineNum < linePain[i].getLineBeens().size()) {
                    xuLineNum = linePain[i].getLineBeens().size();
                }
                //Y軸最大刻度值
                if (maxValue < ArrayListMax(linePain[i].getLineBeens())) {
                    maxValue = ArrayListMax(linePain[i].getLineBeens());
                }
                int size = linePain[i].getLineBeens().size();
                if (x < size) {//找出最長的一條折線(多條折線不一定一樣長短)
                    x = size;
                    maxLenthlinePain = linePain[i];
                }
                if (maxLenthlinePain == null) {
                    maxLenthlinePain = linePain[0];
                }
            } else {
                return;
            }
            painLines.add(paint);
        }
        if (maxValue > 1000) {
            maxValue = Arith.div(maxValue, (double) 1000, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 1000;
        } else if (maxValue > 100) {
            maxValue = Arith.div(maxValue, (double) 100, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 100;
        } else if (maxValue > 10) {
            maxValue = Arith.div(maxValue, (double) 10, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 10;
        } else if (maxValue < 5) {
            maxValue = 5;
        }
    }

    public double strToDouble(String str) {
        return Double.parseDouble(str);
    }

    /**
     * 刻畫Y軸數據
     */
    public void drawYData(LinePain... linePain) {
        //Y軸數據
        if (paintYText == null) {
            paintYText = new Paint();
        }
        paintYText.setColor(color);
        paintYText.setTextSize(30);
        paintYText.setAntiAlias(true);
        String strY;
        //Y軸一個刻度 等於的數據大小
        int v1 = (int) (new BigDecimal(maxValue / (Ylenth - 1)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue());

        //Y軸刻度數據
        for (int i = 0; i < Ylenth; i++) {
            int i1 = (int) (maxValue - i * v1);
            if (i1 < 0) {
                i1 = 0;
            }
            /**
             *使數據爲10、100、1000的倍數---純屬爲了數據好看,
             * 可以通過 {@link com.jjf.customchartline.view.MYChartLine#setYDataToMultiple(boolean)} 設置
             */
            if (isYDataToMultiple) {
                if (i1 > 1000) {
                    strY = i1 / 100 * 100 + "";
                } else if (i1 > 100) {
                    strY = i1 / 10 * 10 + "";
                } else {
                    strY = i1 + "";
                }
            } else {
                strY = i1 + "";
            }
            // Y軸文字
            int heigY = i * YScale + (linePain[0].getNumberPromptSize() / 2) + topDim + surplusHeight;
            if (i < Ylenth - 1) {
                float v = XPoint - strY.length() * (linePain[0].getNumberPromptSize() / 2) - 10;
                if (v < 0) {
                    v = 0;
                }
                canvas.drawText(strY, v, heigY, paintYText);
            }
            if (i == 0) {
                minY = heigY;
            } else if (i == Ylenth - 1) {
                maxY = heigY;
            }
        }
    }

  
    //根據手勢位置返回數據到頁面
    public void setToData(int x) {
        try {
            double mx = XScale / 2;
            x = (int) (x + mx);
            int div = (int) Arith.div((x - XPoint), XScale == 0 ? 1 : XScale, 2).doubleValue();
            chartResultData.getDatasY().clear();
            for (int j = 0; j < dataList.length; j++) {
                if (dataList[j].getLineBeens().size() > div) {
                    chartResultData.setDataX(dataList[j].getLineBeens().get(div).getX());
                    chartResultData.getDatasY().add(dataList[j].getLineBeens().get(div).getY() + "");
                } else {
                    chartResultData.getDatasY().add("0");
                }
            }
            setData(chartResultData);
//            MYChartLine.this.postInvalidate();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

項目資源地址 https://download.csdn.net/download/jiaojunfeng/10963486

上傳資源的時候 需要的C幣那裏沒法設置。默認是5。今天也沒法改,本想設置最少的那個1幣。。。。。

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