Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android PieChart 餅圖控件

Android PieChart 餅圖控件

編輯:關於Android編程

今天寫一個餅圖自定義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;i0)
				gapCount++;
		}
		float angle = 0;
		pieAngle = 360 - gapCount*ANGLE_DIS;
		for(int i=0;i0)
			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

\ 
這段代碼是計算百分比文字在圓內的角度,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;
}
}
 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved