手把手教你写一个可以上下滑动点改变值的安卓折线图(iOS同理,iOS看不懂联系我出个iOS版- -、最好别联系)

也写了两个月安卓了,这周完了就放假~产出点还算有用的东西。

先看效果

2.gif

思路

首先要画折线图,那么一定是要用到Canvas,其次的难点在与如果找到手指点到的点,以及手指如何拖动。

由于是折线图,那么一定需要按照线和比例来算值,长和宽的在屏幕上的长度是死的,但是值是活的,所以所有的值都需要根据比例去匹配

然后是手指在View上的点,这个可以参考画画的那类demo

最后是拖动,拖动简单,难一些的就是根据比例去算。但是思路有了这点就不是难点了

放代码

1、首先就是在init方法里初始化一些必要的东西

private void init() {

        if (data.size() == 0) {
            SlideYLine line = new SlideYLine();
            line.setColor("#7fffd4");
            for (int i = -5; i < 6; i ++) {
                SlideYChartPoint point = new SlideYChartPoint(i, 0);
                line.addPoint(point);
            }
            data.add(line);

            SlideYLine line1 = new SlideYLine();
            line1.setColor("#0000ff");
            for (int i = -5; i < 12; i ++) {
                SlideYChartPoint point = new SlideYChartPoint(i * 2, i * 3);
                line1.addPoint(point);
            }
            data.add(line1);
            searchMinAndMax();
        }


        int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

        hLength = width * 0.75f;          // x轴长度
        vLength = height * 0.75f;         // y轴长度
        tableX = (width - hLength) / 2;       // 开始绘图的x坐标
        tableY = (height - vLength) / 2;       // 开始UI图的y坐标
        coordTextSize = (float) (width / 7.5 / 5);
    }

2、当然是去onDraw里写代码

@Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

        canvas.drawColor(Color.WHITE);
        init();
        drawTable(canvas);// 画表格
        drawLabel(canvas);// 画标签
        drawLine(canvas);// 画线
        drawPoint(canvas);// 画点
    }
// 画表格
    private void drawTable(Canvas canvas) {

        hMargin = vLength / (hLineCount - 1);

        Paint tablePaint = new Paint();
//        tablePaint.setColor(Color.BLACK);
        tablePaint.setStrokeWidth((float) 1.0);              // 设置线宽
        // 横线
        for (int i = 0; i < hLineCount; i++) {
            canvas.drawLine(tableX, tableY + i * hMargin, tableX + hLength, tableY + i * hMargin, tablePaint);        //绘制直线
//            tablePaint.setStrokeWidth((float) 5.0);              // 设置线宽
        }

        vMargin = hLength / (vLineCount - 1);
        for (int i = 0; i < vLineCount; i++) {
            canvas.drawLine(tableX + i * vMargin, tableY, tableX + i * vMargin, tableY + vLength, tablePaint);        //绘制直线
//            tablePaint.setStrokeWidth((float) 5.0);
        }
    }

    // 画标签
    private void drawLabel(Canvas canvas) {

        if (hMargin == -1 || vMargin == -1) {
            Log.d(TAG, "drawLabel: 出错了, 没有算出间距?");
            return;
        }

        Paint labelPaint = new Paint();
        labelPaint.setColor(Color.BLUE);
        labelPaint.setTextSize(coordTextSize);

        Rect bounds = new Rect();

        float xScale = (maxX - minX) / (vLineCount - 1);
        float yScale = (maxY - minY) / (hLineCount - 1);
        if (xScale == 0) {
            maxX = 5;
            minX = -5;
            xScale = (maxX - minX) / (vLineCount - 1);
        }
        if (yScale == 0) {
            maxY = 5;
            minY = -5;
            yScale = (maxY - minY) / (hLineCount - 1);
        }


        // 画左 y label
        for (int i = 0; i < hLineCount; i++) {
            String label = format2Bit(maxY - i * yScale);
            labelPaint.getTextBounds(label, 0, label.length(), bounds);
            canvas.drawText(label, tableX - bounds.width() - 8, tableY + bounds.height() / 2 + hMargin * i, labelPaint);
        }

        // 画右 y label
        for (int i = 0; i < hLineCount; i++) {
            String label = format2Bit(maxY - i * yScale);
            labelPaint.getTextBounds(label, 0, label.length(), bounds);
            canvas.drawText(label, tableX + hLength + 8, tableY + bounds.height() / 2 + hMargin * i, labelPaint);
        }

        // 画上 x label
        for (int i = 0; i < vLineCount; i++) {
            String label = format2Bit(minX + i * xScale);
            labelPaint.getTextBounds(label, 0, label.length(), bounds);
            canvas.drawText(label, tableX - bounds.width() / 2 + vMargin * i, tableY - 8, labelPaint);
        }

        // 画下 x label
        for (int i = 0; i < vLineCount; i++) {
            String label = format2Bit(minX + i * xScale);
            labelPaint.getTextBounds(label, 0, label.length(), bounds);
            canvas.drawText(label, tableX - bounds.width() / 2 + vMargin * i, tableY + vLength + bounds.height() + 8, labelPaint);
        }
    }


    // 画线
    private void drawLine(Canvas canvas) {

        Paint linePaint = new Paint();
        float width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        linePaint.setStrokeWidth(width / 7.5f / 25f);

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

            linePaint.setColor(Color.parseColor(data.get(i).getColor()));// 设置颜色
            ArrayList points = data.get(i).getPoints();

            for (int j = 0; j < points.size() - 1; j++) {
                SlideYChartPoint point = coordinateConversion(points.get(j));
                SlideYChartPoint nextPoint = coordinateConversion(points.get(j + 1));
                canvas.drawLine(point.getX(), point.getY(), nextPoint.getX(), nextPoint.getY(), linePaint);
            }
        }
    }

    // 画点
    private void drawPoint(Canvas canvas) {

        int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        float tempScale = width / 7.5f;

        Paint pointPaint = new Paint();
        pointPaint.setStrokeCap(Paint.Cap.ROUND);

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

            ArrayList points = data.get(i).getPoints();
            for (int j = 0; j < points.size(); j++) {
                pointPaint.setColor(Color.parseColor(data.get(i).getColor()));// 设置颜色
                SlideYChartPoint point = coordinateConversion(points.get(j));
                canvas.drawCircle(point.getX(), point.getY(), tempScale / 15, pointPaint);
                pointPaint.setColor(Color.WHITE);
                canvas.drawCircle(point.getX(), point.getY(), tempScale / 30, pointPaint);
            }
        }
    }

3、点击点显示x和y的数据

/**
     * 点击数据点后,展示详细的数据值
     */
    private void showDetails() {
        if (mPopWin != null) mPopWin.dismiss();
        TextView tv = new TextView(getContext());
        tv.setTextColor(Color.WHITE);
        tv.setBackgroundResource(R.drawable.shape_pop_bg);
        GradientDrawable myGrad = (GradientDrawable) tv.getBackground();

        for (int i = 0; i < data.size(); i++) {
            if (data.get(i).getPoints().contains(movePoint)) {
                myGrad.setColor(Color.parseColor(data.get(i).getColor()));
                break;
            }
        }

        tv.setPadding(20, 0, 20, 0);
        tv.setGravity(Gravity.CENTER);
        tv.setText("x:" + movePoint.getX() + ",y:" + format2Bit(movePoint.getY()));
        mPopWin = new PopupWindow(tv, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        mPopWin.setBackgroundDrawable(new ColorDrawable(0));
        mPopWin.setFocusable(false);
        // 根据坐标点的位置计算弹窗的展示位置
        SlideYChartPoint tempPoint = coordinateConversion(movePoint);
        int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        float tempXScale = width / 7.5f;
        int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        float tempYScale = height / 7.5f;
        int xoff = (int) (tempPoint.getX() - 0.5 * tempXScale);
        int yoff = -(int) (getHeight() - tempPoint.getY() + 0.75f * tempYScale);
        mPopWin.showAsDropDown(this, xoff, yoff);
        mPopWin.update();
    }

4、拖动点改值

@Override
    public boolean onTouchEvent(MotionEvent event) {

        float touchX = event.getX();
        float touchY = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:


                lastY = event.getRawY();

                int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
                float tempXScale = width / 7.5f;
                int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
                float tempYScale = height / 7.5f;

                ArrayList tempPoints = new ArrayList<>();
                for (int i = 0; i < data.size(); i++) {
                    SlideYLine line = data.get(i);
                    ArrayList points = line.getPoints();
                    for (int j = 0; j < points.size(); j++) {
                        SlideYChartPoint point = coordinateConversion(points.get(j));
                        if (Math.abs(touchX - point.getX()) < tempXScale / 5 && Math.abs(touchY - point.getY()) < tempYScale / 5) {
                            tempPoints.add(points.get(j));
                        }
                    }
                }

                if (tempPoints.size() > 1) {
                    float tempMargin = 0;
                    for (int i = 0; i < tempPoints.size(); i++) {
                        SlideYChartPoint point = tempPoints.get(i);
                        if (tempMargin < Math.abs(touchX - point.getX()) + Math.abs(touchY - point.getY())) {
                            movePoint = point;
                        }
                    }
                } else if (tempPoints.size() == 1) {
                    movePoint = tempPoints.get(0);
                } else {
                    return false;
                }

                return true;
            case MotionEvent.ACTION_MOVE:

                if (movePoint == null) {
//                    Toast.makeText(mContext, "滑动时出错", Toast.LENGTH_SHORT).show();
                    return false;
                }

                float dy = event.getRawY() - lastY;
                movePoint.setY(movePoint.getY() - coordinateConversionY(dy));

                setData((ArrayList) data.clone());
                fresh();

                lastY = (int) event.getRawY();

                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: up");
                showDetails();
                movePoint = null;
                break;
        }


        return super.onTouchEvent(event);
    }

5、外部传值

public void setData(ArrayList data) {
        this.data = data;

        searchMinAndMax();
    }

private void searchMinAndMax() {

        for (int i = 0; i < data.size(); i++) {
            SlideYLine line = data.get(i);
            for (int j = 0; j < line.getPoints().size(); j++) {
                SlideYChartPoint point = line.getPoints().get(j);

                if (point.getX() > maxX) {
                    maxX = point.getX();
                } else if (point.getX() < minX) {
                    minX = point.getX();
                }

                if (point.getY() > maxY) {
                    maxY = point.getY();
                } else if (point.getY() < minY) {
                    minY = point.getY();
                }
            }
        }
    }

    public ArrayList getData() {
        return data;
    }

6、一些用到的工具方法

// 把坐标值换成图表上的比例
    private SlideYChartPoint coordinateConversion(SlideYChartPoint point) {

        float x = 0;
        float y = 0;
        if (maxX - minX == 0) {
            x = hLength * 0.5f;
        } else {
            x = tableX + (point.getX() - minX) / (maxX - minX) * hLength;
        }
        if (maxY - minY == 0) {
            y = vLength * 0.5f;
        } else {
            y = tableY + vLength - (point.getY() - minY) / (maxY - minY) * vLength;
        }

        return new SlideYChartPoint(x, y);
    }

    // 把坐标从 y 轴的位置转换成实际的值
    private float coordinateConversionY(float y) {
        if (maxY - minY == 0) {

        }

        return (maxY - minY) / vLength * y;
    }

    // 保留两位小数
    private String format2Bit(float number) {

        DecimalFormat decimalFormat = new DecimalFormat("###.00");
        String target = decimalFormat.format(number);
        if (target.startsWith(".")) {
            target = "0" + target;
        }
        return target;
    }

7、必须要调用的重绘代码

 /**
     * 重新设置x轴刻度、数据、标题后必须刷新重绘
     */
    public void fresh() {
        init();
        requestLayout();
        postInvalidate();
    }

8、线的类

public class SlideYLine {

    private ArrayList points = new ArrayList<>();
    private String color;

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void addPoint(SlideYChartPoint point) {
        points.add(point);
    }

    public ArrayList getPoints() {
        return points;
    }
}

9、点的类


public class SlideYChartPoint {

    public SlideYChartPoint(float x, float y) {
        this.x = x;
        this.y = y;
    }

    private float x;
    private float y;

    public void setX(float x) {
        this.x = x;
    }

    public float getX() {
        return x;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getY() {
        return y;
    }
}

10、调用

private void setData() {

        ArrayList data = new ArrayList();

        SlideYLine line1 = new SlideYLine();
        SlideYLine line2 = new SlideYLine();
        SlideYLine line3 = new SlideYLine();

        line1.setColor("#0000ff");
        line2.setColor("#5f9ea0");
        line3.setColor("#ff8c00");


        for (int i = 0; i < 10; i++) {
            line1.addPoint(new SlideYChartPoint(i, i * 10));
        }

        for (int i = 10; i < 20; i++) {
            line2.addPoint(new SlideYChartPoint(i, i * 10));
        }

        for (int i = 20; i < 30; i++) {
            line3.addPoint(new SlideYChartPoint(i, i * 10));
        }


        data.add(line1);
        data.add(line2);
        data.add(line3);
        mChartView1.setData(data);
        mChartView1.fresh();
    }

最后是代码地址:
https://pan.baidu.com/s/1eUgwlDC 密码:6666
就不放github了。

你可能感兴趣的:(手把手教你写一个可以上下滑动点改变值的安卓折线图(iOS同理,iOS看不懂联系我出个iOS版- -、最好别联系))