Android PieChart 饼图控件
Posted hygok
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android PieChart 饼图控件相关的知识,希望对你有一定的参考价值。
今天写一个饼图自定义View的文章。由于公司的项目需要用到饼图,UI给的设计图和自己找的一个饼图框架的标题位置不符,所以就自己画了一个。
1,使用预览
PieChart mChart mChart = (PieChart) findViewById(R.id.pieChar);
mChart = (PieChart) findViewById(R.id.pieChar);
String[] titles = new String[] "钱包余额","金钱袋资产","金宝箱资产";
mChart.setTitles(titles);
int[] colors = new int[]0xfff5a002,0xfffb5a2f,0xff36bc99;
mChart.setColors(colors);
mChart.setValues(new double[]999,999,999);
mChart.postInvalidate();
需要传入标题和值,每个饼的颜色也可以重新设置,不传颜色值就用默认的。画饼图的难点就在数学计算和逻辑思维能力。
2代码分析
/**
* 获得每个值所占的角度
* @return
*/
private float[] getAngles()
if(mValues == null || mValues.length == 0) return null;
double sum = 0;
int len = mTitles.length;
float[] angles = new float[len];
int gapCount = 0;//饼图间隙条数
for(int i=0;i<len;i++)
sum += mValues[i];
if(mValues[i]>0)
gapCount++;
float angle = 0;
pieAngle = 360 - gapCount*ANGLE_DIS;
for(int i=0;i<len-1;i++)
angles[i] = (float)(pieAngle*mValues[i]/sum);
angle += angles[i];
if(mValues[len-1]>0)
angles[len - 1] = pieAngle - angle;
return angles;
根据传过来的值计算每个值所占的角度,饼图之间有1°的间隙,
if(mValues[i]>0) gapCount++;只有值大于0时才画饼,才增加一条间隙。
pieAngle = 360 - gapCount*ANGLE_DIS;这里计算所有饼所占整个圆的角度。
if(mValues[len-1]>0) angles[len - 1] = pieAngle - angle;最后一个饼的角度为饼的总角度减去前面所有饼的角度之和,这样做是为了让所有饼的角度加起来等于pieAngle这个角度,减少除法运算的小数误差效果。
/**
* 在饼图的每个饼上写上百分比,必须放在计算了每个值所占的角度angles之后
* @param canvas
*/
private void setPieContentText(Canvas canvas)
float pre = 0;
float[] centerAngle = new float[mAngles.length];
for(int i=0;i<mAngles.length;i++)
if(mAngles[i] == 0) continue;
centerAngle[i] = ANGLE_DIS+mAngles[i]/2 + pre;
pre += mAngles[i];
float cenR = pieR*1.0f/2*3/5;
float lastPir = 1.0f;//为了使所有的%比加起来等于1
mTextPaint.setColor(0xFFFFFFFF);
float cenX = 0;
float cenY = 0;
for(int i=0;i<centerAngle.length;i++)
if(centerAngle[i]==0 ) continue;
float xa = (float) (cenR * Math.cos(centerAngle[i] * (Math.PI / 180)));
float ya = (float) (cenR * Math.sin(centerAngle[i] * (Math.PI / 180)));
cenX = getWidth()*1.0f/2+xa;
cenY = TOP_PADDING + pieR*1.0f/2 + ya;
mTextPaint.setTextSize(mPieTextSize);
double curPer = numDecimals(mAngles[i]/pieAngle);
String perMsg = i==centerAngle.length-1?percentFormat(lastPir):percentFormat(curPer);
canvas.drawText(perMsg, cenX-getTextWidth(perMsg, mPieTextSize)*1.0f/2, cenY+getTextHeight(perMsg, mPieTextSize)*1.0f/2/2, mTextPaint);
lastPir -= curPer;
这段代码是计算百分比文字在圆内的角度,centerAngle[i] = ANGLE_DIS+mAngles[i]/2 + pre;百分比文字在每个饼的中心。 float cenR = pieR*1.0f/2*3/5;计算百分比文字中心到圆心的距离,可根据需要做调整,pieR为饼图直径。 如上图所示,float xa = (float) (cenR * Math.cos(centerAngle[i] * (Math.PI / 180)));float ya = (float) (cenR * Math.sin(centerAngle[i] * (Math.PI / 180)));中xa和ya为文字到圆心的水平垂直距离。cenX = getWidth()*1.0f/2+xa; cenY = TOP_PADDING + pieR*1.0f/2 + ya;cenX 和cenY为文字在view上面的坐标点。
3,自定义饼图源码
import java.text.DecimalFormat;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import cn.golditfin.component.amount.AmountUtils;
/**
* 圆形饼图控件,title为多少条就显示多少条,无视value
* @author HuangYuGuang
* Create on 2015年12月18日
* File Name PieChart.java
*/
public class PieChart extends View
private final float density = getResources().getDisplayMetrics().density;
/**饼图之间间隔的角度*/
private final float ANGLE_DIS = 1;
/**左右两边空间距离*/
private final float LR_PADDING = 25 * density;
/**顶部空间距离*/
private final float TOP_PADDING = 25 * density;
/**饼图和下边文字的距离*/
private final float PIE_TEXT_DIS = 22 * density;
/**上下行文字的距离*/
private final float TEXT_TEXT_DIS = 15 * density;
/**底部和文字的距离,实际为BOTTOM_DIS+TEXT_TEXT_DIS*/
private final float BOTTOM_DIS = 10 * density;
/**标题和值的最小距离*/
private final float TITLE_VALUE_DIS = 18 * density;
/**每部分的颜色值,默认有10个颜色 */
private int[] mColors = new int[]0xfff5a002,0xfffb5a2f,0xff36bc99,0xff43F90C,0xff181A18,0xffF802F6
,0xff022DF8,0xffECF802,0xff02F8E8,0xffEA0F8E;
/**值*/
private double[] mValues;
/**值转换成角度*/
private float[] mAngles;
/**饼图直径*/
private float pieR;
/**饼图所占总的角度*/
private float pieAngle;
private String[] mTitles ; //每部分的内容
private String mEmptyMsg = "暂无数据"; //无数据提示的内容
private float mTitleSize;
private float mValueSize;
/**饼图里面文字的大小*/
private float mPieTextSize;
private int mTitleColor = 0xFF595959;
private int mValueColor = 0xFF595959;
private int mDefaultPointColor = 0xfff5a002; //无数据时提示文字的颜色
private Rect mTextBound;
private Paint mTextPaint;
private Paint mPiePaint;
public PieChart(Context context)
this(context,null);
public PieChart(Context context, AttributeSet attrs)
this(context, attrs,0);
public PieChart(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
mTitleSize = sp2px(14);
mPieTextSize = sp2px(12);
mValueSize = sp2px(16);
mPiePaint = new Paint();
mTextPaint = new Paint();
mTextBound = new Rect();
mTextPaint.setColor(0xff595959);
mTextPaint.setTextSize(mTitleSize);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
mPiePaint.setAntiAlias(true);
mTextPaint.setAntiAlias(true);
/**------------------------------无数据--------------------------------*/
if(mTitles == null || mTitles.length == 0 || isValueEmpty())
mPiePaint.setColor(mDefaultPointColor);
mTextPaint.setColor(0xffffffff);
float cr = (getWidth()<getHeight()?getWidth()/2:getHeight()/2) - LR_PADDING;
canvas.drawCircle(getWidth()/2, getHeight()/2, cr, mPiePaint);
mTextPaint.getTextBounds(mEmptyMsg, 0, mEmptyMsg.length(), mTextBound);
canvas.drawText(mEmptyMsg, (getWidth()-mTextBound.width())/2, (getHeight()+mTextBound.height())/2, mTextPaint);
return;
/**------------------------------无数据--------------------------------*/
/**------------------------------画饼图--------------------------------*/
int textHeight = getTextHeight("00", Math.max(mTitleSize, mValueSize));
float r1 = getWidth() - LR_PADDING*2;
float r2 = getHeight() - TOP_PADDING - PIE_TEXT_DIS - (TEXT_TEXT_DIS+textHeight)*(mTitles.length) - BOTTOM_DIS;
pieR = Math.min(r1, r2);//为了防止饼图越界,饼图直径选取最小值
RectF oval = new RectF((getWidth()-pieR)/2, TOP_PADDING, (getWidth()+pieR)/2, TOP_PADDING+pieR);
mPiePaint.setStyle(Paint.Style.FILL);
mAngles = getAngles();
float startAngle = 0;
for(int i=0;i<mAngles.length;i++)
mPiePaint.setColor(mColors[i]);
if(mAngles[i] == 0) continue;
canvas.drawArc(oval, startAngle, mAngles[i], true, mPiePaint);
startAngle += (mAngles[i]+ANGLE_DIS);
/**------------------------------画饼图--------------------------------*/
/**------------------------------最下面的文字,title和value--------------------------------*/
float cr = 5 * density; //圆点半径
float ctd = 8 * density; //圆点和右边文字距离
float titleLen = getMaxTextWidth(mTitles, mTitleSize) + TITLE_VALUE_DIS;
float len = 2*cr + ctd + titleLen + getMaxTextWidth(mValues, mValueSize);
float topDis = TOP_PADDING + PIE_TEXT_DIS +pieR + textHeight; //第一行文字底部和控件顶部距离
float cX = (getWidth()-len)/2+cr;
float titleX = cX+cr+ctd;
float valueX = titleX + titleLen;
int valueLen = mValues.length-1;
for(int i=0;i<mTitles.length;i++)
mPiePaint.setColor(mColors[i]);
float yDis = topDis+(textHeight+TEXT_TEXT_DIS)*i;
canvas.drawCircle(cX, yDis-textHeight/2, cr, mPiePaint);
mTextPaint.setTextSize(mTitleSize);
mTextPaint.setColor(mTitleColor);
canvas.drawText(mTitles[i], titleX, yDis, mTextPaint);
mTextPaint.setColor(mValueColor);
mTextPaint.setTextSize(mValueSize);
canvas.drawText(AmountUtils.moneyFormat(i>valueLen?0:mValues[i]), valueX, yDis, mTextPaint);
/**------------------------------最下面的文字,title和value--------------------------------*/
//饼图上的文字
setPieContentText(canvas);
/**
* 设置每个饼图代表的名字
* @param titles
*/
public void setTitles(List<String> titles)
mTitles = (String[])titles.toArray();
/**
* 设置每个饼图代表的名字
* @author HuangYuGuang
* Create on 2015年12月30日
* @param titles
*/
public void setTitles(String[] titles)
mTitles = titles;
/**
* 设置值
* @param values
*/
public void setValues(List<Double> values)
mValues = new double[values.size()];
for(int i=0;i<values.size();i++)
mValues[i] = values.get(i);
/**
* 设置值
* @param values
*/
public void setValues(double[] values)
mValues = values;
/**
* 设置每块饼图的颜色
* @param colors
*/
public void setColors(List<Integer> colors)
mColors = new int[colors.size()];
for(int i=0;i<colors.size();i++)
mColors[i] = colors.get(i);
/**
* 设置每块饼图的颜色
* @param colors
*/
public void setColors(int[] colors)
mColors = colors;
/**
* 设置名字的字体大小
* @param size
*/
public void setTitleSize(float size)
mTitleSize = sp2px(size);
/**
* 设置数值的大小
* @param size
*/
public void setValueSize(float size)
mValueSize = sp2px(size);
/**
* 设置饼图上文字的大小
* @param size
*/
public void setPieTextSize(float size)
mPieTextSize = sp2px(size);
/**
* 名字的颜色
* @param titleColor
*/
public void setTitleColor(int titleColor)
mTitleColor = titleColor;
/**
* 数值的颜色
* @param valueColor
*/
public void setValueColor(int valueColor)
mValueColor = valueColor;
/**
* 设置无数据时提示文字的颜色
* Create on 2015年12月31日
* @param color
*/
public void setDefaultPointColor(int color)
mDefaultPointColor = color;
/**
* 无数据的提示内容
* @param msg
*/
public void setEmptyMsg(String msg)
mEmptyMsg = msg;
private boolean isValueEmpty()
if(mValues == null || mValues.length == 0)
return true;
for(double va:mValues)
if(va > 0)
return false;
mEmptyMsg = "暂无数据";
return true;
/**
* 获得每个值所占的角度
* @return
*/
private float[] getAngles()
if(mValues == null || mValues.length == 0) return null;
double sum = 0;
int len = mTitles.length;
float[] angles = new float[len];
int gapCount = 0;//饼图间隙条数
for(int i=0;i<len;i++)
sum += mValues[i];
if(mValues[i]>0)
gapCount++;
float angle = 0;
pieAngle = 360 - gapCount*ANGLE_DIS;
for(int i=0;i<len-1;i++)
angles[i] = (float)(pieAngle*mValues[i]/sum);
angle += angles[i];
if(mValues[len-1]>0)
angles[len - 1] = pieAngle - angle;
return angles;
/**
* 在饼图的每个饼上写上百分比,必须放在计算了每个值所占的角度angles之后
* @param canvas
*/
private void setPieContentText(Canvas canvas)
float pre = 0;
float[] centerAngle = new float[mAngles.length];
for(int i=0;i<mAngles.length;i++)
if(mAngles[i] == 0) continue;
centerAngle[i] = ANGLE_DIS+mAngles[i]/2 + pre;
pre += mAngles[i];
float cenR = pieR*1.0f/2*3/5;
float lastPir = 1.0f;//为了使所有的%比加起来等于1
mTextPaint.setColor(0xFFFFFFFF);
float cenX = 0;
float cenY = 0;
for(int i=0;i<centerAngle.length;i++)
if(centerAngle[i]==0 ) continue;
float xa = (float) (cenR * Math.cos(centerAngle[i] * (Math.PI / 180)));
float ya = (float) (cenR * Math.sin(centerAngle[i] * (Math.PI / 180)));
cenX = getWidth()*1.0f/2+xa;
cenY = TOP_PADDING + pieR*1.0f/2 + ya;
mTextPaint.setTextSize(mPieTextSize);
double curPer = numDecimals(mAngles[i]/pieAngle);
String perMsg = i==centerAngle.length-1?percentFormat(lastPir):percentFormat(curPer);
canvas.drawText(perMsg, cenX-getTextWidth(perMsg, mPieTextSize)*1.0f/2, cenY+getTextHeight(perMsg, mPieTextSize)*1.0f/2/2, mTextPaint);
lastPir -= curPer;
private int getMaxTextWidth(double[] ds,float size)
String[] strs = new String[ds.length];
for(int i=0;i<ds.length;i++)
strs[i] = AmountUtils.moneyFormat(ds[i]);
return getMaxTextWidth(strs, size);
private int getMaxTextWidth(String[] strs,float size)
Rect textBound = new Rect();
Paint paint = new Paint();
paint.setTextSize(size);
int len = 0;
for(int i=0;i<strs.length;i++)
paint.getTextBounds(strs[i], 0, strs[i].length(), textBound);
len = Math.max(len, textBound.width());
return len;
private int getTextWidth(String str,float size)
return getTextRect(str, size).width();
private int getTextHeight(String str,float size)
return getTextRect(str, size).height();
private Rect getTextRect(String str,float size)
Rect textBound = new Rect();
Paint paint = new Paint();
paint.setTextSize(size);
paint.getTextBounds(str, 0, str.length(), textBound);
return textBound;
private float sp2px(float sp)
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
/**
* 格式化为百分比格式
* @param num
* @return
*/
private String percentFormat(double num)
DecimalFormat df = new DecimalFormat("#0.00%");
return df.format(num);
/**
* 保留四位小数
* @param num
* @return
*/
public static double numDecimals(double num)
return ((int)(num*10000))*1.0d/10000;
饼图控件下载
以上是关于Android PieChart 饼图控件的主要内容,如果未能解决你的问题,请参考以下文章
android mpchartlib中piechart数据描述太大怎么设置到外面啊
ggplot, facet, piechart:将文本放在饼图切片的中间