自定义UI 绘制饼图

Posted Notzuonotdied

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义UI 绘制饼图相关的知识,希望对你有一定的参考价值。

系列文章目录

  1. 自定义UI 基础知识
  2. 自定义UI 绘制饼图
  3. 自定义UI 圆形头像
  4. 自定义UI 自制表盘
  5. 自定义UI 简易图文混排
  6. 自定义UI 使用Camera做三维变换
  7. 自定义UI 属性动画
  8. 自定义UI 自定义布局

文章目录


前言

这系列的文章主要是基于扔物线的HenCoderPlus课程的源码来分析学习。


创建绘制对象

我们需要创建一个画笔🖌Paint来绘制我们的饼图。

public class PieChart extends View 
    // 饼图的半径
    private static final int RADIUS = (int) Utils.dp2px(150);
    // 抗锯齿(可以有效的解决毛边的问题)
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 饼图的分布区域
    RectF bounds = new RectF();

	// 系统默认调用的就是这个构造函数
    public PieChart(Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
    

设置布局位置

如果您的视图不需要对其大小进行特殊控制,您只需替换一个方法,即onSizeChanged()。系统会在首次为您的视图分配大小时调用onSizeChanged(),如果视图大小由于任何原因而改变,系统会再次调用该方法。请在onSizeChanged()中计算位置、尺寸以及其他与视图大小相关的任何值,而不要在每次绘制时都重新计算。

摘录自Andorid官方文档:处理布局事件

public class PieChart extends View 
    // 饼图的半径
    private static final int RADIUS = (int) Utils.dp2px(150);
    // 抗锯齿(可以有效的解决毛边的问题)
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 饼图的分布区域
    RectF bounds = new RectF();

    public PieChart(Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
    

    /**
     * 在此视图的大小发生变化时调用。
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) 
        super.onSizeChanged(w, h, oldw, oldh);
        // 根据View的宽度重新计算饼图的位置
        int ww = getWidth() / 2;
        // 设置矩形的位置
        // 四个参数:left、top、right、bottom
        // left:x轴方向上,矩形左边的点的位置
        // top:y轴方向上,矩形上边的点的位置
        // right:x轴方向上,矩形右边的点的位置
        // bottom:y轴方向上,矩形下边的点的位置
        bounds.set(ww - RADIUS, ww - RADIUS, ww + RADIUS, ww + RADIUS);
    

  • android.graphics.RectF#set(float, float, float, float)的四个参数说明
参数说明
leftx轴方向上,矩形左边的点的位置
topy轴方向上,矩形上边的点的位置
rightx轴方向上,矩形右边的点的位置
bottomy轴方向上,矩形下边的点的位置

实验效果

让我们将我们设置的bounds的范围绘制出来看看。

public class PieChart extends View 
    // 饼图的半径
    private static final int RADIUS = (int) Utils.dp2px(150);
    // 抗锯齿(可以有效的解决毛边的问题)
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 饼图的分布区域
    RectF bounds = new RectF();

    public PieChart(Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
    

    /**
     * 在此视图的大小发生变化时调用。
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) 
        super.onSizeChanged(w, h, oldw, oldh);
        // 根据View的宽度重新计算饼图的位置
        int ww = getWidth() / 2;
        bounds.set(ww - RADIUS, ww - RADIUS, ww + RADIUS, ww + RADIUS);
    

	// 重写方法,实现定义绘制内容
    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        canvas.drawRect(bounds, paint);
    

自定义绘制内容

重写android.view.View#onDraw方法,即可实现自定义绘制。

/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) 


绘制扇形

public class PieChart extends View 

	// 饼图的分布区域
    RectF bounds = new RectF();
	// 前面一样的代码我省略了哈
		
    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        // 绘制扇形
        canvas.drawArc(bounds, 0, 120, true, paint);
    

上述绘制扇形用到的方法为:android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint)

参数说明
RectF扇形展示的区域
startAngle扇形起始角度
sweepAngle扇形扫过的角度
useCenter是否使用圆心。

true:绘制出来是扇形(请见下图一)
false:绘制出来是弧形(请见下图三)
paint画笔
  • 为了便于大家理解,我将bounds也绘制出来作为对比。
    • 灰色区域bounds的绘制范围。
    • 黑色扇形是我们绘制的内容。

绘制扇形图

public class PieChart extends View 
    // 饼图的半径
    private static final int RADIUS = (int) Utils.dp2px(150);
    // 抗锯齿(可以有效的解决毛边的问题)
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 饼图的分布区域
    RectF bounds = new RectF();
    // 从坐标轴的第四象限开始画图
    int[] angles = 60, 100, 120, 80;
    int[] colors = 
            Color.parseColor("#2979FF"),
            Color.parseColor("#C2185B"),
            Color.parseColor("#009688"),
            Color.parseColor("#FF8F00")
    ;

    public PieChart(Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
    

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) 
        super.onSizeChanged(w, h, oldw, oldh);
        // 根据View的宽度重新计算饼图的位置
        int ww = getWidth() / 2;
        bounds.set(ww - RADIUS, ww - RADIUS, ww + RADIUS, ww + RADIUS);
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        // 绘制矩形
        // canvas.drawRect(bounds, paint);
        int currentAngle = 0;
        for (int i = 0; i < angles.length; i++) 
            paint.setColor(colors[i]);
            canvas.drawArc(bounds, currentAngle, angles[i], true, paint);
            currentAngle += angles[i];
        
    

  • 图二:在onDraw中增加了canvas.drawRect(bounds, paint);的绘制。
  • 扇形的绘制顺序如图二中标记📌的顺序。

将扇形图往外拉

public class PieChart extends View 
	// 去掉了部分重复的代码

    // 饼图相对坐标轴的偏移量
    private static final int OFFSET = (int) Utils.dp2px(20);
    // 往外拉的扇形的索引值
    private static final int PULLED_OUT_INDEX = 2;

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

        int currentAngle = 0;
        for (int i = 0; i < angles.length; i++) 
            paint.setColor(colors[i]);
            // save表示保存当前的视图
            canvas.save();
            if (i == PULLED_OUT_INDEX) 
                int tempAngle = angles[i] / 2;
                canvas.translate(
                        (float) Math.cos(Math.toRadians(currentAngle + tempAngle)) * OFFSET,
                        (float) Math.sin(Math.toRadians(currentAngle + tempAngle)) * OFFSET
                );
            
            canvas.drawArc(bounds, currentAngle, angles[i], true, paint);
            // 恢复由于执行了translate后的canvas视图
            canvas.restore();
            currentAngle += angles[i];
        
    

数学计算有点忘了(╯︵╰)?可以看看:正弦、余弦和正切

原理解析

Android官方文档:Canvas#translate

直接看图,看绘制的顺序:

步骤说明
1扇形是在矩形bounds上面绘制的。
2绘制蓝色扇形。注意:以圆心为坐标原点,那么绘制顺序是由第四象限开始,第一象限截止。
3绘制酒红色扇形
4Canvas执行了translate方法,将绘制区域(灰色区域)左上方挪。
5在变换后的Canvas上绘制橄榄绿色扇形
6恢复未变换前的Canvas,并绘制橘黄色扇形

总结:

  • 将扇形往外拉的操作是基于Canvas#translate实现的。
  • 将变换后的Canvas恢复,是基于canvas.save();canvas.restore();来完成的。

那么,如果没有canvas.save();canvas.restore();会咋样?

总结:

  • Canvas#translate之后无法恢复。第三、第四个扇形会粘在一起,成为一大块了。

附录

以上是关于自定义UI 绘制饼图的主要内容,如果未能解决你的问题,请参考以下文章

自定义UI 绘制饼图

R语言 | 绘制饼图(扇形图)方法示例

python添加饼图扇形面积

echarts中饼图的legend自定义icon图片(扇形为例)

python使用matplotlib可视化饼图(pie plot)使用explode参数自定义设置每个扇形离开中心的距离

使用ThreeJS绘制一个饼图