要做这么一个效果,我们应该分几步来写,
1.先做一个静态的饼状图
2.然后加上属性动画,有一个绘制的过程(这里有个难点)
3.加上选中效果
第一步:绘制一个饼状图,用canvas的drawArc来绘制圆弧,代码如下
private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(255);
canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
}
这里是封装出来的方法,mRectF是绘制的矩形区域,startAngle是开始角度,sweepAngle是扫过的角度也就是你要绘制多少度,true为useCenter,这是你是否采用填充的形式,这里可以自己设为为false看看效果,mChartPaint是你的画笔,需要绘制什么颜色啊采用什么绘制样式啊等。
第二步:加上属性动画,有一个绘制的过程,代码如下
public void startAnima() {
final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0f, 360f);
mValueAnimator.setDuration(3 * 1000);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mAnimaAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
这里的mAnimaAngle是执行动画的时候当前的角度在0到360之间取值。动画是加上了,怎么加到绘制饼状图的过程中呢?怎么才能有一个绘制的动画过程呢?看下边代码
@Override protected void onDraw(Canvas canvas) {
if (mPieModelList == null || mPieModelList.isEmpty()) {
return;
}
for (int i = 0; i < mPieModelList.size(); i++) {
if (mPieModelList.get(i).percent > 0) {
if (mAnimaAngle >= mPieModelList.get(i).startAngle && mAnimaAngle <= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mAnimaAngle - mPieModelList.get(i).startAngle);
} else if (mAnimaAngle >= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
}
if (mPieModelList.get(i).selected) {
drawSelectedView(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
}
}
}
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredWidth() / 2, padding, mCirclePaint);
}
private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(255);
canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
}
private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(150);
canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
}
drawSelectedView这个方法暂时忽略,canvas.drawCircle这是绘制中心的一个白色的圆,你也可以去掉看效果,mPieModeList这个集合是装载的弧形对象的,我们把每一个弧形写成一个model这样有利于做选中和切换颜色的效果,model的代码后边贴,我们可以看到在draw方法里,有一个for循环,这个for循环绘制每一个弧形(每一个弧形都有不同的颜色和开始角度和扫过的角度),但是在for循环里有两个判断,第一个判断是:mAnimaAngle>=当前model的startAngle并且<=当前model的终点角度,也就是说只能在当前model的开始角度和终点角度之间绘制它自己特有的颜色。第二个判断是mAnimaAngle>=当前model的终点角度,如果动画执行的mAnimaAngle大于当前model的终点角度,那么该是什么颜色就绘制什么颜色,如果把这个判断去掉的话,会导致只会绘制最后一个model的弧形,前边的都会消失。
第三步:绘制选中的区域,并且让选中的弧形突出。代码如下
private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(150);
canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
}
这个效果我想了半天,怎么让选中的弧形突出呢?最后还是借鉴了MPAndroidChart库的实现方式,给弧形的绘制区域多加30或者50不就突出了么,而且选中不是有一个透明度的效果么,就直接设置画笔的透明度值就好。那么在哪里设置选中的区域位置呢?
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
padding = w / 6;
mRectF = new RectF(padding, padding, w - padding, w - padding);
mSelectedRectF.set(mRectF);
mSelectedRectF.inset(-30, -30);
}
这里有个mSelectedRectF.inset(-30,-30)这里为什么要设置为负数,因为设置的参数如果为正数,则矩形会向内移动,使矩形变窄,而设置为负数,则矩形向外移动,矩形会变宽。
OK,饼状图全部分析完毕,最后是全部代码
package cms.chart.demo.view;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import java.util.List;
import cms.chart.demo.bean.PieModel;
/**
* 自定义饼状图 第一:可能需要绘制多个颜色的图
* <p>
* Created by 54966 on 2018/2/27.
*/
public class PieChartView extends View {
private Paint mChartPaint;
private Paint mCirclePaint; // 中心圆
private RectF mRectF;
private int padding;
private List<PieModel> mPieModelList;
private float mAnimaAngle;
private RectF mSelectedRectF = new RectF();
public PieChartView(Context context) {
this(context, null);
}
public PieChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PieChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mChartPaint = new Paint();
mChartPaint.setAntiAlias(true);
mChartPaint.setDither(true);
mChartPaint.setStrokeWidth(100);
mChartPaint.setStyle(Paint.Style.FILL);
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setColor(Color.WHITE);
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override protected void onDraw(Canvas canvas) {
if (mPieModelList == null || mPieModelList.isEmpty()) {
return;
}
for (int i = 0; i < mPieModelList.size(); i++) {
if (mPieModelList.get(i).percent > 0) {
if (mAnimaAngle >= mPieModelList.get(i).startAngle &&
mAnimaAngle <= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mAnimaAngle - mPieModelList.get(i).startAngle);
} else if (mAnimaAngle >= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
}
if (mPieModelList.get(i).selected) {
drawSelectedView(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
}
}
}
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredWidth() / 2, padding, mCirclePaint);
}
private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(255);
canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
}
private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(150);
canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
}
public void startAnima() {
final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0f, 360f);
mValueAnimator.setDuration(3 * 1000);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
mAnimaAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
public void setData(List<PieModel> pieModelList) {
this.mPieModelList = pieModelList;
for (int i = 0; i < mPieModelList.size(); i++) {
PieModel model = mPieModelList.get(i);
if (i == 0) {
model.startAngle = 0;
} else {
model.startAngle = mPieModelList.get(i - 1).startAngle + mPieModelList.get(i - 1).sweepAngle;
}
model.sweepAngle = (model.percent * 360);
}
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
padding = w / 6;
mRectF = new RectF(padding, padding, w - padding, w - padding);
mSelectedRectF.set(mRectF);
mSelectedRectF.inset(-30, -30);
}
}
package cms.chart.demo;
import java.util.ArrayList;
import java.util.List;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import cms.chart.demo.bean.PieModel;
import cms.chart.demo.utils.ColorRandom;
import cms.chart.demo.view.PieChartView;
public class MainActivity extends AppCompatActivity {
private PieChartView id_pie_chart;
private TextView id_tv_1, id_tv_2, id_tv_3, id_tv_4;
private List<PieModel> pieModelList = new ArrayList<>();
private List<Integer> colorList = new ArrayList<>();
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
id_tv_1 = findViewById(R.id.id_tv_1);
id_tv_2 = findViewById(R.id.id_tv_2);
id_tv_3 = findViewById(R.id.id_tv_3);
id_tv_4 = findViewById(R.id.id_tv_4);
id_pie_chart = findViewById(R.id.id_pie_chart);
ColorRandom colorRandom = new ColorRandom(10);
for (int i = 0; i < 5; i++) {
int colors = (int) colorRandom.getColors().get(i);
if (i == 0) {
pieModelList.add(new PieModel(colors, 0.1f));
} else {
pieModelList.add(new PieModel(colors, 0.3f));
}
}
id_pie_chart.setData(pieModelList);
id_pie_chart.startAnima();
List<Double> mList = new ArrayList<>();
mList.add(0.25);
mList.add(0.35);
mList.add(0.15);
mList.add(0.05);
mList.add(0.20);
id_tv_1.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (pieModelList.get(0).selected) {
pieModelList.get(0).selected = false;
} else {
pieModelList.get(0).selected = true;
}
id_pie_chart.setData(pieModelList);
id_pie_chart.invalidate();
}
});
id_tv_2.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (pieModelList.get(1).selected) {
pieModelList.get(1).selected = false;
} else {
pieModelList.get(1).selected = true;
}
id_pie_chart.setData(pieModelList);
id_pie_chart.invalidate();
}
});
id_tv_3.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (pieModelList.get(2).selected) {
pieModelList.get(2).selected = false;
} else {
pieModelList.get(2).selected = true;
}
id_pie_chart.setData(pieModelList);
id_pie_chart.invalidate();
}
});
id_tv_4.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (pieModelList.get(3).selected) {
pieModelList.get(3).selected = false;
} else {
pieModelList.get(3).selected = true;
}
id_pie_chart.setData(pieModelList);
id_pie_chart.invalidate();
}
});
}
}
package cms.chart.demo.bean;
/**
* Created by 54966 on 2018/2/28.
*/
public class PieModel {
public float startAngle; // 开始绘制的角度
public float sweepAngle; // 扫过的角度
public int color; // 显示的颜色
public float percent; // 所占百分比
public boolean selected; // true为选中
public PieModel(int color, float percent) {
this.color = color;
this.percent = percent;
}
public PieModel(int color, float percent, boolean selected) {
this.color = color;
this.percent = percent;
this.selected = selected;
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffffff"
tools:context="cms.chart.demo.MainActivity">
<cms.chart.demo.view.PieChartView
android:id="@+id/id_pie_chart"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerHorizontal="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/id_pie_chart"
android:orientation="horizontal">
<TextView
android:id="@+id/id_tv_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="选中1" />
<TextView
android:id="@+id/id_tv_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="选中2" />
<TextView
android:id="@+id/id_tv_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="选中3" />
<TextView
android:id="@+id/id_tv_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="选中4" />
</LinearLayout>
</RelativeLayout>
package com.qfdqc.views.demo.utils;
import java.util.ArrayList;
import java.util.Random;
import android.graphics.Color;
/**
* Created by 54966 on 2018/3/8.
*/
public class ColorRandom {
private ArrayList<Integer> colorArrays;
private int count;
public ColorRandom(int count) {
colorArrays = new ArrayList<>(count);
this.count = count;
setColor();
}
private void setColor() {
for (int i = 0; i < count; i++) {
int color = getColor();
colorArrays.add(color);
}
}
private Integer getColor() {
int color = Color.parseColor("#FFA500");
while (colorArrays.contains(color) || "#FFFFFF".equals(color)) {
color = getRandColorCode();
if (!colorArrays.contains(color)) {
break;
}
}
return color;
}
public ArrayList getColors() {
return colorArrays;
}
private Integer getRandColorCode() {
String r, g, b;
Random random = new Random();
r = Integer.toHexString(random.nextInt(256)).toUpperCase();
g = Integer.toHexString(random.nextInt(256)).toUpperCase();
b = Integer.toHexString(random.nextInt(256)).toUpperCase();
r = r.length() == 1 ? "0" + r : r;
g = g.length() == 1 ? "0" + g : g;
b = b.length() == 1 ? "0" + b : b;
return Color.parseColor("#" + r + g + b);
}
}
其实多研究别人的开源框架能学到不少封装的思想。MPAndroidChart可以去github上搜索
网友评论