现在应用里面集成图表是很常见的事了,简单、直观能给用户更直观的感受,包括支付宝,招商银行App等,虽然有很多第三方的图标库,但是自己实现一个是不是很有成就感?主要优势还是体现在自己可以实现一些特殊需求以及自己实现代码量小,不到150行。现在我们就来一步一步实现一个带动画的圆环图,先放效果图。
初始化一些值
privatestatic final String TAG = "RingView";
private Paint mPaint;
private Context mContext;
//绘制区域宽
private float mWidth;
//绘制区域高
private float mHeight;
//外圆直径
private float mOuterRing;
//颜色
private int[] colors = newint[]{0xFFF9A947, 0xFFe57646, 0xFF1E71BC, 0xFF428BFE};
//绘制区域正方形
private RectF mRectF;
//各绘制角度集合
private List<Float> mListAcr;
//内圈大小
private float mInnerRing;
//绘制的进度
private float mStartAcr = 1f;
public RingView(Context context) {
super(context, null, 0);
}
public RingView(Context context,AttributeSet attrs) {
this(context, attrs, 0);
}
public RingView(Context context,AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mListAcr = new ArrayList<>();
mContext = context;
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
TypedArray typedArray =context.obtainStyledAttributes(attrs, R.styleable.RingView);
mOuterRing =typedArray.getDimension(R.styleable.RingView_outer_ring_radius, 0);
mInnerRing =typedArray.getDimension(R.styleable.RingView_inner_ring_radius, 0);
}
@Override
protected void onMeasure(intwidthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
float left = (mWidth - mOuterRing) / 2;
float top = (mHeight - mOuterRing) / 2;
mRectF = new RectF(left, top, mWidth -left, mHeight - top);
}
布局文件:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.example.sheteng.ringview.RingView
android:layout_width="90dp"
android:id="@+id/ringview"
android:layout_height="90dp"
app:inner_ring_radius="30dp"
app:outer_ring_radius="90dp"
/>
<Button
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="showRing"
android:text="点我"/>
</LinearLayout>
首先是计算部分,计算各个色块占据的角度,保存到一个集合里面
publicvoid initData(List<Float> list) {
mListAcr.clear();
if (list == null) {
return;
}
float total = 0;
for (Float integer : list) {
total += integer;
}
if (total > 0) {
float totalAcr = 0;
for (int i = 0; i < list.size();i++) {
float acr = (list.get(i) /total) * 360;
if (i == list.size() - 1) {
acr = 360 - totalAcr;
}
totalAcr += acr;
mListAcr.add(acr);
}
}
invalidate();
}
接下来考虑怎么画成圆饼,我这里采用的是先画成扇形,再在中间画一个一个小白圆,叠加成为圆环,
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int total = 0;
for (int i = 0; i < mListAcr.size();i++) {
mPaint.setColor(colors[i]);
if (mListAcr.get(i) == 0) {
continue;
}
float endAcr = mListAcr.get(i);
canvas.drawArc(mRectF, 270 + total,endAcr, true, mPaint);
total += mListAcr.get(i);
}
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mWidth / 2, mHeight /2, mInnerRing / 2, mPaint);
}
这样的效果如下,是不是还差点什么?
对,这不是没动画么?动画采用一次绘制比上一次绘制多几个角度,一样一次次绘制下来就形成了动画。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int total = 0;
mStartAcr += 3;
if (mStartAcr > 360) {
mStartAcr = 360;
}
for (int i = 0; i < mListAcr.size();i++) {
mPaint.setColor(colors[i]);
if (mListAcr.get(i) == 0) {
continue;
}
float endAcr = mStartAcr - total;
if (endAcr > mListAcr.get(i)){
endAcr = mListAcr.get(i);
}
canvas.drawArc(mRectF, 270 + total,endAcr, true, mPaint);
if (mStartAcr >= total +mListAcr.get(i)) {
total += mListAcr.get(i);
continue;
} else {
break;
}
}
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mWidth / 2, mHeight /2, mInnerRing / 2, mPaint);
if (mStartAcr < 360) {
invalidate();
}
}
用mStartAcr记录绘制的进度一次多绘制3个角度,效果如下:
有没有觉得这动画好生硬,我们每次都多画3个角度当然生硬了,我们把3换成一个渐变值,让他随着绘制的进度调整值,先变大在变小,这里我用的余弦
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int total = 0;
mStartAcr += getInterpolation(mStartAcr/ 360f);
if (mStartAcr > 360) {
mStartAcr = 360;
}
for (int i = 0; i < mListAcr.size();i++) {
mPaint.setColor(colors[i]);
if (mListAcr.get(i) == 0) {
continue;
}
float endAcr = mStartAcr - total;
if (endAcr > mListAcr.get(i)){
endAcr = mListAcr.get(i);
}
canvas.drawArc(mRectF, 270 + total,endAcr, true, mPaint);
if (mStartAcr >= total +mListAcr.get(i)) {
total += mListAcr.get(i);
continue;
} else {
break;
}
}
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mWidth / 2, mHeight /2, mInnerRing / 2, mPaint);
if (mStartAcr < 360) {
invalidate();
}
}
/**
* 生成插值
*/
public float getInterpolation(float input){
double cos = Math.cos(input * Math.PI);
return (float) ((1f - Math.abs(cos)) *6 + 3f);
}
这样就达到我们前面放的效果图,大功告成了!
完整的项目地址:https://github.com/sheteng63/RingView
期待你们的star,如有问题,敬请指正!