自定义图表控件--同时显示柱状图和折线图

public class MyChartView extends View {
    private static final int LEFT = 1;//在通用函数中用于区别左边y轴和右边y轴
    private static final int RIGHT = 2;
    private Paint mPaint;
    private int xNums;//x轴坐标分割点个数
    private int yNums;//y轴坐标分割点个数
    private String[] xTexts;//x坐标字段
    private String[] yBarTexts;//y坐标字段
    private String[] yLineTexts;//y坐标字段
    private double[] yBarUnits;//y坐标值
    private double[] yLineUnits;//y坐标值
    private int xStep;//x轴上每一个字段距离
    private int yStep;//y轴上每一个虚线距离
    private int yBarStepVal;//y轴字段间距值
    private int yLineStepVal;//y轴字段间距值
    private int width;//获取控件宽度
    private int height;//获取控件高度
    private boolean isBarPercent = false;//检测yStepVal是不是百分数
    private boolean isLinePercent = false;//检测yStepVal是不是百分数
    private int canDrawMaxNum = 0;//根据屏幕宽度设置x轴最多可显示的字段数
    private float xOffset = 0;//当前的位置
    private float startX;//手指按下位置
    private int MAX_OFFSET;//移动范围
    private float xOffsetThisTime;//现在要移动的值
    private boolean isFirstInitDraw = true;//是否第一次画图,使图表初始化在最后数据的位置
    private boolean isMoveable = false;//检查是不是需要滑动

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        initDraw();
        drawAxis(canvas);
        drawYAxisText(canvas);
        drawHLines(canvas);
        Log.d("xOffset = ", xOffset + "");

        if (isMoveable) {
            //移动绘制画柱状图和折线图区域,移动绘制x轴文字区域
            canvas.save();
            canvas.clipRect(dp2px(30) + dp2px(3), dp2px(30) + dp2px(1), width - dp2px(30) - dp2px(3), height + dp2px(30), Region.Op.REPLACE);
            canvas.translate(xOffset, 0);
            drawBarChart(canvas);
            drawXAxisText(canvas);
            drawLineChart(canvas);
            canvas.restore();
        } else {
            //不用移动时候直接绘制内容
            drawBarChart(canvas);
            drawXAxisText(canvas);
            drawLineChart(canvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isMoveable) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startX = (int) event.getRawX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float stopX = (int) event.getRawX();
                    xOffsetThisTime = stopX - startX;
                    xOffset += xOffsetThisTime;
                    startX = stopX;

                    invalidate();
                    isFirstInitDraw = false;
                    break;
                case MotionEvent.ACTION_UP:
                    //smooth scroll
                    new Thread(new SmoothScrollThread(xOffsetThisTime)).start();
                    break;
            }
            return true;
        } else {
            return false;
        }

    }

    /**
     * 初始化onDraw函数中需要的一些变量
     */
    private void initDraw() {
        mPaint = new Paint();
        width = getWidth();
        height = getHeight() - dp2px(30);
        canDrawMaxNum = (width - dp2px(60)) / dp2px(30);
        Log.d("canDrawMaxNum", canDrawMaxNum + "");
        //设置y坐标字段,尽量以整数来分割
        setYTexts(yBarUnits, LEFT);
        setYTexts(yLineUnits, RIGHT);
        for (int i = 0; i < yLineUnits.length; i++) {
            Log.d("yLineUnits" + i + " == ", "" + yLineUnits[i]);
        }
        //如果字段数比canDrawMaxNum小,则将字段加宽以适应屏幕
        if (yBarTexts.length - 1 < canDrawMaxNum) {
            xStep = (width - dp2px(60)) / (yBarTexts.length - 1);
        } else {
            xStep = (width - dp2px(60)) / canDrawMaxNum;
        }
        yStep = (height - dp2px(30)) / (yBarTexts.length - 1);

        MAX_OFFSET = (dp2px(30) + xStep / 2 * (1 + 2 * (xTexts.length - 1)) + dp2px(10) + dp2px(10)) -
                (dp2px(30) + xStep / 2 - dp2px(10)) - (width - dp2px(60));
        if (isFirstInitDraw) {
            xOffset = -MAX_OFFSET;
        }
    }

    /**
     * 画坐标轴
     *
     * @param canvas
     */
    private void drawAxis(Canvas canvas) {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(1);

        //画x轴
        canvas.drawLine(dp2px(30), height, width - dp2px(30), height, mPaint);

        //画左边y轴
        canvas.drawLine(dp2px(30), height, dp2px(30), dp2px(10), mPaint);
        //画左边y轴箭头
        canvas.drawLine(dp2px(30), dp2px(10), dp2px(27), dp2px(13), mPaint);
        canvas.drawLine(dp2px(30), dp2px(10), dp2px(33), dp2px(13), mPaint);

        //画右边y轴
        canvas.drawLine(width - dp2px(30), height, width - dp2px(30), dp2px(10), mPaint);
        //画右边y轴箭头
        canvas.drawLine(width - dp2px(30), dp2px(10), width - dp2px(27), dp2px(13), mPaint);
        canvas.drawLine(width - dp2px(30), dp2px(10), width - dp2px(33), dp2px(13), mPaint);
    }

    /**
     * 画x轴坐标点处的文字
     *
     * @param canvas
     */
    private void drawXAxisText(Canvas canvas) {

        Log.d("yStep text = ", yStep + "");
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setTextSize(sp2px(8));
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(2);
        for (int i = 0; i < xTexts.length; i++) {
            canvas.drawText(xTexts[i], dp2px(30) + xStep / 2 * (1 + 2 * i) - dp2px(10), height + dp2px(10), mPaint);
        }
    }

    /**
     * 画y轴坐标点处的文字
     *
     * @param canvas
     */
    private void drawYAxisText(Canvas canvas) {

        Log.d("yStep text = ", yStep + "");
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setTextSize(sp2px(8));
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(2);

        for (int i = 0; i < yBarTexts.length; i++) {
            canvas.drawText(yBarTexts[i], dp2px(10), height - (i) * yStep + dp2px(3), mPaint);
        }
        for (int i = 0; i < yLineTexts.length; i++) {
            canvas.drawText(yLineTexts[i], width - dp2px(30) + dp2px(10), height - (i) * yStep + dp2px(3), mPaint);
        }
    }

    /**
     * 画水平线
     *
     * @param canvas
     */
    private void drawHLines(Canvas canvas) {
        Log.d("yStep  Line= ", yStep + "");
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStrokeWidth(1);
        for (int i = 0; i < yBarTexts.length - 1; i++) {
            canvas.drawLine(dp2px(30), height - (i + 1) * yStep, width - dp2px(30), height - (i + 1) * yStep, mPaint);
        }
    }

    /**
     * 画柱状图
     *
     * @param canvas
     */
    private void drawBarChart(Canvas canvas) {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.CYAN);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(0);

        for (int i = 0; i < yNums; i++) {
            float drawHeight;
            if (!isBarPercent) {
                if (!yBarTexts[0].equals("0")) {
                    drawHeight = (float) (height - yStep * ((yBarUnits[i] - yBarStepVal) / yBarStepVal));
                } else {
                    drawHeight = (float) (height - yStep * (yBarUnits[i] / yBarStepVal));
                }
            } else {
                if (!yBarTexts[0].equals("0")) {
                    drawHeight = (float) (height - (float) yStep * ((yBarUnits[i] * 100 - yBarStepVal) / yBarStepVal));
                } else {
                    drawHeight = (float) (height - (float) yStep * (yBarUnits[i] * 100 / yBarStepVal));
                }
            }
            canvas.drawRect(dp2px(30) + xStep / 2 * (1 + 2 * i) - dp2px(10), drawHeight, dp2px(30) + xStep / 2 * (1 + 2 * i) + dp2px(10), height, mPaint);
        }
    }

    /**
     * 画折线图
     *
     * @param canvas
     */
    private void drawLineChart(Canvas canvas) {
        mPaint.setAntiAlias(true);

        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(0);
        float x = 0, y = 0;
        for (int i = 0; i < yNums; i++) {
            float drawHeight;
            if (!isLinePercent) {
                if (!yLineTexts[0].equals("0")) {
                    drawHeight = (float) (height - yStep * ((yLineUnits[i] - yLineStepVal) / yLineStepVal));
                } else {
                    drawHeight = (float) (height - yStep * (yLineUnits[i] / yLineStepVal));
                }
            } else {
                if (!yLineTexts[0].equals("0")) {
                    drawHeight = (float) (height - (float) yStep * ((yLineUnits[i] * 100 - yLineStepVal) / yLineStepVal));
                } else {
                    drawHeight = (float) (height - (float) yStep * (yLineUnits[i] * 100 / yLineStepVal));
                }
            }
            mPaint.setColor(Color.MAGENTA);
            canvas.drawCircle(dp2px(30) + xStep / 2 * (1 + 2 * i), drawHeight, 5, mPaint);
            if (i > 0) {
                mPaint.setColor(Color.BLUE);
                canvas.drawLine(x, y, dp2px(30) + xStep / 2 * (1 + 2 * i), drawHeight, mPaint);
            }
            x = dp2px(30) + xStep / 2 * (1 + 2 * i);
            y = drawHeight;
//            if (i>0){
//                canvas.drawLine();
//            }
        }
    }

    /**
     * 坐标转换,从dp转换为px
     *
     * @param value
     * @return
     */
    private int dp2px(int value) {
        float v = getContext().getResources().getDisplayMetrics().density;
        return (int) (v * value + 0.5f);
    }

    /**
     * 坐标转换,从sp转换为px
     *
     * @param value
     * @return
     */
    private int sp2px(int value) {
        float v = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (v * value + 0.5f);
    }

    /**
     * 从服务器或其他地方传入数据,由此函数适配
     *
     * @param object 传入数据以Json格式传入,Json格式如下:
     *               {
     *               "chart":{
     *               "xunits":["xUnit1","xUnit2",...],
     *               "ybarunits":["yUnit1","yUnit2",...],
     *               "ylineunits":["yUnit1","yUnit2",...]
     *               }
     */
    public void setData(JSONObject object) {
        try {
            //解析Json数据
            JSONObject result = object.getJSONObject("chart");
            JSONArray xArray = result.getJSONArray("xunits");
            JSONArray yBarArray = result.getJSONArray("ybarunits");
            JSONArray yLineArray = result.getJSONArray("ylineunits");
            Log.d("barArray = ", yBarArray.toString());
            Log.d("lineArray = ", yLineArray.toString());

            //部署数据
            xNums = yNums = xArray.length();
            xTexts = new String[xNums];

            xTexts = parseJsonArray2StringArray(xArray);
            yBarUnits = new double[yNums];
            yLineUnits = new double[yNums];
            String[] tempBar = parseJsonArray2StringArray(yBarArray);
            String[] tempLine = parseJsonArray2StringArray(yLineArray);
            for (int i = 0; i < yNums; i++) {
                yBarUnits[i] = Double.parseDouble(tempBar[i]);
                yLineUnits[i] = Double.parseDouble(tempLine[i]);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从服务器或其他地方传入数据,由此函数适配
     *
     * @param xUnits     x轴数据,传入字段名称数组
     * @param yBarUnits  y轴数据,传入柱状图字段值数组
     * @param yLineUnits y轴数据,传入折线图字段值数组
     */
    public void setData(String[] xUnits, String[] yBarUnits, String[] yLineUnits) {
        //部署数据
        xNums = yNums = xUnits.length;
        xTexts = new String[xNums];

        xTexts = xUnits;
        this.yBarUnits = new double[yNums];
        this.yLineUnits = new double[yNums];
        for (int i = 0; i < yNums; i++) {
            this.yBarUnits[i] = Double.parseDouble(yBarUnits[i]);
            this.yLineUnits[i] = Double.parseDouble(yLineUnits[i]);
        }
    }

    /**
     * 设置y坐标字段,尽量以整数来分割
     * y坐标字段最多设置为x轴每个柱状图之间有间隔
     *
     * @param yUnits
     * @param which  控制是左边还是右边
     */
    private void setYTexts(double[] yUnits, int which) {
        String[] yTempTexts;
        int yTempStepVal;
        boolean isTempPercent;
        if (yUnits.length > canDrawMaxNum) {
            yTempTexts = new String[canDrawMaxNum + 1];
            isMoveable = true;
        } else {
            yTempTexts = new String[yUnits.length + 1];
            isMoveable = false;
        }
        Log.d("yTempTexts.length = ", yTempTexts.length + "");

        //获取yUnits的最大值和最小值
        double max = 0;
        double min = yUnits[0];
        for (int i = 0; i < yUnits.length; i++) {
            if (max < yUnits[i]) {
                max = yUnits[i];
            }
            if (min > yUnits[i]) {
                min = yUnits[i];
            }
        }
        Log.d("max, min ", max + ", " + min);
        //将最大值与最小值的差等分成yUnits.length份,并取相近的整数代替
        int times = yUnits.length > canDrawMaxNum ? yUnits.length / canDrawMaxNum : 1;
        yTempStepVal = (int) (((max - min) / yUnits.length + 1) * times);
        //判断yTempStepVal是不是小于最小值
        while (yTempStepVal * yTempTexts.length < max) {
            yTempStepVal++;
        }
        if (yTempStepVal < min) {
            //可以显示出最小值,不需要再处理了
            if (yTempStepVal >= 2) {
                isTempPercent = false;
                Log.d("yTempStepVal = ", yTempStepVal + "");
                for (int i = 0; i < yTempTexts.length; i++) {
                    yTempTexts[i] = String.valueOf(yTempStepVal * (i + 1));
                }
            } else {
                isTempPercent = true;
                yTempStepVal = (int) (((max - min) / yUnits.length) * 100 + 1);
                Log.d("yTempStepValP = ", yTempStepVal + "");
                for (int i = 0; i < yTempTexts.length; i++) {
                    yTempTexts[i] = yTempStepVal * (i + 1) + "%";
                }
            }
            Log.d("isTempPercent = ", isTempPercent + "");
        } else {
            //无法显示最小值,则起点从0开始,再重新调整yTempStepVal的值
            while (yTempStepVal * (yTempTexts.length - 1) < max) {
                yTempStepVal++;
            }

            if (yTempStepVal >= 2) {
                isTempPercent = false;
                Log.d("yTempStepVal = ", yTempStepVal + "");
                yTempTexts[0] = String.valueOf(0);
                for (int i = 1; i < yTempTexts.length; i++) {
                    yTempTexts[i] = String.valueOf(yTempStepVal * i);
                }
            } else {
                isTempPercent = true;
                yTempStepVal = (int) (((max - min) / yUnits.length) * 100 + 1);
                Log.d("yTempStepValP = ", yTempStepVal + "");
                yTempTexts[0] = String.valueOf(0);
                for (int i = 1; i < yTempTexts.length; i++) {
                    yTempTexts[i] = yTempStepVal * (i + 1) + "%";
                }
            }
            Log.d("isTempPercent = ", isTempPercent + "");
        }

        if (which == LEFT) {
            //左边
            yBarTexts = yTempTexts;
            yBarStepVal = yTempStepVal;
            isBarPercent = isTempPercent;
        }
        if (which == RIGHT) {
            //右边
            yLineTexts = yTempTexts;
            yLineStepVal = yTempStepVal;
            isLinePercent = isTempPercent;
        }
    }

    /**
     * 将Json数组转换成字符串数组以方便操作
     *
     * @param jsonArray
     * @return
     * @throws JSONException
     */
    private String[] parseJsonArray2StringArray(JSONArray jsonArray) throws JSONException {
        String[] array = new String[jsonArray.length()];
        for (int i = 0; i < jsonArray.length(); i++) {
            array[i] = jsonArray.getString(i);
        }
        return array;
    }

    /**
     * 自定义线程,用以控制图表移动速度和移动逻辑
     */
    private class SmoothScrollThread implements Runnable {
        float xOffsetThisTime;
        boolean scrolling = true;

        public SmoothScrollThread(float xOffsetThisTime) {
            this.xOffsetThisTime = xOffsetThisTime;
            scrolling = true;
        }

        @Override
        public void run() {
            while (scrolling) {
                long start = System.currentTimeMillis();
                xOffsetThisTime = (int) (0.9f * xOffsetThisTime);
                xOffset += xOffsetThisTime;

                checkXOffset();
                postInvalidate();
                isFirstInitDraw = false;
                if (Math.abs(xOffsetThisTime) < 20) {
                    scrolling = false;
                }

                long end = System.currentTimeMillis();
                if (end - start < 20) {
                    try {
                        Thread.sleep(20 - (end - start));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 检查移动是否在可移动范围内
     */
    private void checkXOffset() {
        if (xOffset > 0) {
            xOffset = 0;
        }

        if (xOffset < -MAX_OFFSET) {
            xOffset = -MAX_OFFSET;
        }
    }
}


你可能感兴趣的:(canvas,图表,自定义view)