效果圖
效果圖
效果圖
</div>
實(shí)現(xiàn)原理分析
- 每段弧線(xiàn)的繪制:根據(jù)每個(gè)數(shù)據(jù)所占總數(shù)的百分比得出該弧線(xiàn)的度數(shù)(一共360度),然后使用canvas.drawArc繪制即可。至于弧線(xiàn)的顏色,我們可以隨機(jī)生成。
- 弧線(xiàn)中數(shù)據(jù)的繪制:只要能獲取到每段弧線(xiàn)的中心點(diǎn)在view中的坐標(biāo),我們就能在弧線(xiàn)中心坐標(biāo)處繪制數(shù)據(jù)。那么如何獲取呢?答案是三角函數(shù)(沒(méi)學(xué)好的,百度去百度鏈接)。每段弧線(xiàn)的中心點(diǎn)與Y軸的夾角我們是知道的(根據(jù)上一步可以算出),然后斜邊是圓弧的半徑,已知夾角和斜邊,使用cos和sin,每段弧線(xiàn)的中心點(diǎn)與圓心的距離就能算出來(lái),那么弧線(xiàn)中心點(diǎn)在整個(gè)view的坐標(biāo)就得到了,繪制數(shù)據(jù)就是輕輕松松的了。
代碼中有詳細(xì)的注釋?zhuān)唧w代碼下面貼出。
項(xiàng)目地址
完整的代碼和項(xiàng)目大家可以到我的github中查看,里面有相關(guān)的使用方法,同時(shí)這個(gè)項(xiàng)目上傳到了maven倉(cāng)庫(kù),可以通過(guò)gradle直接使用
compile 'com.zhijieeeeee:pieviewlibrary:2.0.0'
github地址:https://github.com/zhijieeeeee/PieView
實(shí)現(xiàn)代碼
public class PieView2 extends View {
/**
* 使用wrap_content時(shí)默認(rèn)的尺寸
*/
private static final int DEFAULT_WIDTH = 800;
private static final int DEFAULT_HEIGHT = 800;
/**
* 中心坐標(biāo)
*/
private int centerX;
private int centerY;
/**
* 半徑
*/
private float radius;
/**
* 弧形外接矩形
*/
private RectF rectF;
/**
* 中間文本的大小
*/
private Rect centerTextBound = new Rect();
/**
* 數(shù)據(jù)文本的大小
*/
private Rect dataTextBound = new Rect();
/**
* 扇形畫(huà)筆
*/
private Paint mArcPaint;
/**
* 中心文本畫(huà)筆
*/
private Paint centerTextPaint;
/**
* 數(shù)據(jù)畫(huà)筆
*/
private Paint dataPaint;
/**
* 數(shù)據(jù)源數(shù)字?jǐn)?shù)組
*/
private int[] numbers;
/**
* 數(shù)據(jù)源名稱(chēng)數(shù)組
*/
private String[] names;
/**
* 數(shù)據(jù)源總和
*/
private int sum;
/**
* 顏色數(shù)組
*/
private int[] colors;
private Random random = new Random();
//自定義屬性 Start
/**
* 中間字體大小
*/
private float centerTextSize = 80;
/**
* 數(shù)據(jù)字體大小
*/
private float dataTextSize = 30;
/**
* 中間字體顏色
*/
private int centerTextColor = Color.BLACK;
/**
* 數(shù)據(jù)字體顏色
*/
private int dataTextColor = Color.BLACK;
/**
* 圓圈的寬度
*/
private float circleWidth = 100;
//自定義屬性 End
public PieView2(Context context) {
super(context);
init();
}
public PieView2(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PieView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PieView);
centerTextSize = typedArray.getDimension(R.styleable.PieView_centerTextSize, centerTextSize);
dataTextSize = typedArray.getDimension(R.styleable.PieView_dataTextSize, dataTextSize);
circleWidth = typedArray.getDimension(R.styleable.PieView_circleWidth, circleWidth);
centerTextColor = typedArray.getColor(R.styleable.PieView_centerTextColor, centerTextColor);
dataTextColor = typedArray.getColor(R.styleable.PieView_dataTextColor, dataTextColor);
typedArray.recycle();
init();
}
/**
* 初始化
*/
private void init() {
mArcPaint = new Paint();
mArcPaint.setStrokeWidth(circleWidth);
mArcPaint.setAntiAlias(true);
mArcPaint.setStyle(Paint.Style.STROKE);
centerTextPaint = new Paint();
centerTextPaint.setTextSize(centerTextSize);
centerTextPaint.setAntiAlias(true);
centerTextPaint.setColor(centerTextColor);
dataPaint = new Paint();
dataPaint.setStrokeWidth(2);
dataPaint.setTextSize(dataTextSize);
dataPaint.setAntiAlias(true);
dataPaint.setColor(dataTextColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidthSize = MeasureSpec.getSize(widthMeasureSpec);
int measureHeightSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
if (measureWidthMode == MeasureSpec.AT_MOST
&& measureHeightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
} else if (measureWidthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WIDTH, measureHeightSize);
} else if (measureHeightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(measureWidthSize, DEFAULT_HEIGHT);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = getMeasuredWidth() / 2;
centerY = getMeasuredHeight() / 2;
//設(shè)置半徑為寬高最小值的1/4
radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 4;
//設(shè)置扇形外接矩形
rectF = new RectF(centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
calculateAndDraw(canvas);
}
/**
* 計(jì)算比例并且繪制扇形和數(shù)據(jù)
*/
private void calculateAndDraw(Canvas canvas) {
if (numbers == null || numbers.length == 0) {
return;
}
//扇形開(kāi)始度數(shù)
int startAngle = 0;
//所占百分比
float percent;
//所占度數(shù)
float angle;
for (int i = 0; i < numbers.length; i++) {
percent = numbers[i] / (float) sum;
//獲取百分比在360中所占度數(shù)
if (i == numbers.length - 1) {//保證所有度數(shù)加起來(lái)等于360
angle = 360 - startAngle;
} else {
angle = (float) Math.ceil(percent * 360);
}
//繪制第i段扇形
drawArc(canvas, startAngle, angle, colors[i]);
startAngle += angle;
//繪制數(shù)據(jù)
if (numbers[i] <= 0) {
continue;
}
//當(dāng)前弧線(xiàn)中心點(diǎn)相對(duì)于縱軸的夾角度數(shù),由于扇形的繪制是從三點(diǎn)鐘方向開(kāi)始,所以加90
float arcCenterDegree = 90 + startAngle - angle / 2;
drawData(canvas, arcCenterDegree, i, percent);
}
//繪制中心數(shù)字總和
canvas.drawText(sum + "", centerX - centerTextBound.width() / 2, centerY + centerTextBound.height() / 2, centerTextPaint);
}
/**
* 計(jì)算每段弧度的中心坐標(biāo)
*
* @param degree 當(dāng)前扇形中心度數(shù)
*/
private float[] calculatePosition(float degree) {
//由于Math.sin(double a)中參數(shù)a不是度數(shù)而是弧度,所以需要將度數(shù)轉(zhuǎn)化為弧度
//而Math.toRadians(degree)的作用就是將度數(shù)轉(zhuǎn)化為弧度
//sin 一二正,三四負(fù) sin(180-a)=sin(a)
//扇形弧線(xiàn)中心點(diǎn)距離圓心的x坐標(biāo)
float x = (float) (Math.sin(Math.toRadians(degree)) * radius);
//cos 一四正,二三負(fù)
//扇形弧線(xiàn)中心點(diǎn)距離圓心的y坐標(biāo)
float y = (float) (Math.cos(Math.toRadians(degree)) * radius);
//每段弧度的中心坐標(biāo)(扇形弧線(xiàn)中心點(diǎn)相對(duì)于view的坐標(biāo))
float startX = centerX + x;
float startY = centerY - y;
float[] position = new float[2];
position[0] = startX;
position[1] = startY;
return position;
}
/**
* 繪制數(shù)據(jù)
*
* @param canvas 畫(huà)布
* @param degree 第i段弧線(xiàn)中心點(diǎn)相對(duì)于縱軸的夾角度數(shù)
* @param i 第i段弧線(xiàn)
* @param percent 數(shù)據(jù)百分比
*/
private void drawData(Canvas canvas, float degree, int i, float percent) {
//弧度中心坐標(biāo)
float startX = calculatePosition(degree)[0];
float startY = calculatePosition(degree)[1];
//獲取名稱(chēng)文本大小
dataPaint.getTextBounds(names[i], 0, names[i].length(), dataTextBound);
//繪制名稱(chēng)數(shù)據(jù),20為縱坐標(biāo)偏移量
canvas.drawText(names[i],
startX - dataTextBound.width() / 2,
startY + dataTextBound.height() / 2 - 20,
dataPaint);
//拼接百分比并獲取文本大小
DecimalFormat df = new DecimalFormat("0.0");
String percentString = df.format(percent * 100) + "%";
dataPaint.getTextBounds(percentString, 0, percentString.length(), dataTextBound);
//繪制百分比數(shù)據(jù),20為縱坐標(biāo)偏移量
canvas.drawText(percentString,
startX - dataTextBound.width() / 2,
startY + dataTextBound.height() * 2 - 20,
dataPaint);
}
/**
* 繪制扇形
*
* @param canvas 畫(huà)布
* @param startAngle 開(kāi)始度數(shù)
* @param angle 扇形的度數(shù)
* @param color 顏色
*/
private void drawArc(Canvas canvas, float startAngle, float angle, int color) {
mArcPaint.setColor(color);
//-0.5和+0.5是為了讓每個(gè)扇形之間沒(méi)有間隙
canvas.drawArc(rectF, startAngle - 0.5f, angle + 0.5f, false, mArcPaint);
}
/**
* 生成隨機(jī)顏色
*/
private int randomColor() {
int red = random.nextInt(256);
int green = random.nextInt(256);
int blue = random.nextInt(256);
return Color.rgb(red, green, blue);
}
/**
* 設(shè)置數(shù)據(jù)
*
* @param numbers 數(shù)字?jǐn)?shù)組
* @param names 名稱(chēng)數(shù)組
*/
public void setData(int[] numbers, String[] names) {
if (numbers == null || numbers.length == 0 || names == null || names.length == 0) {
return;
}
if (numbers.length != names.length) {
//名稱(chēng)個(gè)數(shù)與數(shù)字個(gè)數(shù)不相等
return;
}
this.numbers = numbers;
this.names = names;
colors = new int[numbers.length];
for (int i = 0; i < this.numbers.length; i++) {
//計(jì)算總和
sum += numbers[i];
//隨機(jī)顏色
colors[i] = randomColor();
}
//計(jì)算總和數(shù)字的寬高
centerTextPaint.getTextBounds(sum + "", 0, (sum + "").length(), centerTextBound);
invalidate();
}
}
自定義屬性
<declare-styleable name="PieView">
<attr name="centerTextSize" format="dimension" />
<attr name="dataTextSize" format="dimension" />
<attr name="centerTextColor" format="color" />
<attr name="dataTextColor" format="color" />
<attr name="circleWidth" format="dimension" />
</declare-styleable>