按照国际惯例,先上效果图
数据结构
从图可以看出我们需要标志类型的label 数量value 所占的角度angle 还有代表的颜色,得出数据结构如下
public static class Entry implements Comparable {
public String label;
public int value;
public float angle;
public int color;
public Entry(String label, int value) {
this.label = label;
this.value = value;
}
@Override
public int compareTo(@NonNull Object o) {
Entry e = (Entry) o;
if (value > e.value)
return -1;
if (value < e.value)
return 1;
return 0;
}
}
需求分析
按照数据总数平分一个圆,但是可能存在不能整分的情况,还有可能分的角度太小都看不到。
- 最小角度为2,小于2度的设置成2度,方便查看
- 按照Entry 的value 值分配角度e.angle = (360.0f * e.value) / count + arrearage;(count总个数,arrearage为上一个亏欠的度数)
- 上面亏欠的度数arrearage = e.angle - 2f (<0) ,由下一个项目补偿
- 如果全部计算完毕之后arrearage < 0 ,既还有欠费,那么再循环一遍,重新分配一次
- 为了简化程序,arrearage 亏欠补偿是由下一个补偿的,没有考虑平均分摊,而且只有补偿之后e.angle + arrearage > 2 角度仍然大于2度的才有资格替上面一个补偿亏欠
代码
注释已经写的很清楚了,这里就不再解释,具体的坐标计算了,里面包括了一些数学的东西,椭圆的知识忘了可以百度一下,还有解决了TextPaint 绘制文字重叠不自动换行的问题,具体参考 Canvas的drawText绘制文本自动换行(支持设置显示最大行数)。
自定义view : PieChart.java
public class PieChart extends View {
ArrayList<Entry> mDataSet = new ArrayList<>();
Paint mPaint;
//写小圆文字 自动换行和限制最大行数
private TextPaint mTextPain;
private static final int MAX_LINE = 2;
public PieChart(Context context) {
super(context);
init();
}
public PieChart(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public PieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public PieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
/**
*
* @param data 由于CompanyInfo 限制,最多只有 6个数据项
* @param centerText
*/
public void setData(ArrayList<Entry> data, String centerText) {
mDataSet.clear();
if (data != null) {
mDataSet.addAll(data);
}
mCenterText = centerText;
preCalcute();
this.requestLayout();
}
public void setData(String[] labels, int values[], String centerText) {
mDataSet.clear();
for (int i = 0; i < labels.length; i++) {
mDataSet.add(new Entry(labels[i], values[i]));
}
mCenterText = centerText;
preCalcute();
this.requestLayout();
}
// 计算角度,颜色
private void preCalcute() {
//计算 颜色(最大->最小 按COLORS数字依次分配,所以先排序
ArrayList<PieChart.Entry> tmp = new ArrayList<>(mDataSet.size());
tmp.addAll(mDataSet);
Collections.sort(tmp);
int i = 0;
for (Entry e : tmp) {
e.color = COLORS[i++];
}
if (mSortData) {
Collections.sort(mDataSet);
}
int count = 0;
for (Entry e : mDataSet) {
count += e.value;
}
mCount = count;
//计算角度
float arrearage = 0;//用于补偿的中间变量,初始化0
for (Entry e : mDataSet) {
e.angle = (360.0f * e.value) / count + arrearage;
// 角度太小,就画不出来了,所以设置最小角度为2,把多占用的让下一个承担
if (e.angle < 2f) {
arrearage = e.angle - 2f;//这是欠的度数
e.angle = 2;
} else {
arrearage = 0;
}
}
if (arrearage < 0) {
//最后还有欠费,就再循环一遍,让大家(下一个)分担下欠费
for (Entry e : mDataSet) {
if (e.angle + arrearage > 2) {
//如果分担后仍然大于2,就让他分担
e.angle += arrearage;
break;
}
}
}
Locale locale = Locale.getDefault();
if (tmp.size() < 6) {
//下面的小圆小于6个
singleItemWidth = convertDpToPixel(63);
} else {
singleItemWidth = convertDpToPixel(56);
}
if (locale.getLanguage().toLowerCase().startsWith("zh")) {
textSizeLabel = convertDpToPixel(14);
} else {
if (tmp.size() < 6) {
textSizeLabel = convertDpToPixel(13);
} else {
textSizeLabel = convertDpToPixel(11);
}
}
}
//是否需要对数据进行排序
boolean mSortData;
public void setSort(boolean sort) {
mSortData = sort;
}
//预制颜色,从大到小
final int COLORS[] = {
0xFF00A7CF, 0xFF8E7BE6, 0xFF0179B1, 0xFF73AC1A, 0xFFF5B910<