一、前言
上一篇介绍上自定义View之柱状图,本篇介绍自定义饼状图,主要也是因为项目中的的需要,有个不同比例的数组或列表展示,并且第一次初始化时有个动画效果,即根据当前进度绘制数组或列表中的数据。话不多说,先上效果图:
二、实现步骤
1.定义PieChartView继承View,给PieChartView添加自定义属性:
即在value目录下找到attrs.xml文件,在文件中可以定义PieChartView用到的属性,比如颜色、字体大小等属性。文件内容如下:
<declare-styleable name="PieChartView">
<attr name="arcWidth" format="dimension"/><!-- 饼状图圆环宽度 -->
<attr name="arcOrginColor" format="color"/><!-- 圆环初始颜色 -->
<attr name="arcCoverColor" format="color"/><!-- 圆环覆盖颜色 -->
<attr name="centerText" format="string"/><!-- 中间文字-->
<attr name="centerTextSize" format="dimension"/><!-- 中间文字大小 -->
<attr name="centerTextColor" format="color"/><!-- 中间文字颜色 -->
</declare-styleable>
2.在.xml布局文件中对自定义的属性进行设置:
<com.cyj.piechartview.PieChartView
android:id="@+id/pie_chart_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="20dp"
android:layout_centerHorizontal="true"
app:arcWidth="30dp"
app:centerText="分布情况"
app:centerTextColor="@color/mainColor"
app:centerTextSize="18sp"
app:arcOrginColor="@color/subColor5"
app:arcCoverColor="@color/accentColor"/>
3.在PieChartView的构造方法中获得自定义的属性,主要代码如下:
public PieChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PieChartView);
arcWidth = typedArray.getDimension(R.styleable.PieChartView_arcWidth, 20);
arcOrginColor = typedArray.getColor(R.styleable.PieChartView_arcOrginColor, getResources().getColor(R.color.color_gray));
arcCoverColor = typedArray.getColor(R.styleable.PieChartView_arcCoverColor, getResources().getColor(R.color.accentColor));
centerTextSize = typedArray.getDimension(R.styleable.PieChartView_centerTextSize, 18);
centerText = typedArray.getString(R.styleable.PieChartView_centerText);
centerTextColor = typedArray.getColor(R.styleable.PieChartView_centerTextColor, getResources().getColor(R.color.mainColor));
typedArray.recycle();
init();
}
4.初始化画笔及一些必要变量,代码如下:
private void init() {
arcPaint.reset();
arcPaint.setAntiAlias(true);//用来防止边缘的锯齿
arcPaint.setStrokeWidth(arcWidth);
arcPaint.setStyle(Paint.Style.STROKE); //画笔类型 STROKE空心 FILL 实心
arcPaint.setColor(arcOrginColor);
arcCoverPaint.reset();
arcCoverPaint.setAntiAlias(true);//用来防止边缘的锯齿
arcCoverPaint.setStrokeWidth(arcWidth);
arcCoverPaint.setStyle(Paint.Style.STROKE); //画笔类型 STROKE空心 FILL 实心
arcCoverPaint.setColor(arcOrginColor);
mCenterTextPaint = new Paint();
mCenterTextPaint.setStyle(Paint.Style.FILL);
mCenterTextPaint.setColor(centerTextColor);
mCenterTextPaint.setTextSize(centerTextSize);
mCenterTextPaint.setAntiAlias(true);
}
5.重写onMeasure()方法。在onMeasure()方法中完成当布局为wrap_content时设置默认长宽。代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
}
private int measure(int origin) {
int result = DEFAULT_MIN_WIDTH;
int specMode = MeasureSpec.getMode(origin);
int specSize = MeasureSpec.getSize(origin);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
6.重写onDraw方法。在onDraw方法中完成饼状图的绘制。代码如下
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
width = getWidth();
height = getHeight();
center = width > height ? height / 2 : width / 2;
radius = (int) (center - arcWidth / 2); // 圆环的半径
rectF = new RectF(center - radius, center - radius, center + radius, center + radius);
drawOrginArc(canvas);//画初始圆环
drawCoverArc(canvas);//画彩色圆环
drawCenterText(canvas);//画中间值
}
主要完全以下几个部分的绘制:
a.绘制初始圆环,代码如下:
private void drawOrginArc(Canvas canvas) {
arcPaint.setColor(arcOrginColor);
canvas.drawArc(rectF, 0, 360, false, arcPaint);
}
b.绘制彩色圆环根据动画进度绘制,为了实现动画效果,绘制过程中有个计算当前绘制角度,具体看代码中注释,代码如下:
private void drawCoverArc(Canvas canvas) {
float mStartAngle = 0;
// for (int i = 0; i < value.length; i++) {
// arcCoverPaint.setColor(valueColors[i]);
// float angle = 360 * value[i];
// canvas.drawArc(rectF, mStartAngle, angle, false, arcCoverPaint);
// mStartAngle += angle;
// }
int poisition = 0;
float currentVauleSum = 0;
//该for循环判断当前currentValue处于列表或数组第几个位置
for (int i = 0; i < value.length; i++) {
currentVauleSum = 0;
for (int j = 0; j <= i; j++) {// 前i个的和
currentVauleSum += value[j];
currentVauleSum = (float) (Math.round(currentVauleSum * 100)) / 100;
}
float currentRate = currentValue / 360;
if (currentRate <= currentVauleSum) {
poisition = i;
break;
}
}
//根据currentValue所处于列表或数组的位置 分别进行绘制
for (int i = 0; i <= poisition; i++) {
if (i == poisition) {//根据当前currentValue值绘制圆环
arcCoverPaint.setColor(valueColors[i]);
canvas.drawArc(rectF, mStartAngle, currentValue - mStartAngle, false, arcCoverPaint);
} else {// i<poisition 绘制当前i的完整圆环
arcCoverPaint.setColor(valueColors[i]);
float angle = 360 * value[i];
canvas.drawArc(rectF, mStartAngle, angle, false, arcCoverPaint);
mStartAngle += angle;
}
}
}
通过如下方法改变currentValue值:
//初始化动画加载进度
valueAnimator = ValueAnimator.ofFloat(0, 360);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentValue = (Float) valueAnimator.getAnimatedValue();
invalidate(); //动画效果
}
});
3.绘制饼状图中间的文字
private void drawCenterText(Canvas canvas) {
//画中间数值
float baseLine = center - (mCenterTextPaint.getFontMetrics().descent + mCenterTextPaint.getFontMetrics().ascent) / 2;
mCenterTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(centerText, center, baseLine, mCenterTextPaint);
}
三、使用
在Activity中初始化饼状图数据数组或列表,并开启动画效果:
float[] value = {0.1f, 0.4f, 0.2f, 0.3f};
pie_chart_view.initAndStart(value);
上面已经贴出了实现的关键代码,完整demo可以参考:https://github.com/yjchen920927/PieChartView