美文网首页Android开发Android开发经验谈Android技术知识
Android绘制柱状图、折线图、饼状图

Android绘制柱状图、折线图、饼状图

作者: 奔跑吧李博 | 来源:发表于2020-07-27 10:55 被阅读0次

当项目要做图标功能时,大家基本就是去引用开源库来使用了,其中最著名的图标库是MPAndroidChart了。但是,如果能够自己手动撸出各种图标,那是不是很酷呢?常用图表类型包括柱状图,折线图,饼状图,雷达图,股票图,这里是做的前三者图表。

演示效果:

戳这里Github代码

实现步骤

  • 柱状图

1.以等差高度画Y轴文字和横线
2.画X轴文字
3.依据数值画不等高度的矩形柱
4.点击各个柱子显示顶部数值

  • 折线图

1.以等差高度画Y轴文字和横线
2.画X轴文字
3.根据数值绘制path

  • 饼状图

1.根据数值绘制不同起始角度和弧度的圆环
2.依据弧度并利用三角函数知识绘制各版块文字,并调节位置至板块中心
3.绘制内部同心圆环

绘制圆环上文字知识点:


圆心为o(x,y),半径r,圆上各个弧度点上的坐标,求值公式为
xA = x+rsinα,yA = y-rcosα,α大小随着顶点变化而变化。

代码实现:
  • 柱状图View
/**
 * create by libo
 * create on 2020/7/26
 * description 柱状图View
 */
public class BarChartView extends View {
    /** 文字Paint */
    private Paint textPaint;
    /** Y轴基准线Paint */
    private Paint linePaint;
    /** 柱型paint */
    private Paint rectPaint;
    /** Y轴每单元数量高度 */
    private float unitHeight;
    /** Y轴数据数组 */
    private int[] unitHeightNum = new int[] {100, 200, 300, 400, 500};
    /** 各个阶段数据数组 */
    private int[] stageNum = new int[] {125, 230, 323, 253, 398, 410};
    private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
    private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red};
    /** X轴单元宽度 */
    private float unitWidth;
    /** 横线左边距大小 */
    private float lineLeftPadding;
    /** 柱状图左右间距 */
    private int rectPadding = 12;
    /** 每个柱子集合 */
    private ArrayList<Bar> bars = new ArrayList<>();
    /** 当前显示值的位置 */
    private int showValuePos = -1;

    public BarChartView(Context context) {
        super(context);
        initPaint();
    }

    public BarChartView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    private void initPaint() {
        textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.textcolor));
        textPaint.setTextSize(40);
        textPaint.setAntiAlias(true);

        linePaint = new Paint();
        linePaint.setColor(getResources().getColor(R.color.linecolor));
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(2);

        rectPaint = new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawYText(canvas);
        drawXText(canvas);
        drawBars(canvas);
    }

    /**
     * 绘制Y轴文字及基准线
     */
    private void drawYText(Canvas canvas) {
        int top = getHeight() - 80;  //给底部文字留下高度
        unitHeight = getHeight()/unitHeightNum.length - 20;

        for (int num : unitHeightNum) {
            Rect rect = new Rect();
            String text = num + "万";
            textPaint.getTextBounds(text, 0, text.length(), rect);
            canvas.drawText(text, 0, top, textPaint);  //画文字

            lineLeftPadding = rect.width() + 20;
            canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint);  //画横线
            top -= unitHeight;
        }
    }

    /**
     * 绘制X轴文字
     */
    private void drawXText(Canvas canvas) {
        float left = lineLeftPadding;
        unitWidth = getWidth()/stageNum.length - 20;

        Bar bar;
        for (int i=0;i<stageNum.length;i++) {
            canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint);  //画文字

            float top = getHeight() - (float)stageNum[i]/100*unitHeight;
            int color = getResources().getColor(colors[i%colors.length]);
            bar = new Bar(stageNum[i], left+rectPadding, top, left+unitWidth-rectPadding, getHeight()- 80, color);
            bars.add(bar);
            left += unitWidth;
        }
    }

    /**
     * 绘制柱形
     */
    private void drawBars(Canvas canvas) {
        //画矩形,并左右设置间距
        //根据该项数值获取实际的柱形高度
        //Y轴每格单元高度为100数值
        for (int i=0;i<bars.size();i++) {
            Bar bar = bars.get(i);
            rectPaint.setColor(bar.color);
            canvas.drawRect(bar.left, bar.top, bars.get(i).right, bar.bootom, rectPaint);

            //绘制柱形上数值
            if (showValuePos == i) {
                String value = String.valueOf(bar.value);
                Rect rect = new Rect();
                textPaint.getTextBounds(value, 0, value.length(), rect);
                float textLeft = bar.left + (bar.right-bar.left-rect.width())/2;  //计算使文字在柱形居中位置
                canvas.drawText(value, textLeft, bar.top-20, textPaint);  //绘制柱顶部数值
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            for (int i=0;i<bars.size();i++) {
                if (event.getX() > bars.get(i).left && event.getX() < bars.get(i).right) {
                    //按下事件在当前柱形内
                    showValuePos = i;
                    invalidate();
                }
            }
        }

        return true;
    }

    /**
     * 柱形类
     */
    class Bar {
        private int value;
        private float left;
        private float top;
        private float right;
        private float bootom;
        private int color;

        public Bar(int value, float left, float top, float right, float bootom, int color) {
            this.value = value;
            this.left = left;
            this.top = top;
            this.right = right;
            this.bootom = bootom;
            this.color = color;
        }
    }

}

  • 折线图View
/**
 * create by libo
 * create on 2020/7/26
 * description 折线图View
 */
public class LineChartView extends View {
    /** 文字Paint */
    private Paint textPaint;
    /** 基准线Paint */
    private Paint linePaint;
    /** 折线paint */
    private Paint charLinePaint;
    /** Y轴每单元数量高度 */
    private float unitHeight;
    /** Y轴数据数组 */
    private int[] unitHeightNum = new int[] {0, 20, 40, 60, 80, 100};
    private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
    /** 横线左边距大小 */
    private float lineLeftPadding;
    /** X轴单元宽度 */
    private float unitWidth;
    /** 各个阶段数据数组 */
    private int[] stageNum = new int[] {56, 40, 82, 74, 60, 92};
    private Path linePath;

    public LineChartView(Context context) {
        super(context);
        initPaint();
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    private void initPaint() {
        textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.textcolor));
        textPaint.setTextSize(40);
        textPaint.setAntiAlias(true);

        linePaint = new Paint();
        linePaint.setColor(getResources().getColor(R.color.linecolor));
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(2);

        charLinePaint = new Paint();
        charLinePaint.setColor(getResources().getColor(R.color.orange));
        charLinePaint.setAntiAlias(true);
        charLinePaint.setStyle(Paint.Style.FILL);

        linePath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawYText(canvas);
        drawXText(canvas);
        drawLinePath(canvas);
    }

    /**
     * 绘制Y轴文字及基准线
     */
    private void drawYText(Canvas canvas) {
        int top = getHeight() - 80;  //给底部文字留下高度
        unitHeight = getHeight()/unitHeightNum.length - 20;

        Rect rect = new Rect();
        String longText = unitHeightNum[unitHeightNum.length-1]+"万";  //以最长文字对齐
        textPaint.getTextBounds(longText, 0, longText.length(), rect);

        for (int num : unitHeightNum) {

            canvas.drawText(num + "万", 0, top, textPaint);  //画文字

            lineLeftPadding = rect.width() + 20;
            canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint);  //画横线
            top -= unitHeight;
        }
    }

    /**
     * 绘制X轴文字
     */
    private void drawXText(Canvas canvas) {
        float left = lineLeftPadding;
        unitWidth = getWidth()/stageNum.length - 20;

        for (int i=0;i<stageNum.length;i++) {
            canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint);  //画文字

            canvas.drawLine(left, getHeight()-80, left, 80, linePaint);

            left += unitWidth;
        }
    }

    /**
     * 绘制折线
     */
    private void drawLinePath(Canvas canvas) {
        float left = lineLeftPadding;

        for (int i=0;i<stageNum.length;i++) {
            float topX = left + unitWidth/2;
            float topY = getHeight() - (float)stageNum[i]/20*unitHeight;

            if (i == 0) {
                linePath.moveTo(topX, topY);
            } else {
                linePath.lineTo(topX, topY);
            }

            charLinePaint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(topX, topY, 10, charLinePaint);  //绘制拐点小圆

            left += unitWidth;
        }

        charLinePaint.setStyle(Paint.Style.STROKE);
        charLinePaint.setStrokeWidth(4);
        canvas.drawPath(linePath, charLinePaint);
    }
}

  • 饼状图View
/**
 * create by libo
 * create on 2020/7/26
 * description 饼状图
 */
public class PieChartView extends View {
    private Paint piePaint;
    private Paint innerPiePaint;
    /** 圆环宽度 */
    private int ringWidth;
    private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red, R.color.orange};
    private int[] values = new int[] {21, 12, 30, 23, 14};
    private String[] titles = new String[] {"医院", "学校", "酒店", "商场", "商业建筑"};
    private Paint textPaint;
    private Paint centerTextPaint;
    private String title = "设施占比";

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

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

    private void init() {
        piePaint = new Paint();
        piePaint.setAntiAlias(true);
        piePaint.setStyle(Paint.Style.STROKE);
        piePaint.setAlpha(190);

        innerPiePaint = new Paint();
        innerPiePaint.setAntiAlias(true);
        innerPiePaint.setStyle(Paint.Style.STROKE);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(getResources().getColor(R.color.white));
        textPaint.setTextSize(45);

        centerTextPaint = new Paint();
        centerTextPaint.setAntiAlias(true);
        centerTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
        centerTextPaint.setTextSize(55);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawRing(canvas);
        drawText(canvas);
        drawCenterText(canvas);
    }

    /**
     * 画分块圆环
     */
    private void drawRing(Canvas canvas) {
        ringWidth = getMeasuredWidth()/4;
        piePaint.setStrokeWidth(ringWidth);
        innerPiePaint.setStrokeWidth(60);
        RectF rectF = new RectF(ringWidth/2, ringWidth/2, getWidth()-ringWidth/2, getHeight()-ringWidth/2);
        RectF innerRectF = new RectF(ringWidth, ringWidth, getWidth()-ringWidth, getHeight()-ringWidth);

        int startAngle = -90;  //12点钟方向起始
        for (int i=0;i<values.length;i++) {
            float sweepAngle = (float) values[i]/100*360;
            piePaint.setColor(getResources().getColor(colors[i]));
            innerPiePaint.setColor(getResources().getColor(colors[i]));

            canvas.drawArc(rectF, startAngle, sweepAngle, false, piePaint);
            innerPiePaint.setAlpha(120);
            canvas.drawArc(innerRectF, startAngle, sweepAngle, false, innerPiePaint);

            startAngle += sweepAngle;
        }
    }

    /**
     * 画每分块文字
     */
    private void drawText(Canvas canvas) {
        int startAngle = 0;  //12点钟方向起始
        int radius = getWidth()/2-ringWidth/2;
        for (int i=0;i<titles.length;i++) {
            float sweepAngle = (float) values[i]/100*360;

            double angle = Math.toRadians(startAngle + sweepAngle/2);
            float x = (float) (getWidth()/2 + radius*Math.sin(angle));
            float y = (float) (getHeight()/2 - radius*Math.cos(angle));

            //横坐标需要全部左移文字宽度的一半
            Rect rect = new Rect();
            textPaint.getTextBounds(titles[i], 0, titles[i].length(), rect);
            canvas.drawText(titles[i], x-rect.width()/2, y, textPaint);

            startAngle += sweepAngle;
        }
    }

    /**
     * 中心文字
     */
    private void drawCenterText(Canvas canvas) {
        Rect rect = new Rect();
        centerTextPaint.getTextBounds(title, 0, title.length(), rect);
        canvas.drawText(title, getWidth()/2-rect.width()/2, getHeight()/2+rect.height()/2, centerTextPaint);
    }

}

相关文章

网友评论

    本文标题:Android绘制柱状图、折线图、饼状图

    本文链接:https://www.haomeiwen.com/subject/bfzrxktx.html