安卓自定义折线图、柱状图

自定义柱状图、折线图、饼状图,先上效果图。
在这里插入图片描述
上面是柱状图和折线图混合图。
需要代码的直接上链接拿走https://github.com/yangxiaohan2014/ChartView

分析图表

  1. X轴分析
    X轴显示日期,并且是可左右滑动的

  2. Y轴分析
    Y轴显示数据,比较简单

  3. 图表分析
    图表分为两部分:折线图和柱状图

自定义View

1.分析ChartView需要的坐标数据
原点坐标ox、oy,
X轴步长Xstep,
X轴第一个坐标值
X轴滑动时需要的两个变量:
X轴第一个坐标的最小值minXInit,
X轴第一个坐标的最大值maxXInit
如下图:
在这里插入图片描述
滑动时需要计算图表拖到最左边的值:
移动到最左边视图

2.初始化需要的变量和自定义属性

 public ChartView(Context context) {
    
    
        super(context);
        init(context);
    }

    public ChartView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        initCustomAttrs(context, attrs);
        init(context);
    }

    public ChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
        initCustomAttrs(context, attrs);
        init(context);
    }

    private void init(Context context) {
    
    
        WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#FF2741"));
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(3);

    }
    private void initCustomAttrs(Context context, AttributeSet attrs) {
    
    
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChartView);
        xStep = typedArray.getDimension(R.styleable.ChartView_x_step, 45);
        yStep = typedArray.getDimension(R.styleable.ChartView_y_step, 35);

        xTextColor = typedArray.getColor(R.styleable.ChartView_x_text_color, Color.BLACK);
        yTextColor = typedArray.getColor(R.styleable.ChartView_y_text_color, Color.BLACK);
        linColor = typedArray.getColor(R.styleable.ChartView_x_line_color, Color.BLUE);
        columnWidth = typedArray.getDimension(R.styleable.ChartView_column_width, 20);
        columnColor = typedArray.getColor(R.styleable.ChartView_column_color, Color.BLUE);
        isColumnGradient = typedArray.getBoolean(R.styleable.ChartView_column_color_gradient, false);
        columnStartColor = typedArray.getColor(R.styleable.ChartView_column_start_color, Color.BLUE);
        columnEndColor = typedArray.getColor(R.styleable.ChartView_column_end_color, Color.BLUE);
        columnTextColor = typedArray.getColor(R.styleable.ChartView_column_text_color, Color.BLUE);
        startX = (int) typedArray.getDimension(R.styleable.ChartView_x_padding, DensityUtil.dp2px(10));
        startY = (int) typedArray.getDimension(R.styleable.ChartView_y_padding, DensityUtil.dp2px(10));

        linePointColor = typedArray.getColor(R.styleable.ChartView_line_point_color, Color.BLUE);
        lineChartColor = typedArray.getColor(R.styleable.ChartView_line_chart_color, Color.BLUE);

        lineTextColor = typedArray.getColor(R.styleable.ChartView_line_text_color, Color.BLUE);
        isChartDataVisible = typedArray.getBoolean(R.styleable.ChartView_is_chart_data_visible, false);
        typedArray.recycle();
    }

3.重写onLayout、onDraw方法
onlayout方法中获取图表的原点O的位置分别为ox、oy,xInit为x轴第一个刻度位置,minXInit为X轴最小值,maxXInit为滑动时X轴的最大值

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    
    
        if (changed) {
    
    
            height = getHeight();
            width = getWidth();
            ox = startX;
            oy = startY + height - paddingBottom;

            xInit = startX + xStep;
            minXInit = width - ox - xStep * (xPoints.size() - 1);
            maxXInit = xInit;
        }

        super.onLayout(changed, left, top, right, bottom);
   
   }

  @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        canvas.drawColor(bgcolor);
        drawOXYPoint(canvas);
        drawYPoints(canvas);
//        drawXPoints(canvas);
        drawColumnsAndLines(canvas);

    }

4.开始绘制
首先绘制原点O和X轴、Y轴

 private void drawOXYPoint(Canvas canvas) {
    
    
        mPaint.setColor(yTextColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(DensityUtil.dp2px(14));
        String o = "0";
        Rect rect = new Rect();
        mPaint.getTextBounds("0", 0, o.length(), rect);
        int bottom = rect.bottom;
        int top = rect.top;
        int left = rect.left;
        int right = rect.right;
        int oWidth = right - left;
        int oHeight = bottom - top;
        canvas.drawText(o, ox - (oWidth + columnTextPadding), oy + oHeight / 2, mPaint);
//        mPaint.setColor(linColor);
//        //横线
//        canvas.drawLine(startX, startY + height - paddingBottom, startX + width, startY + height - paddingBottom, mPaint);
//        //竖线
//        canvas.drawLine(startX, startY, startX, height + startY - paddingBottom, mPaint);

        canvas.drawText(o, startX - (oWidth + columnTextPadding), oy + oHeight / 2, mPaint);
        mPaint.setColor(linColor);
        //横线
        canvas.drawLine(startX, startY + height - paddingBottom, startX + width, startY + height - paddingBottom, mPaint);
        //竖线
        canvas.drawLine(startX, startY, startX, height + startY - paddingBottom, mPaint);
    }

绘制Y轴坐标

  /**
     * 绘制纵坐标
     *
     * @param canvas
     */
    private void drawYPoints(Canvas canvas) {
    
    
        mPaint.setStyle(Paint.Style.FILL);
        for (int i = 0; i < yPoints.length; i++) {
    
    
            mPaint.setColor(linColor);
            float yLength = (i + 1) * yStep;
            float pointY = oy - yLength;
            canvas.drawLine(startX, pointY, startX + pointWidth, pointY, mPaint);
            mPaint.setColor(yTextColor);
            String pointStr = yPoints[i];
            Rect rect = new Rect();
            mPaint.getTextBounds(pointStr, 0, pointStr.length(), rect);
            int width = rect.right - rect.left;
            int height = rect.bottom - rect.top;

            canvas.drawText(pointStr, ox - (width + yTextPadding), pointY + height / 2, mPaint);

        }

    }

绘制折线图和柱状图

private void drawColumnsAndLines(Canvas canvas) {
    
    
        //??
        int layerId = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
        drawColumns(canvas);
        drawLines(canvas);

        // 将折线超出x轴坐标的部分截取掉
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.BLUE);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        RectF rectF = new RectF(0, 0, ox, height);
        canvas.drawRect(rectF, mPaint);
        mPaint.setXfermode(null);

        //保存图层
        canvas.restoreToCount(layerId);
    }
 /**
     * 绘制柱状图
     *
     * @param canvas
     */
    private void drawColumns(Canvas canvas) {
    
    
        for (int i = 0; i < xPoints.size(); i++) {
    
    
            float xLength = i * xStep;
            float pointX = xInit + xLength;
            float centerX = pointX;
            float columnHeight = yStep * xPoints.get(i).getxDecimal1() / stepYNumber;

            Shader shader = new LinearGradient(centerX, oy, centerX, oy - columnHeight, columnStartColor,
                    columnEndColor, Shader.TileMode.CLAMP);

            if (isColumnGradient) {
    
    
                mPaint.setShader(shader);
            } else {
    
    
                mPaint.setColor(columnColor);
            }

            //画柱形图
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    
    
                canvas.drawRoundRect(centerX - columnWidth / 2, oy - columnHeight, centerX + columnWidth / 2, oy, 0, 0, mPaint);
            } else {
    
    
                canvas.drawRect(centerX - columnWidth / 2, oy - columnHeight, centerX + columnWidth / 2, oy, mPaint);
            }
            mPaint.setShader(null);

            if (xPoints.get(i).isDataVisible) {
    
    
                //绘制柱状图数据
                String pointNum = xPoints.get(i).getxDecimal1() + "";
                Rect rectNum = new Rect();
                mPaint.getTextBounds(pointNum, 0, pointNum.length(), rectNum);
                int numRectHeight = rectNum.bottom - rectNum.top;
                int numRectWidth = rectNum.right - rectNum.left;
                mPaint.setColor(columnTextColor);
                canvas.drawText(pointNum, centerX - numRectWidth / 2, oy - columnHeight - columnTextPadding, mPaint);
                mPaint.setColor(selectedXTitleColor);
            }

            //绘制横坐标标题
            mPaint.setColor(xTextColor);
            String title = xPoints.get(i).getTitle();
            Rect rect = new Rect();
            mPaint.getTextBounds(title, 0, title.length(), rect);
            int rectHeight = rect.bottom - rect.top;
            int rectWidth = rect.right - rect.left;
            canvas.drawText(title, centerX - rectWidth / 2, oy + rectHeight + columnTextPadding, mPaint);
        }

    }

绘制折线图:
为了使折线看起来顺滑流畅,使用了二次贝塞尔曲线来绘制

 /**
     * 绘制折线图
     *
     * @param canvas
     */
    private void drawLines(Canvas canvas) {
    
    
        if (xPoints.size() == 0) {
    
    
            return;
        }
        Point[] mPoints = new Point[xPoints.size()];
//        float[] pointsy = new float[xPoints.size()];
        for (int i = 0; i < xPoints.size(); i++) {
    
    

            float xLength = i * xStep;
            float pointX = xInit + xLength;
            //计算圆圈坐标
            float pointY = yStep * xPoints.get(i).getxDecimal2() / stepYNumber;
            mPaint.setColor(linePointColor);

            mPaint.setStyle(Paint.Style.FILL);

            //绘制圆圈
            canvas.drawCircle(pointX, oy - pointY, DensityUtil.dp2px(3), mPaint);
            //绘制圆圈上的数值
            if (xPoints.get(i).isDataVisible) {
    
    
                String pointNum = xPoints.get(i).getxDecimal2() + "";
                Rect rectNum = new Rect();
                mPaint.getTextBounds(pointNum, 0, pointNum.length(), rectNum);
                int numRectWidth = rectNum.right - rectNum.left;
                mPaint.setColor(lineTextColor);
                canvas.drawText(pointNum, pointX - numRectWidth / 2, oy - pointY - columnTextPadding, mPaint);
            }


            //绘制贝塞尔曲线
            Point point = new Point((int) (0.5f + pointX), (int) (0.5f + oy - pointY));
            mPoints[i] = point;
            if (i > 0 && i <= xPoints.size() - 1) {
    
    
                mPaint.setStyle(Paint.Style.STROKE);
                Point startp = mPoints[i - 1];
                Point endp = mPoints[i];
                int wt = (startp.x + endp.x) / 2;
                Point p3 = new Point();
                Point p4 = new Point();
                p3.y = startp.y;
                p3.x = wt;
                p4.y = endp.y;
                p4.x = wt;

                Path path = new Path();
                path.moveTo(startp.x, startp.y);
                //二次贝塞尔曲线
//                path.quadTo(p3.x,p3.y,endp.x,endp.y);
                //绘制三次贝塞尔曲线
                path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);

                canvas.drawPath(path, mPaint);
            }
        }


    }

5.图表上数据的点击事件处理

  int lastX;
    int lastY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        this.getParent().requestDisallowInterceptTouchEvent(true);
        obtainVelocityTracker(event);


        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (xStep*xPoints.size() + startX > getWidth()){
    
    
                    int offsetx = (int) (event.getX() - lastX);
                    lastX = (int) event.getX();
                    if (xInit + offsetx < minXInit) {
    
    
                        xInit = (int) minXInit;
                    } else if (xInit + offsetx > maxXInit) {
    
    
                        xInit = (int) maxXInit;
                    } else {
    
    
                        xInit = xInit + offsetx;
                    }
                    invalidate();
                }

                break;
            case MotionEvent.ACTION_UP:
                if (event.getX() - lastX < 5) {
    
    
                    clickAction(event);
                }
//                scrollAfterActionUp();
                this.getParent().requestDisallowInterceptTouchEvent(false);
                recycleVelocityTracker();
                break;
            case MotionEvent.ACTION_CANCEL:
                this.getParent().requestDisallowInterceptTouchEvent(false);
                recycleVelocityTracker();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                break;
            case MotionEvent.ACTION_POINTER_UP:
                break;
        }
        return true;
    }

 /**
     * 点击X轴坐标或者折线节点
     *
     * @param event
     */
    private void clickAction(MotionEvent event) {
    
    
        int dp8 = DensityUtil.dp2px(10);
        float eventX = event.getX();
        float eventY = event.getY();
        for (int i = 0; i < xPoints.size(); i++) {
    
    
            //节点
            float x = xInit + xStep * i;

            float linePointY = yStep * xPoints.get(i).getxDecimal2() / stepYNumber;
            float columnY = yStep * xPoints.get(i).getxDecimal1() / stepYNumber;
            float y1 = oy - linePointY;
            float y2 = oy - columnY;

            if (eventX >= x - dp8 && eventX <= x + dp8 &&
                    eventY >= y1 - dp8 && eventY <= y1 + dp8 && selectIndex != i + 1) {
    
    //每个节点周围8dp都是可点击区域
                if (selectIndex == i) {
    
    
                    xPoints.get(i).isDataVisible = false;
                } else {
    
    
                    xPoints.get(selectIndex) .isDataVisible = false;
                    xPoints.get(i).isDataVisible = true;
                }
                selectIndex = i;

                invalidate();
                return;
            }
            if (eventX >= x - dp8 && eventX <= x + dp8 &&
                    eventY >= y2 - dp8 && eventY <= y2 + dp8 && selectIndex != i + 1) {
    
    //每个节点周围8dp都是可点击区域
                selectIndex = i + 1;
                invalidate();
                return;
            }

        }
    }

猜你喜欢

转载自blog.csdn.net/qq_32365409/article/details/106619165