自定义UI 绘制饼图
Posted Notzuonotdied
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义UI 绘制饼图相关的知识,希望对你有一定的参考价值。
系列文章目录
文章目录
前言
这系列的文章主要是基于扔物线的HenCoderPlus课程的源码来分析学习。
- 扔物线课程源码:PieChart.java
- android官方文档:自定义绘制
创建绘制对象
我们需要创建一个画笔🖌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)
的四个参数说明
参数 | 说明 |
---|---|
left | x轴方向上,矩形左边的点的位置 |
top | y轴方向上,矩形上边的点的位置 |
right | x轴方向上,矩形右边的点的位置 |
bottom | y轴方向上,矩形下边的点的位置 |
实验效果
让我们将我们设置的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 | 绘制酒红色扇形。 |
4 | Canvas 执行了translate 方法,将绘制区域(灰色区域)向左上方挪。 |
5 | 在变换后的Canvas 上绘制橄榄绿色扇形。 |
6 | 恢复未变换前的Canvas ,并绘制橘黄色扇形。 |
总结:
- 将扇形往外拉的操作是基于Canvas#translate实现的。
- 将变换后的
Canvas
恢复,是基于canvas.save();
和canvas.restore();
来完成的。
那么,如果没有canvas.save();
和canvas.restore();
会咋样?
总结:
- Canvas#translate之后无法恢复。第三、第四个扇形会粘在一起,成为一大块了。
附录
- Android Developer:自定义视图组件
- 扔物线官网:扔物线
- 很感谢大佬提供的教程和源码,才能好好系统学习下自定义UI的内容。
- rengwuxian/HenCoderPlus
- rengwuxian/HenCoderPlus3
以上是关于自定义UI 绘制饼图的主要内容,如果未能解决你的问题,请参考以下文章
echarts中饼图的legend自定义icon图片(扇形为例)
python使用matplotlib可视化饼图(pie plot)使用explode参数自定义设置每个扇形离开中心的距离