废话不多说,先直接上最终的效果图(完整代码在底部)
我把这个view拆分理解,主要的思路如下:
1.通过 canvas.drawArc()方法画出单个扇形,再根据起始角度和偏移角度不断的添加扇形,最终实现整个一个圆形。
2.添加动画效果,使得圆形的展开是一个渐进的过程,不是一下子就展现。
3.填上文字
上面3点就是主要的思路。
有了思路,就动手撸代码了,。
RectF rectf = new RectF(-100, -100, 100, 100);
canvas.drawArc(rectf, 0,36, true, mPaint); //画扇形
画完整的圆形的重点在于上面一段代码,理解了这段代码就好操作多了。上述代码得到的效果如下
Step 1. 画完整的圆形 (需要先将canvas的原点,移到屏幕中心)
if (null == mDatas) {
return;
}
float currentStartAngle = mStartAngle; //当前起始角度
canvas.translate(mWith, mHight); //移到中心位置
mRadius = (float) (Math.min(mWith, mHight) * 0.8); //设定圆的半径大小
RectF rectf = new RectF(-mRadius, -mRadius, mRadius, mRadius);
for (int i = 0; i < mDatas.size(); i++) { //通过循环,不断的拼接扇形
canvas.drawArc(rectf, currentStartAngle, mDatas.get(i).getAngle(), true, mPaint); //画扇形
mPaint.setColor(colors[i % colors.length]);
currentStartAngle += mDatas.get(i).getAngle(); //下一个的扇形起始角度
}
代码跑完的效果如下:
Step 2. 添加动画效果
首先我们需要定义一下动画的效果,动画完成的时间为3秒,并且是线性的
/**
* 添加动画效果
*/
public void initAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
anim.setDuration(3000);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.start();
}
定义好了动画效果之后,我们要在画扇形部分的代码做些稍微的变动。也就是第一步的画扇形的代码部分
if (animatedValue - currentStartAngle > 0) {
canvas.drawArc(rectf, currentStartAngle, animatedValue - currentStartAngle, true, mPaint);
drawText(canvas, currentStartAngle + mDatas.get(i).getAngle() / 2, mDatas.get(i));
}
这样动画的效果就可以了(没有做成gif图片,所以就将就的看着.)
Step 3. 添加文字
添加文字,我这里偷了下懒,直接通过canvas.drawTextOnPath()方法来实现,这样做的最终结果就是画上去的字是围着圆绕的,并不是端正的。如果有这个需求,可以通过canvas.drawText()方法来实现,就比如圆中心的hello字符串就是通过如下代码
canvas.drawText("hello", 0, 0, textPain);
整个添加文字的代码如下,在setp1的for循环里面调用即可。
/**
* 在当前要画文字的饼图中间文字
*
* @param starAngle 起始角度
* @param pieData 当前要画文字的饼图数据
*/
public void drawText(Canvas canvas, float starAngle, PieData pieData) {
Path path = new Path();
path.addArc(new RectF(-mRadius, -mRadius, mRadius, mRadius), starAngle, pieData.getAngle());
canvas.drawTextOnPath(pieData.getName() + " /" + (int) (pieData.getPercentage() * 100) + "%", path, -10, -10, textPain);
canvas.drawText("hello", 0, 0, textPain); //在圆点写文字
}
最终的效果就完成了,效果参见首张图,接下来附上完整的代码:
package com.example.five.demotest.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.example.five.demotest.bean.PieData;
import java.util.ArrayList;
/**
* 饼状图
*/
public class PieView extends View {
private int[] colors = {Color.RED, Color.YELLOW, Color.GREEN, Color.GRAY}; //初始化颜色
private float mStartAngle = 0; //初始化绘制角度
private ArrayList mDatas = new ArrayList<>(); //数据源
private int mWith, mHight; //宽和高
private Paint mPaint = new Paint(); //初始化画笔
private Context mContext;
private float animatedValue = 0;
private float mRadius; //圆的半径
private Paint textPain = new Paint();
public PieView(Context context) {
this(context, null);
}
public PieView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* 初始化控件
*/
private void initView() {
mPaint.setStyle(Paint.Style.FILL); //画笔设置为填充
mPaint.setAntiAlias(true); //设置为抗锯齿
textPain.setStyle(Paint.Style.FILL); //画笔设置为填充
textPain.setAntiAlias(true); //设置为抗锯齿
textPain.setTextSize(20);
initAnimation();
}
/**
* 绘画方法
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mDatas) {
return;
}
float currentStartAngle = mStartAngle; //当前起始角度
canvas.translate(mWith, mHight); //移到中心位置
mRadius = (float) (Math.min(mWith, mHight) * 0.8); //设定圆的半径大小
RectF rectf = new RectF(-mRadius, -mRadius, mRadius, mRadius);
for (int i = 0; i < mDatas.size(); i++) {
if (animatedValue - currentStartAngle > 0) {
canvas.drawArc(rectf, currentStartAngle, animatedValue - currentStartAngle, true, mPaint);
drawText(canvas, currentStartAngle + mDatas.get(i).getAngle() / 2, mDatas.get(i));
}
mPaint.setColor(colors[i % colors.length]);
currentStartAngle += mDatas.get(i).getAngle();
}
}
/**
* 在当前要画文字的饼图中间文字
*
* @param starAngle 起始角度
* @param pieData 当前要画文字的饼图数据
*/
public void drawText(Canvas canvas, float starAngle, PieData pieData) {
Path path = new Path();
path.addArc(new RectF(-mRadius, -mRadius, mRadius, mRadius), starAngle, pieData.getAngle());
canvas.drawTextOnPath(pieData.getName() + " /" + (int) (pieData.getPercentage() * 100) + "%", path, -10, -10, textPain);
canvas.drawText("hello", 0, 0, textPain);
}
/**
* 添加动画效果
*/
public void initAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
anim.setDuration(3000);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.start();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHight = h / 2;
mWith = w / 2;
}
/**
* 设置起始位置
*
* @param startAngle
*/
public void setmStartAngle(int startAngle) {
this.mStartAngle = startAngle;
invalidate(); //刷新视图
}
/**
* 设置数据
*
* @param datas
*/
public void setmDatas(ArrayList datas) {
this.mDatas.clear();
this.mDatas.addAll(datas);
initDatas();
invalidate();
}
private void initDatas() {
if (mDatas == null || mDatas.size() == 0)
return;
float sumValue = 0;
for (int i = 0; i < mDatas.size(); i++) { //获取总的数据和
PieData pieData = mDatas.get(i);
sumValue += pieData.getValue();
int j = i % colors.length;
mDatas.get(i).setColor(colors[j]); //更改数据值的颜色
}
//设置每个扇形的角度值
for (int i = 0; i < mDatas.size(); i++) {
PieData pieData = mDatas.get(i);
mDatas.get(i).setPercentage(pieData.getValue() / sumValue);
mDatas.get(i).setAngle(pieData.getValue() / sumValue * 360);
}
}
}
pie的数据实体类代码如下:
package com.example.five.demotest.bean;
/**
* 饼状图的javabean
*/
public class PieData {
private String name;//名称
private float value;//数值
private float percentage; //百分比
private int color; //颜色
private float angle; //角度
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public float getPercentage() {
return percentage;
}
public void setPercentage(float percentage) {
this.percentage = percentage;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
public PieData(String name, float value) {
this.name = name;
this.value = value;
}
}