一、概述
有时开发会遇到需要的图表,导入一个三方库太大,这时候就可以自己来自定义个图表控件,近日我就开发了个饼状图,用来显示各板块占比和信息显示,并加入了动画,某块模块放大,自定义设置板块颜色、字体颜色等功能,可直接拿来使用。
二、效果图
也可以设置成实心的饼状图
三、设计思路
先绘制一个个弧形版块拼接成圆,同一圆心,再绘制一个小点的带透明度的白色圆盖在其上,然后绘制中心的孔,即纯白的圆盖在最上面,最后根据计算,将文字绘制在版块的角平分线上,居在透明圆和外圆的中心。
四、代码实现
1、重写draw方法
再calculate()方法中计算外圆半径,计算数据总和。
public void draw(Canvas canvas) {
super.draw(canvas);
//将画布绘成白色
canvas.drawColor(Color.WHITE);
//平移画布,使(0,0)点至中心,便于计算
canvas.translate(getWidth()/2,getHeight()/2);
calculate();
if (showAnimator){
drawByAnim(canvas); //动画绘制
}else {
drawByNormal(canvas); //普通绘制
}
}
2、普通绘制
这边就是给画笔设置对应颜色,若未设置则随机取色,这边还有个功能就是使该板块有凸起的效果,其实就是采取画布平移(canvas.translate),根据该板块角平分线往外拉一定距离;绘制弧形,使用方法canvas.drawArc(RectF,起始绘制角度,旋转绘制角度,是否包含圆心,画笔);最后将画布恢复到之前保存的状态即为外角平分线平移的状态;将该板块的角度累加到当前角度中去。
/**
* 非动画绘制
*/
private void drawByNormal(Canvas canvas){
//循环绘制各模块
for (int i=0;i<count;i++){
if (0 == getData().get(i).getColor()){
int color = getRandColor();
mOutPaint.setColor(color);
getData().get(i).setColor(color);
}else {
mOutPaint.setColor(getData().get(i).getColor());
}
//计算该板块角度
float sweepAngle = getData().get(i).getData()/sumData*360f;
//保存当前画布状态
canvas.save();
//当前模块角平分线的sin和cos值
if (getData().get(i).isRaised()){
float mathCos = (float) (Math.cos((sweepAngle/2+mCurrentDegree)/180f*Math.PI));
float mathSin = (float) (Math.sin((sweepAngle/2+mCurrentDegree)/180f*Math.PI));
//若该板块设置凸起,平移画布,使其与周边各块产生间距,并突出
canvas.translate(mathCos*mSpace, mathSin*mSpace);
}
//绘制外圆
canvas.drawArc(mRadiusRectF,mCurrentDegree,sweepAngle,true,mOutPaint);
//恢复平移前的状态
canvas.restore();
mCurrentDegree += sweepAngle;
}
mAlphaPaint.setAlpha((int) (mAlpha*255));
canvas.drawCircle(0,0,mRadius*mAlphaRadiusPercent,mAlphaPaint);
canvas.drawCircle(0,0,mRadius*mHoleRadiusPercent,mHolePaint);
drawTextInner(canvas);
if (showCenterText) {
//绘制中心文字
Paint.FontMetrics metrics = mCenterTextPaint.getFontMetrics();
float textHeight = Math.abs(metrics.descent - metrics.ascent);
canvas.drawText(mCenterText, 0, textHeight / 2 - metrics.descent, mCenterTextPaint);
}
}
3、绘制文字
因setTextAlign(Paint.Align.CENTER)让文字已水平居中,再设置让其垂直也居中即可;根据画笔获取FontMetrics,一开始绘制中文字就在baseline上,我们要让其绘制到ascent和descent中间,因以baseline为基准线,向上为负,所以ascent为负值;现将文字绘制在板块的角平分线上,所以我们高度只需往下平移Math.abs(metrics.descent - metrics.ascent)/2-metrics.descent即可让中文字在中心。
//获取文字高度,因水平已居中
String text = getData().get(i).getMsg();
Paint.FontMetrics metrics = mBlockTextPaint.getFontMetrics();
float textHeight = Math.abs(metrics.descent - metrics.ascent);
float textRadius = (1 + mAlphaRadiusPercent) * mRadius / 2;
//绘制模块文字
canvas.drawText(text, textRadius * mathCos,
textRadius * mathSin + textHeight / 2 - metrics.descent, mBlockTextPaint);
4、动画实现
若本次绘制的角度大于该板块的角度,那就继续进入下个循环,绘制下一板块,若本次绘制角度小于该板块角度,那就绘制到valueAngle,跳出循环,等待下一个valueAngle,再重新绘制图形。
for (int i=0;i<count;i++){
float valueAngle = Math.min(sweepAngle, mAnimatorValue-(mCurrentDegree-mStartDegree));
//绘制外圆
//根据动画,本次真实需绘制角度
canvas.drawArc(mRadiusRectF,mCurrentDegree,valueAngle,true,mOutPaint);
//若本次绘制角度大于该版块角度,则进入下一板块,否则结束循环,重新绘制
if (sweepAngle <= valueAngle){
mCurrentDegree += sweepAngle;
}else {
break;
}
}
animator = ValueAnimator.ofFloat(0, 360f);
animator.setDuration(3000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
refresh(); //触发重新绘制
}
});
animator.addListener(new Animator.AnimatorListener() {
……
@Override
public void onAnimationEnd(Animator animation) {
showAnimator = false;
refresh(); //动画结束,重新绘制,这时动画按钮已关闭,则进入普通绘制,文字也就绘制出来了
}
……
});
五、源码地址
该控件我已经封装好,可拿来直接使用,使用方法已在GitHub上进行简单讲解,也可进源码进行解读,还有些可优化改进的地方,大家可以给我提议或自行改进。
GitHub:https://github.com/ljm17/PieView
若对你有所帮助,请给我个star吧~