Android自定义View——曲线图

之前在博客里面已经介绍过了一种自定义绘制饼图的方法,今天再来介绍一种自定义绘制曲线图的方法。博主之前做过折线图,但是这里需要曲线图,简单的 drawLine 肯定是不行了,查了资料有人推荐用贝塞尔曲线绘制,这里对贝塞尔曲线就不做详细说明了。用贝塞尔曲线绘图的,首先对初学者来说比较麻烦,控制点的不易确定,再者,我看了网上的一些例子,发现贝塞尔曲线画出来的曲线图,跟我们平时画的曲线图还是有一点出入的,在点的衔接处的连贯性并不好,我在网上截了一张图,可以看看贝塞尔曲线的效果。


这里面点比较大,还遮挡一部分曲线,不然可以明显感觉到点的连接处不自然。

这样子肯定不行啊,那只好查查文档了,发现 Path 中有一个 PathEffect 子类,其中有一个 CornerPathEffect,作用是将 Path 的各个连接线段之间的夹角用一种更平滑的方式连接。听上去不错,于是试了下,下面看看效果图:


看起来还不错,下面是主要实现方法。

public class CustomCurveChart extends View {

    // 坐标单位
    private String[] xLabel;
    private String[] yLabel;
    // 曲线数据
    private List<int[]> dataList;
    private List<Integer> colorList;
    private boolean showValue;
    // 默认边距
    private int margin = 20;
    // 距离左边偏移量
    private int marginX = 30;
    // 原点坐标
    private int xPoint;
    private int yPoint;
    // X,Y轴的单位长度
    private int xScale;
    private int yScale;
    // 画笔
    private Paint paintAxes;
    private Paint paintCoordinate;
    private Paint paintTable;
    private Paint paintCurve;
    private Paint paintRectF;
    private Paint paintValue;

    public CustomCurveChart(Context context, String[] xLabel, String[] yLabel,
                            List<int[]> dataList, List<Integer> colorList, boolean showValue) {
        super(context);
        this.xLabel = xLabel;
        this.yLabel = yLabel;
        this.dataList = dataList;
        this.colorList = colorList;
        this.showValue = showValue;
    }

    public CustomCurveChart(Context context) {
        super(context);
    }

    /**
     * 初始化数据值和画笔
     */
    public void init() {
        xPoint = margin + marginX;
        yPoint = this.getHeight() - margin;
        xScale = (this.getWidth() - 2 * margin - marginX) / (xLabel.length - 1);
        yScale = (this.getHeight() - 2 * margin) / (yLabel.length - 1);

        paintAxes = new Paint();
        paintAxes.setStyle(Paint.Style.STROKE);
        paintAxes.setAntiAlias(true);
        paintAxes.setDither(true);
        paintAxes.setColor(ContextCompat.getColor(getContext(), R.color.color14));
        paintAxes.setStrokeWidth(4);

        paintCoordinate = new Paint();
        paintCoordinate.setStyle(Paint.Style.STROKE);
        paintCoordinate.setDither(true);
        paintCoordinate.setAntiAlias(true);
        paintCoordinate.setColor(ContextCompat.getColor(getContext(), R.color.color14));
        paintCoordinate.setTextSize(15);

        paintTable = new Paint();
        paintTable.setStyle(Paint.Style.STROKE);
        paintTable.setAntiAlias(true);
        paintTable.setDither(true);
        paintTable.setColor(ContextCompat.getColor(getContext(), R.color.color4));
        paintTable.setStrokeWidth(2);

        paintCurve = new Paint();
        paintCurve.setStyle(Paint.Style.STROKE);
        paintCurve.setDither(true);
        paintCurve.setAntiAlias(true);
        paintCurve.setStrokeWidth(3);
        PathEffect pathEffect = new CornerPathEffect(25);
        paintCurve.setPathEffect(pathEffect);

        paintRectF = new Paint();
        paintRectF.setStyle(Paint.Style.FILL);
        paintRectF.setDither(true);
        paintRectF.setAntiAlias(true);
        paintRectF.setStrokeWidth(3);

        paintValue = new Paint();
        paintValue.setStyle(Paint.Style.STROKE);
        paintValue.setAntiAlias(true);
        paintValue.setDither(true);
        paintValue.setColor(ContextCompat.getColor(getContext(), R.color.color1));
        paintValue.setTextAlign(Paint.Align.CENTER);
        paintValue.setTextSize(15);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(ContextCompat.getColor(getContext(), R.color.color1));
        init();
        drawTable(canvas, paintTable);
        drawAxesLine(canvas, paintAxes);
        drawCoordinate(canvas, paintCoordinate);
        for (int i = 0; i < dataList.size(); i++) {
            drawCurve(canvas, paintCurve, dataList.get(i), colorList.get(i));
            if (showValue) {
                drawValue(canvas, paintRectF, dataList.get(i), colorList.get(i));
            }
        }
    }

    /**
     * 绘制坐标轴
     */
    private void drawAxesLine(Canvas canvas, Paint paint) {
        // X
        canvas.drawLine(xPoint, yPoint, this.getWidth() - margin / 6, yPoint, paint);
        canvas.drawLine(this.getWidth() - margin / 6, yPoint, this.getWidth() - margin / 2, yPoint - margin / 3, paint);
        canvas.drawLine(this.getWidth() - margin / 6, yPoint, this.getWidth() - margin / 2, yPoint + margin / 3, paint);

        // Y
        canvas.drawLine(xPoint, yPoint, xPoint, margin / 6, paint);
        canvas.drawLine(xPoint, margin / 6, xPoint - margin / 3, margin / 2, paint);
        canvas.drawLine(xPoint, margin / 6, xPoint + margin / 3, margin / 2, paint);
    }

    /**
     * 绘制表格
     */
    private void drawTable(Canvas canvas, Paint paint) {
        Path path = new Path();
        // 横向线
        for (int i = 1; (yPoint - i * yScale) >= margin; i++) {
            int startX = xPoint;
            int startY = yPoint - i * yScale;
            int stopX = xPoint + (xLabel.length - 1) * xScale;
            path.moveTo(startX, startY);
            path.lineTo(stopX, startY);
            canvas.drawPath(path, paint);
        }

        // 纵向线
        for (int i = 1; i * xScale <= (this.getWidth() - margin); i++) {
            int startX = xPoint + i * xScale;
            int startY = yPoint;
            int stopY = yPoint - (yLabel.length - 1) * yScale;
            path.moveTo(startX, startY);
            path.lineTo(startX, stopY);
            canvas.drawPath(path, paint);
        }
    }

    /**
     * 绘制刻度
     */
    private void drawCoordinate(Canvas canvas, Paint paint) {
        // X轴坐标
        for (int i = 0; i <= (xLabel.length - 1); i++) {
            paint.setTextAlign(Paint.Align.CENTER);
            int startX = xPoint + i * xScale;
            canvas.drawText(xLabel[i], startX, this.getHeight() - margin / 6, paint);
        }

        // Y轴坐标
        for (int i = 0; i <= (yLabel.length - 1); i++) {
            paint.setTextAlign(Paint.Align.LEFT);
            int startY = yPoint - i * yScale;
            int offsetX;
            switch (yLabel[i].length()) {
                case 1:
                    offsetX = 28;
                    break;

                case 2:
                    offsetX = 20;
                    break;

                case 3:
                    offsetX = 12;
                    break;

                case 4:
                    offsetX = 5;
                    break;

                default:
                    offsetX = 0;
                    break;
            }
            int offsetY;
            if (i == 0) {
                offsetY = 0;
            } else {
                offsetY = margin / 5;
            }
            // x默认是字符串的左边在屏幕的位置,y默认是字符串是字符串的baseline在屏幕上的位置
            canvas.drawText(yLabel[i], margin / 4 + offsetX, startY + offsetY, paint);
        }
    }

    /**
     * 绘制曲线
     */
    private void drawCurve(Canvas canvas, Paint paint, int[] data, int color) {
        paint.setColor(ContextCompat.getColor(getContext(), color));
        Path path = new Path();
        for (int i = 0; i <= (xLabel.length - 1); i++) {
            if (i == 0) {
                path.moveTo(xPoint, toY(data[0]));
            } else {
                path.lineTo(xPoint + i * xScale, toY(data[i]));
            }

            if (i == xLabel.length - 1) {
                path.lineTo(xPoint + i * xScale, toY(data[i]));
            }
        }
        canvas.drawPath(path, paint);
    }

    /**
     * 绘制数值
     */
    private void drawValue(Canvas canvas, Paint paint, int data[], int color) {
        paint.setColor(ContextCompat.getColor(getContext(), color));
        for (int i = 1; i <= (xLabel.length - 1); i++) {
            RectF rect;
            if (toY(data[i - 1]) < toY(data[i])) {
                rect = new RectF(xPoint + i * xScale - 20, toY(data[i]) - 15,
                        xPoint + i * xScale + 20, toY(data[i]) + 5);
                canvas.drawRoundRect(rect, 5, 5, paint);
                canvas.drawText(data[i] + "w", xPoint + i * xScale, toY(data[i]), paintValue);
            } else if (toY(data[i - 1]) > toY(data[i])) {
                rect = new RectF(xPoint + i * xScale - 20, toY(data[i]) - 5,
                        xPoint + i * xScale + 20, toY(data[i]) + 15);
                canvas.drawRoundRect(rect, 5, 5, paint);
                canvas.drawText(data[i] + "w", xPoint + i * xScale, toY(data[i]) + 10, paintValue);
            } else {
                rect = new RectF(xPoint + i * xScale - 20, toY(data[i]) - 10,
                        xPoint + i * xScale + 20, toY(data[i]) + 10);
                canvas.drawRoundRect(rect, 5, 5, paint);
                canvas.drawText(data[i] + "w", xPoint + i * xScale, toY(data[i]) + 5, paintValue);
            }
        }
    }

    /**
     * 数据按比例转坐标
     */
    private float toY(int num) {
        float y;
        try {
            float a = (float) num / 100.0f;
            y = yPoint - a * yScale;
        } catch (Exception e) {
            return 0;
        }
        return y;
    }

}
图中的两张曲线图都是一个方法,通过一个参数 showValue 控制是否显示数值。


最后附上源码

  • 32
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值