录制的gif图有点渣,但是实际效果还是可以的。
因为项目需要绘制饼状图,就很简单的饼状图带一个选中的效果,本来也想使用MPAndroidChart这个库的,但是觉得就实现一个效果引用整个库,对于一个有态度的程序员来说,内心是拒绝的(我不是装逼的人),所以自己自定义了一个,写的不好仅供参考。
要做这么一个效果,我们应该分几步来写,
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>
其实多研究别人的开源框架能学到不少封装的思想。MPAndroidChart可以去github上搜索