Android-自定义折线图

------------------------------------------------------------------>>> 简单版 ---- Android自定义控件之折线图

效果图:

布局代码:

<!-- 自定义折线图 -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".upgrade.MainActivity">

    <custom.view.upgrade.my_line_chart.MyLineChart
        android:id="@+id/my_line_chart"
        android:layout_width="400dp"
        android:layout_height="300dp"
        android:background="@color/ring_test3"/>

</LinearLayout>

设置参数相关的代码:

/**
  * 自定义折线图
  */
MyLineChart myLineChart = findViewById(R.id.my_line_chart);
myLineChart.setYItemText(new String[]{"1级", "2级", "3级", "4级", "5级"});
myLineChart.setXItemText(new String[]{"2001", "2002", "2003", "2004", "2005", "2006", "2007"});

自定义折线图View:

package custom.view.upgrade.my_line_chart;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

public class MyLineChart extends View {

    private final String TAG = MyLineChart.class.getSimpleName();

    public MyLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAction();
    }

    private Paint paintYItem; // 专门用于画Y轴的画笔
    private Paint paintXItem; // 专门用于话X轴的画笔

    private Paint paintLine;  // 线与点共用一个画笔

    private Rect rectX = new Rect();
    private Rect rectY = new Rect();

    private int w;
    private int h;

    private String[] xItemText = {}; // X轴的文字
    private String[] yItemText = {}; // Y轴的文字

    /**
     * 给使用者设置X轴的文字集合
     */
    public void setXItemText(String[] xItemText) {
        this.xItemText = xItemText;
    }

    /**
     * 给使用者设置Y轴的文字集合
     */
    public void setYItemText(String[] yItemText) {
        this.yItemText = yItemText;
        saveYHeightList = new int[yItemText.length];
    }

    private void initAction() {
        paintYItem = new Paint();
        paintYItem.setAntiAlias(true); // 抗锯齿
        paintYItem.setColor(Color.RED);
        paintYItem.setTextSize(40);

        paintXItem = new Paint();
        paintXItem.setAntiAlias(true);
        paintXItem.setColor(Color.BLUE);
        paintXItem.setTextSize(40);

        paintLine = new Paint();
        paintLine.setAntiAlias(true);
        paintLine.setColor(Color.BLACK);
        paintLine.setTextSize(40);

    }

    /**
     * 测量自身View控件的宽和高,宽和高得到父控件给子控件测量的高宽等
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 精确模式
        // MeasureSpec.EXACTLY 当控件设置尺寸的值是多少,控件的宽高就是多少

        // 最大模式
        // MeasureSpec.AT_MOST 由父控件给出了最大控件,子控件最大只能这么大,当然也可以比父控件小

        if (widthMode == MeasureSpec.EXACTLY) {
            w = widthSize;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            h = heightSize;
        }

        if (heightMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.AT_MOST) {
            throw new IllegalArgumentException("参数异常,请输入指定大小,例如:200dp");
        }

        setMeasuredDimension(w, h);
    }

    private int countYHeight; // 计算记录Y的高度

    private int[] saveYHeightList; // 保存Y轴 五级的高度

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

        if (yItemText.length == 0 || xItemText.length == 0) {
            throw new IllegalArgumentException("参数异常,设置x轴/y轴集合数据值为空");
        }

        // 画Y轴集合数据
        for (int y = 0; y< yItemText.length; y++) {
            countYHeight = (y + 1) * 118;
            canvas.drawText(yItemText[y], 40, countYHeight, paintYItem);
            saveYHeightList[y] = countYHeight;
        }

        paintLine.getTextBounds(xItemText[0], 0, xItemText[0].length(), rectX);
        paintLine.getTextBounds(yItemText[0], 0, yItemText[0].length(), rectY);

        // 画X轴集合的数据
        for (int x = 0; x <xItemText.length; x++) {
            int xLocation = (x + 1) * 122;
            canvas.drawText(xItemText[x], xLocation, (countYHeight + 80), paintXItem);

            // 定义线的 开始 到 结束
            int tempStartY = 0;
            int tempStopY = 0;
            switch (x) {
                case 0:
                    tempStartY = saveYHeightList[0] - (rectY.height() / 3);
                    tempStopY = saveYHeightList[1] - (rectY.height() / 3);
                    break;
                case 1:
                    tempStartY = saveYHeightList[1] - (rectY.height() / 3);
                    tempStopY = saveYHeightList[2] - (rectY.height() / 3);
                    break;
                case 2:
                    tempStartY = saveYHeightList[2] - (rectY.height() / 3);
                    tempStopY = saveYHeightList[3] - (rectY.height() / 3);
                    break;
                case 3:
                    tempStartY = saveYHeightList[3] - (rectY.height() / 3);
                    tempStopY = saveYHeightList[4] - (rectY.height() / 3);
                    break;
                case 4:
                    tempStartY = saveYHeightList[4] - (rectY.height() / 3);
                    tempStopY = saveYHeightList[3] - (rectY.height() / 3);
                    break;
                case 5:
                    tempStartY = saveYHeightList[3] - (rectY.height() / 3);
                    tempStopY  = saveYHeightList[1] - (rectY.height() / 3);
                    break;
                case 6:
                    tempStartY = saveYHeightList[1] - (rectY.height() / 3);
                    break;
                default:
                    break;
            }

            // 画点,注意:X轴需要与点保持一致的位置,只变化Y轴值的位置
            canvas.drawCircle(xLocation + (rectX.width() / 2),
                    tempStartY,
                    20,
                    paintLine);

            // 画线
            if (x < xItemText.length-1) {
                int xLocationLine = ((x + 1) + 1) * 122;
                canvas.drawLine(xLocation + (rectX.width() / 2),
                        tempStartY,
                        xLocationLine + (rectX.width() / 2),
                        tempStopY,
                        paintLine);
            }
        }
    }
}

------------------------------------------------------------------>>> 升级版 ---- Android自定义控件之折线图

效果图:

 

背景颜色:

布局代码:

<!-- 自定义折线图 升级版 -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".upgrade.MainActivity">

    <custom.view.upgrade.my_line_chart2.MyLineChart2
        android:id="@+id/my_line_chart"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/ring_test3"/>

</LinearLayout>

自定义折线图绘制类:

package custom.view.upgrade.my_line_chart2;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import custom.view.R;

public class MyLineChart2 extends View {

    private final String TAG = MyLineChart2.class.getSimpleName();

    public MyLineChart2(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private Paint xyLinePaint; // X轴与Y轴的灰色线的画笔
    private final int XY_LINE_SPACE = 120; // X轴Y轴距离左边底边距离间隙值

    private String[] yListData = {"7.0", "6.0", "5.0", "4.0", "3.0", "2.0", "1.0", "0.0"};
    private String[] xListData = {"活期", "三个月", "六个月", "一年", "两年", "三年", "四年", "五年"};

    // 基准利率的画笔
    private Paint standardRatePaint;

    // 真惠存利率
    private Paint trueHuicunReatPaint;

    // 记录X轴文字的位置
    private int[] xTextLocation;

    // 记录Y轴顶部越界值 与底部越界值
    private int topDemarcation;
    private int bottomeDemarcation;

    // 基准利率动态变化的Y轴值
    private int changeY;
    private int changStopY;

    // 真惠存利率动态变化的Y轴值
    private int changeYHuicun;
    private int changeStopYHuicun;

    // 基准利率动态变化的值
    private int[] changYList = {200, 400, 300, 350, 500, 450, 300};

    // 真惠存动态变化的值
    private int[] changYHuicunList = {540, 300, 350, 600, 550, 480, 200};

    private Rect rect;

    // 专门用于画解释信息文字的画笔
    private Paint textInfoPaint;

    private void initView() {
        xyLinePaint = new Paint();
        xyLinePaint.setAntiAlias(true); // 抗锯齿
        xyLinePaint.setColor(getResources().getColor(R.color.xyline));
        xyLinePaint.setTextSize(22);

        standardRatePaint = new Paint();
        standardRatePaint.setAntiAlias(true);
        standardRatePaint.setColor(getResources().getColor(R.color.standard_rate));
        standardRatePaint.setStyle(Paint.Style.STROKE);
        standardRatePaint.setStrokeWidth(6);

        trueHuicunReatPaint = new Paint();
        trueHuicunReatPaint.setAntiAlias(true);
        trueHuicunReatPaint.setColor(getResources().getColor(R.color.true_huicun));
        trueHuicunReatPaint.setStyle(Paint.Style.STROKE);
        trueHuicunReatPaint.setStrokeWidth(6);

        xTextLocation = new int[xListData.length];
        rect = new Rect();

        textInfoPaint = new Paint();
        textInfoPaint.setAntiAlias(true);
        textInfoPaint.setColor(getResources().getColor(R.color.xyline));
        textInfoPaint.setTextSize(32);
    }

    /**
     * 基准利率七个值
     */
    public void setStandardData(int[] changYList) {
        this.changYList = changYList;
    }

    /**
     * 真惠存利率七个值
     */
    public void setTureHuicunData(int[] changYHuicunList) {
        this.changYHuicunList = changYHuicunList;
    }

    private int viewWidth; // 当前控件的宽度
    private int viewHeight; // 当前控件的高度

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // MeasureSpec.EXACTLY 精确模式,指定大小,例如:200dp
        // MeasureSpec.AT_MOST 最大模式,由父控件控制最大,子控件不能比父控件大,当然可以比父控件小

        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            viewHeight = MeasureSpec.getSize(heightMeasureSpec);
        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            throw new IllegalArgumentException("高度指定参数异常,请按照,例如:200dp");
        }
        viewWidth = MeasureSpec.getSize(widthMeasureSpec);

        // 这两个值是根据手机屏幕而变化的
        Log.d(TAG, "viewHeight:" + viewHeight + " viewWidth:" + viewWidth);

        setMeasuredDimension(viewWidth, viewHeight);
    }

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

        // 画Y轴的箭头符号
        canvas.drawLine(XY_LINE_SPACE, 40, XY_LINE_SPACE + 16, 80, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, 40, XY_LINE_SPACE - 16, 80, xyLinePaint);

        // 画Y轴线
        canvas.drawLine(XY_LINE_SPACE, 40, XY_LINE_SPACE, viewHeight - XY_LINE_SPACE, xyLinePaint);

        // 画Y轴线中的短线 15根
        int  startStopY = 0;
        for (int y=0; y<15; y++) {
            if (y == 0) {
                startStopY = 80;
            }
            startStopY += 35;
            canvas.drawLine(XY_LINE_SPACE, startStopY, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        }
        /*canvas.drawLine(XY_LINE_SPACE, startStopY = 100, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);
        canvas.drawLine(XY_LINE_SPACE, startStopY += 35, XY_LINE_SPACE + 20, startStopY, xyLinePaint);*/

        // 画Y轴线中的信息 字符串
        int textY = 0;
        for (int y2 = 0; y2 < yListData.length; y2 ++) {
            if (y2 == 0) {
                textY = 80;
                topDemarcation = textY;
                topDemarcation += 60;
            }
            textY += 60;
            bottomeDemarcation = textY;
            canvas.drawText(yListData[y2], XY_LINE_SPACE - 60, textY, xyLinePaint);
        }

        // 画X的箭头符号
        canvas.drawLine(viewWidth - (viewWidth / 8), viewHeight - XY_LINE_SPACE, (viewWidth - (viewWidth / 8)) - 40, (viewHeight - XY_LINE_SPACE) - 16, xyLinePaint);
        canvas.drawLine(viewWidth - (viewWidth / 8), viewHeight - XY_LINE_SPACE, (viewWidth - (viewWidth / 8)) - 40, (viewHeight - XY_LINE_SPACE) + 16, xyLinePaint);

        // 画X轴线
        canvas.drawLine(XY_LINE_SPACE, viewHeight - XY_LINE_SPACE, viewWidth - (viewWidth / 8), viewHeight - XY_LINE_SPACE, xyLinePaint);

        // 画15根X轴线
        int startStopX = 0;
        for (int x = 0; x<14; x ++) {
            if (x == 0) {
                startStopX = XY_LINE_SPACE + 20;
            }
            startStopX += 50;
            canvas.drawLine(startStopX, viewHeight - XY_LINE_SPACE, startStopX, viewHeight - XY_LINE_SPACE - 20, xyLinePaint);
        }

        // 画X轴字符串信息
        int testX = 0;
        for (int x2 = 0; x2 < xListData.length; x2 ++) {
            if (testX == 0) {
                testX = 110;
            }
            testX += 100;
            xTextLocation[x2] = testX; // 记录X轴方向的文字位置
            canvas.drawText(xListData[x2], testX,viewHeight - XY_LINE_SPACE + 40, xyLinePaint);
        }

        // 画七个点,X轴方向位置一直不变,Y轴方向的位置是变化的
        for (int dianXY = 0; dianXY < xListData.length - 1; dianXY ++) {
            standardRatePaint.getTextBounds(xListData[dianXY], 0, xListData[dianXY].length(), rect);

            changeY = changYList[dianXY];
            // 如果不等于最后一个,就可以+1
            Log.d(TAG, "dianXY:" + dianXY + " changYList.length:" + changYList.length);
            if (dianXY < changYList.length - 1) {
                changStopY = changYList[dianXY + 1];
            }

            if (changeY > bottomeDemarcation) {
                changeY = bottomeDemarcation;
            } else if (changeY < topDemarcation) {
                changeY = topDemarcation;
            }

            if (changStopY > bottomeDemarcation) {
                changStopY = bottomeDemarcation;
            } else if (changStopY < topDemarcation) {
                changStopY = topDemarcation;
            }

            Log.d(TAG, "rect.width():" + rect.width() + " bottomeDemarcation:" + bottomeDemarcation
                    + " topDemarcation:" + topDemarcation + " changeY:" + changeY);

            standardRatePaint.getTextBounds(yListData[0], 0, yListData[0].length(), rect);

            canvas.drawCircle(xTextLocation[dianXY] + (rect.width()) ,changeY - rect.height(),10, standardRatePaint);

            // 画线 开始的X轴与Y轴要增加,结束的X轴与Y轴要减少
            if (dianXY < xListData.length - 2) {
                changeY -= rect.height();
                // changStopY += rect.height();
                changStopY -= rect.height();

                /*changeY += 10;
                changStopY -= 10;*/

                // 画线,线的走向是根据点走的
                canvas.drawLine(xTextLocation[dianXY] + (rect.width() + 8),
                        changeY,
                        xTextLocation[dianXY > xListData.length ? xListData.length : dianXY + 1] + (rect.width() - 8),
                        changStopY,
                        standardRatePaint);
            }

            /**
             * 真惠存利率区域
             */
            changeYHuicun = changYHuicunList[dianXY];
            // 如果不等于最后一个,就可以+1
            Log.d(TAG, "changYHuicunList.length:" + changYHuicunList.length);
            if (dianXY < changYHuicunList.length - 1) {
                changeStopYHuicun = changYHuicunList[dianXY + 1];
            }

            if (changeYHuicun > bottomeDemarcation) {
                changeYHuicun = bottomeDemarcation;
            } else if (changeYHuicun < topDemarcation) {
                changeYHuicun = topDemarcation;
            }

            if (changeStopYHuicun > bottomeDemarcation) {
                changeStopYHuicun = bottomeDemarcation;
            } else if (changeStopYHuicun < topDemarcation) {
                changeStopYHuicun = topDemarcation;
            }

            trueHuicunReatPaint.getTextBounds(yListData[0], 0, yListData[0].length(), rect);

            canvas.drawCircle(xTextLocation[dianXY] + (rect.width()) ,changeYHuicun - rect.height(),10, trueHuicunReatPaint);

            // 画线 开始的X轴与Y轴要增加,结束的X轴与Y轴要减少
            if (dianXY < xListData.length - 2) {
                changeYHuicun -= rect.height();
                changeStopYHuicun -= rect.height();

                /*changeY += 10;
                changStopY -= 10;*/

                // 画线,线的走向是根据点走的
                canvas.drawLine(xTextLocation[dianXY] + (rect.width() + 8),
                        changeYHuicun,
                        xTextLocation[dianXY > xListData.length ? xListData.length : dianXY + 1] + (rect.width() - 8),
                        changeStopYHuicun,
                        trueHuicunReatPaint);
            }
        }

        // 画两个描述:基准利率(%) 真惠存利率(%)
        int heightLineCircle = XY_LINE_SPACE - (XY_LINE_SPACE / 2);
        int radius = 10;
        // 先画一个点
        canvas.drawCircle(xTextLocation[2],  heightLineCircle, radius, standardRatePaint);
        // 画点的左边横线
        canvas.drawLine(xTextLocation[2] - 6, heightLineCircle, xTextLocation[2] - 70, heightLineCircle, standardRatePaint);
        // 画点的右边横线
        canvas.drawLine(xTextLocation[2] + 6,heightLineCircle,xTextLocation[2] + 70, heightLineCircle, standardRatePaint);
        // 画文字
        canvas.drawText("基准利率(%)", xTextLocation[2] - 90, heightLineCircle + 50, textInfoPaint);

        // 先画一个点
        canvas.drawCircle(xTextLocation[5], heightLineCircle, radius,trueHuicunReatPaint);
        canvas.drawLine(xTextLocation[5] - 6, heightLineCircle, xTextLocation[5] - 70, heightLineCircle, trueHuicunReatPaint);
        canvas.drawLine(xTextLocation[5] + 6, heightLineCircle, xTextLocation[5] + 70, heightLineCircle, trueHuicunReatPaint);
        canvas.drawText("真惠存利率(%)", xTextLocation[5] - 90, heightLineCircle + 50, textInfoPaint);
    }

    /**
     * dp转px
     * @param dp
     * @return
     */
   /* private int dip2px(int dp) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5);
    }*/
}
原文地址:https://www.cnblogs.com/android-deli/p/9789710.html