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 PieChart 饼图控件

android mpchartlib中piechart数据描述太大怎么设置到外面啊

如何为Piechart元素设置自定义颜色

ggplot, facet, piechart:将文本放在饼图切片的中间

Highcharts (PieChart) - 第二次绘制时图表宽度减小

最近项目里碰上了饼图,顺便整理了下几种Android饼图,以作参考