
要做這么一個(gè)效果,我們應(yīng)該分幾步來(lái)寫(xiě),
1.先做一個(gè)靜態(tài)的餅狀圖
2.然后加上屬性動(dòng)畫(huà),有一個(gè)繪制的過(guò)程(這里有個(gè)難點(diǎn))
3.加上選中效果
第一步:繪制一個(gè)餅狀圖,用canvas的drawArc來(lái)繪制圓弧,代碼如下
private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(255);
canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
}
這里是封裝出來(lái)的方法,mRectF是繪制的矩形區(qū)域,startAngle是開(kāi)始角度,sweepAngle是掃過(guò)的角度也就是你要繪制多少度,true為useCenter,這是你是否采用填充的形式,這里可以自己設(shè)為為false看看效果,mChartPaint是你的畫(huà)筆,需要繪制什么顏色啊采用什么繪制樣式啊等。
第二步:加上屬性動(dòng)畫(huà),有一個(gè)繪制的過(guò)程,代碼如下
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是執(zhí)行動(dòng)畫(huà)的時(shí)候當(dāng)前的角度在0到360之間取值。動(dòng)畫(huà)是加上了,怎么加到繪制餅狀圖的過(guò)程中呢?怎么才能有一個(gè)繪制的動(dòng)畫(huà)過(guò)程呢?看下邊代碼
@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這個(gè)方法暫時(shí)忽略,canvas.drawCircle這是繪制中心的一個(gè)白色的圓,你也可以去掉看效果,mPieModeList這個(gè)集合是裝載的弧形對(duì)象的,我們把每一個(gè)弧形寫(xiě)成一個(gè)model這樣有利于做選中和切換顏色的效果,model的代碼后邊貼,我們可以看到在draw方法里,有一個(gè)for循環(huán),這個(gè)for循環(huán)繪制每一個(gè)弧形(每一個(gè)弧形都有不同的顏色和開(kāi)始角度和掃過(guò)的角度),但是在for循環(huán)里有兩個(gè)判斷,第一個(gè)判斷是:mAnimaAngle>=當(dāng)前model的startAngle并且<=當(dāng)前model的終點(diǎn)角度,也就是說(shuō)只能在當(dāng)前model的開(kāi)始角度和終點(diǎn)角度之間繪制它自己特有的顏色。第二個(gè)判斷是mAnimaAngle>=當(dāng)前model的終點(diǎn)角度,如果動(dòng)畫(huà)執(zhí)行的mAnimaAngle大于當(dāng)前model的終點(diǎn)角度,那么該是什么顏色就繪制什么顏色,如果把這個(gè)判斷去掉的話,會(huì)導(dǎo)致只會(huì)繪制最后一個(gè)model的弧形,前邊的都會(huì)消失。
第三步:繪制選中的區(qū)域,并且讓選中的弧形突出。代碼如下
private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
mChartPaint.setColor(color);
mChartPaint.setAlpha(150);
canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
}
這個(gè)效果我想了半天,怎么讓選中的弧形突出呢?最后還是借鑒了MPAndroidChart庫(kù)的實(shí)現(xiàn)方式,給弧形的繪制區(qū)域多加30或者50不就突出了么,而且選中不是有一個(gè)透明度的效果么,就直接設(shè)置畫(huà)筆的透明度值就好。那么在哪里設(shè)置選中的區(qū)域位置呢?
@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);
}
這里有個(gè)mSelectedRectF.inset(-30,-30)這里為什么要設(shè)置為負(fù)數(shù),因?yàn)樵O(shè)置的參數(shù)如果為正數(shù),則矩形會(huì)向內(nèi)移動(dòng),使矩形變窄,而設(shè)置為負(fù)數(shù),則矩形向外移動(dòng),矩形會(huì)變寬。
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;
/**
* 自定義餅狀圖 第一:可能需要繪制多個(gè)顏色的圖
* <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; // 開(kāi)始繪制的角度
public float sweepAngle; // 掃過(guò)的角度
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);
}
}
其實(shí)多研究別人的開(kāi)源框架能學(xué)到不少封裝的思想。MPAndroidChart可以去github上搜索