自定义UI 绘制饼图

Posted Notzuonotdied

tags:

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

系列文章目录

  1. 自定义UI 基础知识
  2. 自定义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 绘制饼图

自定义UI 简易图文混排

自定义UI 简易图文混排

自定义UI 简易图文混排

自定义UI 自制表盘

自定义UI 自制表盘