效果图预览
1. 分析
1. 饼状图这个首先需要确定几块饼,确定每一块饼需要绘制的角度
2. 根据饼的范围计算百分比的坐标位置
3. 动画处理
4. 中间白色圆和文字的绘制
2. 实现原理
1. 绘制各个饼,可以有两种实现方式 一种通过canvas.drawArc然后中间部分用白色圆给盖住,
另一种方式是Path.arcTo然后用path.op方法取交集
2. 绘制中间文字和白色的圆 通过canvas.drawCircle和canvas.drawText
3.动画处理 计算每一个饼扫描过的角度 计算每一块绘制的角度
3. 初始化一些东西 初始化一般我放在onSizeChanged方法中
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mInRadius = dp2px(60);
mOutRadius = dp2px(120);
// setProgressAnimation(DURATION);
}
4. 绘制中间白色圆和中心文字
//画中间白色的圆
private void drawWhiteCircle(Canvas canvas) {
canvas.drawCircle(0,0,mInRadius,mWhiteCirclePaint);
}
//画中间饼状图文字
private void drawText(Canvas canvas) {
String text = "饼状图";
//设置文字水平居中
mTextPaint.setTextAlign(Paint.Align.CENTER);
//测量文字的宽高 要使文字竖直方向也居中
mTextPaint.getTextBounds(text,0,text.length(), mTextRect);
int height = mTextRect.height();
canvas.drawText(text,0,0+height / 2,mTextPaint);
}
5. 画饼状扇形图 这里我为了简单平分了5份饼
- canvas.save()和canvas.restore()是为了保证当前画布操作不会影响之前或者之后的操作,需要注意的是一般是存对出现,不然可能会抛异常
- mPieChartNum:你要等分的饼的数量 这里我就为了简单等分了5份
- mDrawAngle:每一个饼状图扫描的角度 比如均分5个饼的话 mDrawAngle = 360 / 5 = 72度
- //分别是计算x,y方向的坐标值
mOutRadius*(float) Math.cos(Math.toRadians(mStartAngle))
mOutRadius*(float) Math.sin(Math.toRadians(mStartAngle))
mOutPath.arcTo 添加圆环 - 取圆和圆环的交集
// op(a,b,Path.Op.REVERSE_DIFFERENCE) b-a的交集
mPath.op(mInPath,mOutPath, Path.Op.REVERSE_DIFFERENCE)
//画饼状扇形
private void drawPieChart(Canvas canvas) {
canvas.save();
mInPath.reset();
mOutRectF.set(-mOutRadius, -mOutRadius, mOutRadius, mOutRadius);
mInPath.addCircle(0,0,mInRadius, Path.Direction.CW);
for (int i = 0; i < mPieChartNum; i++) {
mStartAngle = (i == 0) ? 0 : mStartAngle + mScaleAngle;
if (Math.min(mDrawAngle, mAnimatedValue - mStartAngle) >= 0) {
float drawAngle = Math.min(mDrawAngle, mAnimatedValue - mStartAngle);
mOutPaint.setColor(mColors[i]);
mOutPath.lineTo(mOutRadius*(float) Math.cos(Math.toRadians(mStartAngle)),
mOutRadius*(float) Math.sin(Math.toRadians(mStartAngle)));
mOutPath.arcTo(mOutRectF, mStartAngle, drawAngle);
// op(a,b,Path.Op.REVERSE_DIFFERENCE) b-a的交集
mPath.op(mInPath,mOutPath, Path.Op.REVERSE_DIFFERENCE);
canvas.drawPath(mPath,mOutPaint);
//画透明度圆环
drawInRing(canvas,mStartAngle,drawAngle);
//画完一段圆弧再画百分比文字
if(drawAngle % mDrawAngle== 0) {
drawPercentText(canvas, mStartAngle);
}
}
mPath.reset();
mOutPath.reset();
}
canvas.restore();
}
//画百分比文字
private void drawPercentText(Canvas canvas,float startAngle) {
float angle = mDrawAngle / 2 + startAngle;
//计算x,y的时候其实并不需要在不同象限单独计算 比如说 cos0 = 1 -cos(180-180) = cos 180 = -1
float x = (float) (0.75 * mOutRadius * Math.cos(Math.toRadians(angle))) ;
float y = (float) (0.75 * mOutRadius * Math.sin(Math.toRadians(angle))) ;
DecimalFormat df = new DecimalFormat("0");
String format = df.format(100 * 1.0f / mPieChartNum) + "%";
mTextPaint.getTextBounds(format,0,format.length(),mPercentRect);
canvas.drawText(format,x,y + mPercentRect.height() / 2,mTextPaint);
}
6. 动画处理
//设置进度条动画
public void setProgressAnimation(long duration) {
if(mProgressAnimator != null && mProgressAnimator.isRunning()){
mProgressAnimator.cancel();
mProgressAnimator.start();
}else {
mProgressAnimator = ValueAnimator.ofFloat(0, 360).setDuration(duration);
mProgressAnimator.setInterpolator(new AccelerateInterpolator());
mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
/**每次要绘制的圆弧角度**/
mAnimatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
mProgressAnimator.start();
}
}
7. 项目源代码下载
后面统一提供代码下载地址
8. 联系方式
QQ:1509815887