安卓可上下滑动改变数值的折线图(基于hellochart)

需求

接手的公司的一个项目,有一个需求是折线图可以通过上下滑动改变数值。原先的大佬自己从头写的,也能实现功能。大佬后来也把思路和代码分享出来了。大家有兴趣的可以看一下。
手把手教你写一个可以上下滑动点改变值的安卓折线图
因为后来需求有些变动,原来的代码改动起来略显吃力,于是自己又以hellocharts为基础重新封装了一个自定义VIew,以此来实现功能。使用起来也更简洁一些。

需求明确:

  • 多条折线图切换滑动
  • 手动上下滑动改变数值

实现效果图

滑动折线图

分析

现在最好用的图表库大概就是hellochart了。但是hellochart并没有提供滑动改变数值的接口以及相关内容。因此我们在hellochart的基础上进行功能扩展,通过自定义控件来实现如图效果。
首先需要确定用户点击的是哪一条线,哪一个点。然后对对应的数据进行操作。重新进行赋值改变图表显示。

实现

尺寸计算

首先计算控件宽高,因为要滑动,不宜进行1:1的计算,滑动难度高。因此乘以0.75,以方便响应用户手势操作。

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

hLength = width * 0.75f;          // x轴长度
vLength = height * 0.75f;         // y轴长度

触摸 - 按下响应事件

lineChartData = vccharts.this.getLineChartData();

                for (int i = 0; i < lineChartData.getLines().size(); i++) {
                    Line line = lineChartData.getLines().get(i);
                    int pointRadius = ChartUtils.dp2px(density, line.getPointRadius());
                    for (int j = 0; j < line.getValues().size(); j++) {
                        PointValue pointValue = line.getValues().get(j);
                        final float rawValueX = vccharts.this.getChartComputator().computeRawX(pointValue.getX());
                        final float rawValueY = vccharts.this.getChartComputator().computeRawY(pointValue.getY());
                        if (isInArea(rawValueX, rawValueY, touchX, touchY, pointRadius + touchToleranceMargin)) {
                            LogUtils.e("     xuanzhong   xxx " + pointValue.getX() + "        yyy   " + pointValue.getY() + "           ");
                            vccharts.this.lineIndex = i;
                            vccharts.this.pointIndex = j;
                            isMoveChange = true;
                            for (Line line1 : lineChartData.getLines()) {
                                line1.setStrokeWidth(2);
                            }
                            line.setStrokeWidth(4);
                            selectLineNumber = i;
                        }
                    }
                }

触摸 - 移动响应事件

移动的时候需要判断位移以及差值,重新计算比例,确定新数值,进而更新数据,刷新View显示。

                    float y_max = 10;
                    float y_min = 0;
//                    如果没有选中任何点
                    if (pointIndex == -1 || lineIndex == -1) {
                        return super.onTouchEvent(event);
                    }
                    lineChartData = vccharts.this.getLineChartData();
                    float y = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).getY();
                    float x = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).getX();
                    float y_new = y_max - coordinateConversionY(touchY);

数值保护,防止超过数据边界

    if (y_new < y_pre) {
           y_new = y_pre;
     }
    if (y_new > y_after) {
           y_new = y_after;
    }
    if (y_new < y_min) {
            y_new = y_min;
    }
    if (y_new > y_max) {
           y_new = y_max;
    }

重新设置数据,通知数据变化

lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).set(x, y_new);
// 通知数据变化
 postInvalidate();

触摸 - 抬起事件

抬起的时候,取消选择的线条的状态,并且计算最终数据,进行显示

if (vccharts.this.pointIndex == -1 || vccharts.this.lineIndex == -1) {
                        isMoveChange = false;
                        return true;
                    }

                    //                    通知数据变化
                    List<PointValue> values = lineChartData.getLines().get(lineIndex).getValues();
                    float[] changeData = new float[values.size()];
                    for (int i = 0; i < values.size(); i++) {
                        changeData[i] = values.get(i).getY();
                    }

                    try {
                        if (vcListener != null) {
                            vcListener.DataChange(lineIndex, new ZuniData(changeData));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    postInvalidate();

                    isMoveChange = false;
                    vccharts.this.pointIndex = -1;
                    vccharts.this.lineIndex = -1;
                    return true;

判断是否点击对应点

    private boolean isInArea(float x, float y, float touchX, float touchY, float radius) {
        float diffX = touchX - x;
        float diffY = touchY - y;
        return Math.pow(diffX, 2) + Math.pow(diffY, 2) <= 2 * Math.pow(radius, 2);
    }

完整代码:

public class vccharts extends LineChartView {
    private static final int DEFAULT_TOUCH_TOLERANCE_MARGIN_DP = 4;
    private final ChartTouchHandler0 touchHandler0;
    boolean isMoveChange = false; // 是否需要根据移动改变
    boolean isBetterPreAfter = true; // 是否需要比较前后
    int pointIndex;
    int lineIndex;
    private LineChartData lineChartData;
    private float vLength;// 竖线长度
    private float hLength;// 横线长度
    private float density;
    private int touchToleranceMargin;
    private int moveNumber = 0;
    private int selectLineNumber = -1; // 现在选中的线条
    private vcDataChangeListener vcListener;


    public vccharts(Context context, AttributeSet attrs) {
        super(context, attrs);

        try {
            density = context.getResources().getDisplayMetrics().density;
            touchToleranceMargin = ChartUtils.dp2px(density, DEFAULT_TOUCH_TOLERANCE_MARGIN_DP);
            LogUtils.e("  touchToleranceMargin  " + touchToleranceMargin);
        } catch (Exception e) {
            e.printStackTrace();
        }
        touchHandler0 = new ChartTouchHandler0(context, this);
        vccharts.this.post(new Runnable() {

            @Override
            public void run() {
                //在此处调用view.getMeasuredWidth()和getMeasuredHeight()可得到正确值
                int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
                int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

                hLength = width * 0.75f;          // x轴长度
                vLength = height * 0.75f;         // y轴长度

                LogUtils.e("  vLength " + vLength + "  h " + hLength);
            }
        });
    }

    public void setVcListener(vcDataChangeListener vcListener) {
        this.vcListener = vcListener;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float touchY = event.getY();
        float touchX = event.getX();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e("                 按下了  ");

                moveNumber = 0;
                lineChartData = vccharts.this.getLineChartData();

                for (int i = 0; i < lineChartData.getLines().size(); i++) {
                    Line line = lineChartData.getLines().get(i);
                    int pointRadius = ChartUtils.dp2px(density, line.getPointRadius());
                    for (int j = 0; j < line.getValues().size(); j++) {
                        PointValue pointValue = line.getValues().get(j);
                        final float rawValueX = vccharts.this.getChartComputator().computeRawX(pointValue.getX());
                        final float rawValueY = vccharts.this.getChartComputator().computeRawY(pointValue.getY());
                        if (isInArea(rawValueX, rawValueY, touchX, touchY, pointRadius + touchToleranceMargin)) {
                            LogUtils.e("     xuanzhong   xxx " + pointValue.getX() + "        yyy   " + pointValue.getY() + "           ");
                            vccharts.this.lineIndex = i;
                            vccharts.this.pointIndex = j;
                            isMoveChange = true;
                            for (Line line1 : lineChartData.getLines()) {
                                line1.setStrokeWidth(2);
                            }
                            line.setStrokeWidth(4);
                            selectLineNumber = i;
                        }
                    }
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                if (isMoveChange) {
                    float y_max = 10;
                    float y_min = 0;
//                    如果没有选中任何点
                    if (pointIndex == -1 || lineIndex == -1) {
                        return super.onTouchEvent(event);
                    }
                    lineChartData = vccharts.this.getLineChartData();
                    float y = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).getY();
                    float x = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).getX();
                    float y_new = y_max - coordinateConversionY(touchY);
                    if (isBetterPreAfter) {
                        float y_pre = y_min;
                        float y_after = y_max;
                        if (pointIndex < lineChartData.getLines().get(lineIndex).getValues().size() - 1) {
                            y_after = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex + 1).getY();
                        }
                        if (pointIndex > 0) {
                            y_pre = lineChartData.getLines().get(lineIndex).getValues().get(pointIndex - 1).getY();
                        }
                        if (y_new < y_pre) {
                            y_new = y_pre;
                        }
                        if (y_new > y_after) {
                            y_new = y_after;
                        }
                    }

                    if (y_new < y_min) {
                        y_new = y_min;
                    }
                    if (y_new > y_max) {
                        y_new = y_max;
                    }
                    lineChartData.getLines().get(lineIndex).getValues().get(pointIndex).set(x, y_new);
//                    通知数据变化
                    /*List<PointValue> values = lineChartData.getLines().get(lineIndex).getValues();
                    float[] changeData = new float[values.size()];
                    for (int i = 0; i < values.size(); i++) {
                        changeData[i] = values.get(i).getY();
                    }*/
                    postInvalidate();
                    /*if (vcListener != null) {
                        moveNumber++;
                        if (moveNumber % 3 == 0) {
                            vcListener.DataChange(lineIndex, new ZuniData(changeData));
                        }
                    }*/
                }
                return super.onTouchEvent(event);
            case MotionEvent.ACTION_UP:
                LogUtils.e("  move " + moveNumber);
                if (isMoveChange) {
                    if (vccharts.this.pointIndex == -1 || vccharts.this.lineIndex == -1) {
                        isMoveChange = false;
                        return true;
                    }

                    //                    通知数据变化
                    List<PointValue> values = lineChartData.getLines().get(lineIndex).getValues();
                    float[] changeData = new float[values.size()];
                    for (int i = 0; i < values.size(); i++) {
                        changeData[i] = values.get(i).getY();
                    }

                    try {
                        if (vcListener != null) {
                            vcListener.DataChange(lineIndex, new ZuniData(changeData));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    postInvalidate();

                    isMoveChange = false;
                    vccharts.this.pointIndex = -1;
                    vccharts.this.lineIndex = -1;
                    return true;
                }
                return true;
            case MotionEvent.ACTION_CANCEL:
                if (isMoveChange) {
                    isMoveChange = false;
                    vccharts.this.pointIndex = -1;
                    vccharts.this.lineIndex = -1;
                    return true;
                }
                return true;
        }
        return super.onTouchEvent(event);
    }

    // 把坐标从 y 轴的位置转换成实际的值
    private float coordinateConversionY(float y) {
        LogUtils.i("坐标从 y 轴的位置转换成实际的值" + (10 - 0) / vLength * y);
        return (10 - 0) / vLength * y;
    }

    // 把坐标从 x 轴的位置转换成实际的值
    private float coordinateConversionX(float x) {
        LogUtils.i("坐标从 x 轴的位置转换成实际的值" + (11 - 0) / hLength * x);
        return (11 - 0) / hLength * x;
    }

    private boolean isInArea(float x, float y, float touchX, float touchY, float radius) {
        float diffX = touchX - x;
        float diffY = touchY - y;
        return Math.pow(diffX, 2) + Math.pow(diffY, 2) <= 2 * Math.pow(radius, 2);
    }

    public int getSelectLineNumber() {
        return selectLineNumber;
    }

    public void setSelectLineNumber(int selectLineNumber) {
        this.selectLineNumber = selectLineNumber;
    }

    public boolean isBetterPreAfter() {
        return isBetterPreAfter;
    }

    public void setBetterPreAfter(boolean betterPreAfter) {
        isBetterPreAfter = betterPreAfter;
    }

    public interface vcDataChangeListener {
        void DataChange(int line_index, ZuniData ZuniData);
    }
}
package com.example.linchartdemo.view; import java.util.List; import android.R.color; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class ZXView extends View { private List<Integer> xlist;// X坐标标签 private List<Integer> ylist;// Y坐标标签 private List<Integer> params;// 参数集合 private Paint paint; private Paint paintLines; private Paint paintArc; private Paint paintText; private int textsize = 20; private float Xoffset = 0;// X轴偏移或叫间隔 private float Yoffset = 0;// Y轴偏移或叫间隔 public float XSpac = 50; public float Xspac = 50; public float YSpac = 50; public float rightXspac = 50; public float rightYspac = 50; public float circleRadius = 10; private float lastX = -1; private float lastY = -1; public ZXView(Context context, List<Integer> xlists, List<Integer> ylists, List<Integer> paramss) { super(context); xlist = xlists; ylist = ylists; params = paramss; initWidget(); } public ZXView(Context context, AttributeSet attrs) { super(context, attrs); initWidget(); } public ZXView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initWidget(); } public void initViewData(List<Integer> xlists, List<Integer> ylists, List<Integer> paramss) { xlist = xlists; ylist = ylists; params = paramss; } @Override protected void onDraw(Canvas canvas) { drawXY(canvas); drawLine(canvas); super.onDraw(canvas); } public void updataUI() { invalidate(); } /** * 画折线 * * @param canvas */ private void drawLine(Canvas canvas) { Xoffset = ((getWidth()) - 20) / (xlist.size()); Yoffset = (getHeight()) / (ylist.size()); Log.i("texts", "X=" + Xoffset + "Y=" + Yoffset); float rulerOffset = 0; if (xlist.size() < 2) throw new IllegalArgumentException("the params argument is <2"); else rulerOffset = (xlist.get(1) - xlist.get(0)); if (params == null && params.size() <= 0) throw new IllegalArgumentException("the params is null or 0"); // Paint p = new Paint(); // p.setAntiAlias(true); // p.setTextSize(25); // p.setColor(Color.WHITE); // canvas.drawText("KW", 10 + Xoffset, 20, p); for (int i = 0; i < params.size(); i++) { int param = params.get(i); float histigramHight = param * (Yoffset / rulerOffset); float currentX = (Xoffset * i + Xspac + 5); float currentY = (getHeight() - YSpac - histigramHight); canvas.drawCircle(currentX, currentY, circleRadius, paintArc); if (lastX != -1) { canvas.drawLine(lastX, lastY, currentX, currentY, paintLines); } lastX = currentX; lastY = currentY; } // 重置lastX跟Y lastX = -1; lastY = -1; } /** * 画坐标轴 * * @param canvas */ private void drawXY(Canvas canvas) { canvas.drawLine(XSpac, 0, XSpac, getHeight() - 5 - XSpac, paint);// x canvas.drawLine(XSpac, getHeight() - XSpac, getWidth(), getHeight() - XSpac, paint);// y canvas.drawLine(getWidth(), 0, getWidth() - 1, getWidth() - YSpac, paint);// 右边Y float yoffset = getHeight() / ylist.size(); float xoffset = (getWidth() - XSpac) / xlist.size(); // 画字 for (int i = 0; i < ylist.size(); i++) { canvas.drawText(ylist.get(i) + "", 0, getHeight() - yoffset * i - YSpac, paintText); } for (int i = 0; i < xlist.size(); i++) { canvas.drawText(xlist.get(i) + "", XSpac + xoffset * i, getHeight() - YSpac + textsize, paintText); } // for (int i = 0; i < ylist.size(); i++) { // for (int j = 0; j < 50; j++) { // canvas.drawLine(XSpac + j * 303, yoffset * i - YSpac, XSpac + j // * 30 + 20, yoffset * i - YSpac, paint); // } // } } /** * 初始化画笔 */ private void initWidget() { paint = new Paint(); paint.setColor(Color.parseColor("#999999")); paint.setTypeface(Typeface.DEFAULT); paint.setAntiAlias(true); paint.setStrokeWidth(2); paintLines = new Paint(); paintLines.setColor(Color.parseColor("#00B4EA")); paintLines.setTypeface(Typeface.DEFAULT); paintLines.setAntiAlias(true); paintLines.setStrokeWidth(3); paintArc = new Paint(); paintArc.setColor(Color.parseColor("#EBEEEF")); paintArc.setTypeface(Typeface.DEFAULT); paintArc.setAntiAlias(true); paintArc.setStrokeWidth(2); paintText = new Paint(); // paintText.setColor(Color.parseColor("#ffffff")); paintText.setTypeface(Typeface.DEFAULT); paintText.setAntiAlias(true); paintText.setStrokeWidth(2); paintText.setTextSize(textsize); } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值