一、前言
上一篇介绍上自定义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的构造方法中获得自定义的属性,主要代码如下:
4.初始化画笔及一些必要变量,代码如下: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(); }
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