1. 程式人生 > >自定義帶節點點選的折線統計圖

自定義帶節點點選的折線統計圖

最近專案中要加一個折線統計圖,要求每個節點可以點選並且可以實現展示資料,我就自己繪製了一個統計圖:

下面是自定義的方法:

public class TestLineChartView extends View {


    private boolean noTitle;

    private float marginLeft;// 左邊距
    private float marginRight;// 右邊距
    private float marginTop;// 上邊距
    private float marginBottom;//下邊距
    private float widthInterval;// 單位寬
    private float heightInterval;// 單位高
    private float viewHeight = 0;// 控制元件高度
    private float viewWidth = 0;// 控制元件寬度

    private List<Double> values;// 折線值
    private List<String> dates;// 日期
    private List<String> stringValues = new ArrayList<String>();//折線值String
    private double maxV;// 最大值
    private double minV;// 最小值

    private float valueInterval;// 每格畫素的value值
    private List<String> numbers;// 縱座標

    private int pointNum = 0;// 選中的是哪一個點
    private float textSize = 0;// 字型大小
    private float titleSize;
    private float xSize;
    private float ySize;
    private int fillCircleRadio = 14;// 實心圓的半徑
    private int strokeCircleRadio = 16;//空心圓半徑
    private int lineSize = 10;
    private int fillPointSize = 5;//實心圓
    private int strokePointSize = 10;//空心圓粗細

    private TextPaint FontPaint;
    private Context mContext;


    // 構造方法
    public TestLineChartView(Context context) {
        this(context, null, 0);
        this.mContext = context;

    }

    public TestLineChartView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        this.mContext = context;
    }

    public TestLineChartView(Context context, AttributeSet attrs,
                             int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
    }

    /**
     * 設定折線的值
     *
     * @param values 折線值
     * @param dates  日期(七天)
     */
    public void setData(List<Double> values, List<String> dates, boolean noTitle) {

        if (values == null || values.size() == 0 || dates == null || dates.size() == 0) {
            return;
        }


        // 儲存傳入的引數
        this.values = values;
        this.dates = dates;
        this.noTitle = noTitle;

        reInitData();

        // 重繪
        this.invalidate();

    }

    // 設定字型大小
    public void setTextSize(int textSize, int titleSize, int xSize, int ySize) {
        this.textSize = textSize;
        this.titleSize = titleSize;
        this.xSize = xSize;
        this.ySize = ySize;
    }


    @Override
    protected void onDraw(Canvas canvas) {


        clearCanvas(canvas);// 清空畫布
        if (values == null || values.size() == 0 || dates == null || dates.size() == 0) {
            return;
        }
        initData();
        if (!noTitle) {
            drawTitle(canvas);// 畫"近七天收益趨勢"
        } else {
            marginTop = marginBottom;
        }

        drewTable(canvas);// 畫表格虛線

        drawX(canvas);// 畫日期
        drawY(canvas);// 畫縱座標數字

        drawDetail(canvas);// 畫詳情資訊(折線,圓點,圓角矩形)


        setClickable(true);// 設定控制元件為可點選
    }

    private void reInitData() {
        maxV = Collections.max(values); //最大值
        minV = Collections.min(values); //最小值
        maxV = (((int) (maxV - minV - 0.001d)) / (dates.size() - 1) + 1) * (dates.size() - 1) + minV;

        //得到最長數,和最短數
        for (double d : values) {
            String str = String.valueOf(d);
            stringValues.add(str);
        }

        pointNum = (dates.size() - 1);

        // 計算出縱座標顯示的值
        numbers = new ArrayList<>();
        for (int i = 0; i < values.size(); i++) {
            if (maxV == minV) {
                numbers.add(String.format("%.0f", 2 * maxV - (i) * maxV / (dates.size() - 1)));
            } else {
                numbers.add(String.valueOf((int) ((maxV - minV) * (values.size() - 1 - i) / (values.size() - 1) + minV)));
            }
        }

        lineSize = DisplayUtil.dip2px(mContext, 2f);//折線粗細
        fillCircleRadio = DisplayUtil.dip2px(mContext, 3.5f);//實心圓半徑
        strokeCircleRadio = DisplayUtil.dip2px(mContext, 4.25f);//空心圓半徑
        fillPointSize = DisplayUtil.dip2px(mContext, 1.5f);//實心圓粗細
        strokePointSize = DisplayUtil.dip2px(mContext, 2f);//空心圓粗細
    }

    private void initData() {

        viewWidth = getMeasuredWidth();
        viewHeight = getMeasuredHeight();
        // 計算出字型大小
        if (textSize == 0 || titleSize == 0 || xSize == 0 || ySize == 0) {
            textSize = viewWidth / 36;// 8.88
            titleSize = textSize;
            xSize = textSize * 2 / 3;
            ySize = textSize * 2 / 3;
        }

        if (null == numbers || numbers.size() == 0) {
            return;
        }

        // 計算出左邊距
        marginLeft = viewWidth / 30 + getTextWidth(ySize, numbers.get(0)) + strokeCircleRadio + strokePointSize;
        // 計算出右邊距
        marginRight = viewWidth / 12;
        // 計算出上邊距
        marginTop = getTextHeight(titleSize, "近七天收益趨勢") * 4;
        //計算出下邊距
        marginBottom = viewHeight / 10;

        // 計算出單位寬
        widthInterval = (viewWidth - marginLeft - marginRight) / (dates.size() - 1);
        // 計算出單位高
        heightInterval = (viewHeight - marginTop - marginBottom) / (values.size() - 1);

        // 計算出每格畫素的value值
        valueInterval = ((float) (maxV - minV)) / ((values.size() - 1) * heightInterval);
    }

    // 清空畫布
    private void clearCanvas(Canvas canvas) {
        canvas.drawARGB(0, 0, 0, 0);
    }

    // 畫"近七天收益趨勢"
    private void drawTitle(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.colorSendName8));
        paint.setTextSize(titleSize);
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        float textHeight = getTextHeight(titleSize, "近七天收益趨勢");
        textHeight = marginTop - textHeight * 5 / 2;
        canvas.drawText("近七天收益趨勢", getTextWidth(titleSize, "----"), textHeight,
                paint);
    }

    // 畫表格虛線
    private void drewTable(Canvas canvas) {

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(getResources().getColor(R.color.app_default_stroke_color));
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        //畫虛線的
//        PathEffect effects = new DashPathEffect(new float[]{2, 6}, 0);
//        paint.setPathEffect(effects);

        // 橫線
        for (int i = 0; i < values.size(); i++) {
            Path path = new Path();
            path.moveTo(marginLeft, marginTop + i * heightInterval);
            path.lineTo(marginLeft + (dates.size() - 1) * widthInterval, marginTop + i
                    * heightInterval);
            canvas.drawPath(path, paint);
        }

        // 豎線
        for (int i = 0; i < dates.size(); i++) {
            Path path = new Path();
            path.moveTo(marginLeft + i * widthInterval, marginTop + (values.size() - 1)
                    * heightInterval);
            path.lineTo(marginLeft + i * widthInterval, marginTop + (values.size() - 1)
                    * heightInterval + (values.size() - 1));
            canvas.drawPath(path, paint);
        }
    }

    private void drawDetail(Canvas canvas) {
        if (values == null || values.size() == 0 || textSize == 0) {
            return;
        }

        drawFoldLine(canvas);// 畫折線
        drawCircle(canvas);// 畫圓圈
        drawRoundRect(canvas);// 畫圓角矩形
    }

    // 畫折線
    private void drawFoldLine(Canvas canvas) {

        Paint paintFoldLine = new Paint();
        paintFoldLine.setColor(getResources().getColor(R.color.colorSendName8));
        paintFoldLine.setStrokeWidth(lineSize);// 資料線寬度(粗細)
        paintFoldLine.setAntiAlias(true);
        paintFoldLine.setStyle(Paint.Style.STROKE);

        Path pathFoldLine = new Path();
        pathFoldLine.moveTo(marginLeft,
                marginTop + Float.parseFloat((maxV - values.get(0)) + "")
                        / valueInterval);
        for (int i = 1; i < dates.size(); i++) {
            pathFoldLine.lineTo(marginLeft + i * widthInterval, marginTop
                    + Float.parseFloat((maxV - values.get(i)) + "")
                    / valueInterval);
        }

        canvas.drawPath(pathFoldLine, paintFoldLine);
    }

    // 畫圓圈
    private void drawCircle(Canvas canvas) {

        Paint fillPaint = new Paint();
        fillPaint.setColor(getResources().getColor(R.color.colorSendName8));
        fillPaint.setStrokeWidth(fillPointSize);
        fillPaint.setAntiAlias(true);
        fillPaint.setStyle(Paint.Style.FILL);

        Paint fillPaintWhite = new Paint();
        fillPaintWhite.setColor(Color.WHITE);
        fillPaintWhite.setStrokeWidth(strokePointSize);
        fillPaintWhite.setAntiAlias(true);
        fillPaintWhite.setStyle(Paint.Style.FILL);

        Paint paintStroke = new Paint();
        paintStroke.setColor(getResources().getColor(R.color.colorSendName8));
        paintStroke.setStrokeWidth(strokePointSize);
        paintStroke.setAntiAlias(true);
        paintStroke.setStyle(Paint.Style.STROKE);

        for (int i = 0; i < dates.size(); i++) {
            float sPointWidth = marginLeft + i * widthInterval;
            float sPointHeight = marginTop
                    + Float.parseFloat((maxV - values.get(i)) + "")
                    / valueInterval;


            if (i != pointNum) {
                canvas.drawCircle(sPointWidth, sPointHeight, fillCircleRadio,
                        fillPaint);
            } else {
                canvas.drawCircle(sPointWidth, sPointHeight, strokeCircleRadio,
                        fillPaintWhite);
                canvas.drawCircle(sPointWidth, sPointHeight, strokeCircleRadio,
                        paintStroke);
            }


        }

    }

    // 畫圓角矩形
    private void drawRoundRect(Canvas canvas) {
        // 畫圓角矩形
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);// 充滿
        paint.setColor(getResources().getColor(R.color.colorSendName2));
        paint.setAntiAlias(true);// 設定畫筆的鋸齒效果

        // 畫文字
        Paint paintText = new Paint();
        paintText.setStyle(Paint.Style.FILL);// 充滿
        paintText.setColor(Color.WHITE);
        paintText.setTextSize(textSize);

        float textWidth = getTextWidth(textSize, stringValues.get(pointNum));
        float textHeight = getTextHeight(textSize, stringValues.get(pointNum));


        float left;
        float leftText;
        float right;

        float top;
        float bottom;

        float skewing = strokeCircleRadio + strokePointSize / 2;
        if (mContext == null) {
            return;
        }
        float skewingWidth = DisplayUtil.dip2px(mContext, 5f);

        if (pointNum == 0) {
            left = marginLeft - skewing;
            right = marginLeft + textWidth + 2 * skewingWidth - skewing;

        } else if (pointNum == (dates.size() - 1)) {

            left = marginLeft + pointNum * widthInterval - textWidth - 2 * skewingWidth + skewing;
            right = marginLeft + pointNum * widthInterval + skewing;
        } else {

            left = marginLeft + pointNum * widthInterval - textWidth * 1 / 2 - skewingWidth;
            right = marginLeft + pointNum * widthInterval + textWidth * 1 / 2 + skewingWidth;
        }
        leftText = left
                + skewingWidth;

        top = marginTop
                + Float.parseFloat(maxV - values.get(pointNum) + "")
                / valueInterval - textHeight * 7 / 2;
        bottom = marginTop
                + Float.parseFloat(maxV - values.get(pointNum) + "")
                / valueInterval - textHeight * 3 / 2;

        RectF oval3 = new RectF(left, top, right, bottom);// 設定個新的長方形
        canvas.drawRoundRect(oval3, DisplayUtil.dip2px(mContext, 7.7f), DisplayUtil.dip2px(mContext, 5.5f), paint);// 第二個引數是x半徑,第三個引數是y半徑

        canvas.drawText(stringValues.get(pointNum), leftText, top + textHeight * 3 / 2, paintText);
    }

    // 畫日期(橫座標)
    private void drawX(Canvas canvas) {

        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.color_666666));
        paint.setTextSize(xSize);
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);// 數字字型的樣式 (粗細/實體或空心什麼的...)

        float textHeight = getTextHeight(xSize, dates.get(0));

        float height = marginTop + (dates.size() - 1) * heightInterval + textHeight;

        for (int i = 0; i < dates.size(); i++) {
            Path path = new Path();
            path.moveTo(marginLeft + i * widthInterval - getTextWidth(xSize, dates.get(i)) / 2, height + textHeight * 2 / 2);// 只用於移動移動畫筆。
            path.lineTo(marginLeft + i * widthInterval + getTextWidth(xSize, dates.get(i)) / 2, height + textHeight * 2 / 2);// 用於進行直線繪製。
            canvas.drawTextOnPath(dates.get(i), path, 0, 0, paint);
        }

    }

    // 畫縱座標數字
    private void drawY(Canvas canvas) {
        if (numbers == null || numbers.size() == 0 || ySize == 0) {
            return;
        }

        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.color_666666));
        paint.setTextSize(ySize);
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);// 數字字型的樣式 (粗細/實體或空心什麼的...)

        float width = marginLeft - getTextWidth(ySize, numbers.get(0)) - strokeCircleRadio - strokePointSize;
        float height = marginTop + getTextHeight(ySize, numbers.get(0)) / 2;
        for (int i = 0; i < numbers.size(); i++) {
            canvas.drawText(numbers.get(i), width, i * heightInterval
                    + height, paint);
        }

    }

    // 點選事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (null != values && values.size() != 0) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                // 得到座標
                float fx = event.getX();
                float fy = event.getY();

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

                    float width = marginLeft + i * widthInterval;
                    float height = marginTop
                            + Float.parseFloat((maxV - values.get(i)) + "")
                            / valueInterval;

                    if (fx > (width - widthInterval / 2) && fx < (width + widthInterval / 2)) {
                        if (fy > (height - heightInterval) && fy < (height + heightInterval)) {
                            pointNum = i;
                            this.invalidate();
                        }

                    }
                }

            }
        }
        return super.onTouchEvent(event);
    }


    private float getTextWidth(float Size, String text) {
        if (FontPaint == null) {
            FontPaint = new TextPaint();
        }
        FontPaint.setTextSize(Size);
        return FontPaint.measureText(text);
    }

    private int getTextHeight(float Size, String text) {
        if (FontPaint == null) {
            FontPaint = new TextPaint();
        }
        FontPaint.setTextSize(Size);
        Rect bounds = new Rect();
        FontPaint.getTextBounds(text, 0, text.length(), bounds);
        return bounds.bottom + bounds.height();
    }

}

然後可以看到有點選事件可以點選每個節點

下面給貼出呼叫方法:

dateList.add("05-01");
dateList.add("05-02");
dateList.add("05-03");
dateList.add("05-04");
dateList.add("05-05");
dateList.add("05-06");
dateList.add("05-07");
earnList.add(12.33);
earnList.add(31.33);
earnList.add(16.28);
earnList.add(60.12);
earnList.add(22.44);
earnList.add(52.21);
earnList.add(67.66);

data_table = findViewById(R.id.data_table);
// 折線圖
chartView = (TestLineChartView) findViewById(R.id.chartView);
chartView.setTextSize(25, 25, 25, 25);
chartView.setData(earnList,dateList,true);
data_table.setAdapter(new TableAdapter() {
    @Override
    public int getColumnCount() {
        return contentList.size();
    }

    @Override
    public String[] getColumnContent(int position) {
        return contentList.get(position).toArray();
    }
});

這也是借鑑別人的然後做了個小修改,如有冒犯告知刪帖