安卓自定義View之帶動畫的餅狀圖
因爲是金融類的app,包含有很多資產數據,需求是使用餅狀圖展示總資產的不同資產組成,不同資產間有白色間隔線,較大的資產會有放大的動畫效果,整體進行動畫繪製展示,效果如下:
整體代碼如下(全網唯一):
package com.xx.xx.ui.view.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.xx.xx.utils.JRDimensionUtil;
/**
* @ClassName: PieChart
* @Description: 自定義的總資產扇形圖
* @Author yanxu5
* @Date: 2019/5/26
*/
public class PieChart extends View {
private float[] item;// 每一項的值
private float total;// 總共的值
private String[] colors;// 傳過來的顏色
private float[] itemsAngle;// 每一項所佔的角度
private float[] itemsBeginAngle;// 每一項的起始角度
private float mRadius;// 半徑
private float animatedValue;// AnimatedValue
private static final String[] DEFAULT_ITEMS_COLORS = {"#FF8200", "#EF4352"};// 默認顏色
private float mStartX, mStartY, mCenterXY;// 起始值
private Paint mPaint, mLinePaint;// paint
private RectF mOval;// RectF
private int mTagCount;// 大於0的個數
public PieChart(Context context) {
this(context, null);
}
public PieChart(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPieChart();
}
/**
* 初始化
*/
private void initPieChart() {
mRadius = JRDimensionUtil.dp2px(getContext(), 110);
mStartX = JRDimensionUtil.dp2px(getContext(), 110);
mStartY = JRDimensionUtil.dp2px(getContext(), 110);
mCenterXY = mRadius;
float addRadius = JRDimensionUtil.dp2px(getContext(), 5);
float leftTop = 0;
float rightBottom = 2 * mRadius;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(JRDimensionUtil.dp2px(getContext(), 2));// 設置線寬
mLinePaint.setColor(Color.parseColor("#FFFFFF"));
mOval = new RectF(leftTop + addRadius, leftTop + addRadius, rightBottom - addRadius, rightBottom - addRadius);
mOvalMax = new RectF(leftTop, leftTop, rightBottom, rightBottom); // 關鍵思路:逆向思維,實際上先定義的大扇形矩形,再反推的小扇形矩形
}
/**
* 設置每一項的值
*
* @param item item
*/
public void setItem(float[] item) {
if (item != null && item.length > 0) {
calculateTotal();
refreshItemsAngles();
colors = DEFAULT_ITEMS_COLORS;
}
}
/**
* 初始化所有
*
* @param item item
* @param colors 顏色
*/
public void initSrc(float[] item, String[] colors) {
this.item = item;
mTagCount = 0;// 每次初始化重置個數
for (float anItem : item) {
if (anItem > 0) mTagCount++;
}
calculateTotal();
if (total > 0) {
setItem(item);
setColors(colors);
notifyDraw();
} else if (total == 0) {
for (int i = 0; i < item.length; i++) {
item[i] = 1;// 當爲0的時候將不會畫圓
}
initSrc(item, colors);
}
}
/**
* 設置每一項的顏色
*
* @param colors 顏色
*/
public void setColors(String[] colors) {
if (colors != null && colors.length > 0) {
this.colors = colors;
}
}
/**
* onDraw方法
*
* @param canvas canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (item == null || item.length == 0) {// 如無數據
// 畫外圓
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.parseColor("#F5F5F5"));
canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 105), mPaint);
// 畫內圓
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.parseColor("#FFFFFF"));
canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 85), mPaint);
} else {
// 畫扇形
for (int i = 0; i < item.length; i++) {
mPaint.setColor(Color.parseColor(colors[i]));
if (Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]) >= 0) {
canvas.drawArc(mOval, itemsBeginAngle[i], Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]), true, mPaint);
}
}
// 畫變大的扇形 //TODO 佔比較大模塊放大的效果暫時取消
/*if (animatedValue == 360 && !isEquality(item)) {
int i = compareNumber(item);
mPaint.setColor(Color.parseColor(colors[i]));
canvas.drawArc(mOvalMax, itemsBeginAngle[i], Math.min(itemsAngle[i], animatedValue - 90 - itemsBeginAngle[i]), true, mPaint);
}*/
// 畫間隔白線
for (int i = 0; i < item.length; i++) {
if (item.length > 1 && mTagCount > 1)
canvas.drawLine(mStartX, mStartY, mStartX + (float) Math.sin(Math.toRadians(90 + itemsBeginAngle[i])) * mStartX, mStartY - (float) Math.cos(Math.toRadians(90 + itemsBeginAngle[i])) * mStartY, mLinePaint);
}
// 畫內圓
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.parseColor("#FFFFFF"));
canvas.drawCircle(mCenterXY, mCenterXY, JRDimensionUtil.dp2px(getContext(), 85), mPaint);
}
}
/**
* 開始繪製
*/
public void startDraw() {
if (item != null && item.length > 0 && colors != null && colors.length > 0) {
initAnimator();
}
}
/**
* 初始化動畫
*/
private void initAnimator() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
anim.setDuration(1500);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
animatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.start();
}
/**
* 計算總數
*/
private void calculateTotal() {
total = 0;
for (float i : item) {
total += i;
}
}
/**
* 根據每個item的大小,獲得item所佔的角度和起始角度
*/
private void refreshItemsAngles() {
if (item != null && item.length > 0) {
float[] itemsRate = new float[item.length];// 每一塊佔的比例
itemsBeginAngle = new float[item.length];// 每一個角度臨界點
itemsAngle = new float[item.length];// 每一個角度臨界點
float beginAngle = -90;// 初始角度-90度
for (int i = 0; i < item.length; i++) {
itemsRate[i] = (float) (item[i] * 1.0 / total * 1.0);
}
for (int i = 0; i < itemsRate.length; i++) {
if (i >= 1) {
beginAngle = 360 * itemsRate[i - 1] + beginAngle;
}
itemsBeginAngle[i] = beginAngle;
itemsAngle[i] = 360 * itemsRate[i];
}
}
}
/**
* 通知開始繪畫
*/
public void notifyDraw() {
invalidate();
}
/**
* 控件可獲得的空間
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
float widthHeight = 2 * (mRadius);
setMeasuredDimension((int) widthHeight, (int) widthHeight);
}
/**
* 判斷各組成是否相等
*
* @param item 資產組成
* @return 是否相等
*/
private boolean isEquality(float[] item) {
for (int i = 1; i < item.length; i++) {
if (item[i] != item[0]) {
return false;
}
}
return true;
}
/**
* 返回最大值的索引
*
* @param item 資產組成
* @return 最大的資產的角標
*/
private int compareNumber(float[] item) {
float max = item[0];
int maxPosition = 0;
for (int i = 1; i < item.length; i++) {
if (item[i] > max) {
max = item[i];
maxPosition = i;
}
}
return maxPosition;
}
}
調用方法也很簡單
mPieChartView.initSrc(ratios, colors);
mPieChartView.startDraw();