android 圆形统计,CircleStatisticalView

这个博客主要介绍了如何在Android中实现一个圆形统计图,包括自定义View的绘制过程,如背景圆圈、圆点、线条、标记文字等,并通过ValueAnimator实现了动画效果,使得数据变化时图表平滑过渡。
摘要由CSDN通过智能技术生成

import android.animation.AnimatorSet;

import android.animation.TypeEvaluator;

import android.animation.ValueAnimator;

import android.content.Context;

import android.content.res.Resources;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Point;

import android.graphics.RectF;

import android.support.annotation.Nullable;

import android.util.AttributeSet;

import android.util.Log;

import android.view.View;

import java.util.ArrayList;

import java.util.List;

/**

* Created by Relin

* on 2018-08-14.

* 圆形统计图

*/

public class CircleStatisticalView extends View {

/**

* 宽度

*/

private float width;

/**

* 高度

*/

private float height;

/**

* 中心坐标

*/

private float circleX, circleY;

/**

* 半径

*/

private float circleRadius = dpToPx(50);

/**

* 圆圈背景颜色

*/

private int circleBackgroundColor = Color.parseColor("#EDEDED");

/**

* 圆圈宽度

*/

private float circleStrokeWidth = dpToPx(30);

/**

* 圆点边距

*/

private float dotMargin = dpToPx(5);

/**

* 圆点半径

*/

private float dotRadius = dpToPx(5);

/**

* 转角线X差值

*/

private float lineGapX = dpToPx(15);

/**

* 转角线Y差值

*/

private float lineGapY = dpToPx(15);

/**

* 标记线上下的文件间距

*/

private float lineNearTextMargin = dpToPx(5);

/**

* 线条粗细

*/

private float lineStrokeWidth = dpToPx(1.2F);

/**

* 标记文字大小

*/

private float markTextSize = dpToPx(14);

/**

* 标记文字颜色

*/

private int markTextColor = 0;

/**

* 数据

*/

private List statisticalItems;

/**

* 是否使用动画

*/

private boolean isUseAnimation = true;

public CircleStatisticalView(Context context) {

super(context);

init(context, null);

}

public CircleStatisticalView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

public CircleStatisticalView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context, attrs);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

int w = widthSpecSize;

int h = heightSpecSize;

int needHeight = (int) (circleRadius * 2 + dotMargin + dotRadius * 2 + circleStrokeWidth * 2 + lineGapY * 4);

if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {

w = (int) (1.7F * needHeight);

h = needHeight;

} else if (widthSpecMode == MeasureSpec.AT_MOST) {

w = 4 * needHeight / 5;

h = heightSpecSize;

} else if (heightSpecMode == MeasureSpec.AT_MOST) {

w = widthSpecSize;

h = needHeight;

}

setMeasuredDimension(w, h);

//获取最终的宽高

width = getMeasuredWidth();

height = getMeasuredHeight();

circleX = width / 2;

circleY = height / 2;

circleRadius -= getPaddingLeft() - getPaddingRight();

}

/**

* 初始化

*

* @param context

* @param attrs

*/

private void init(Context context, AttributeSet attrs) {

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleStatisticalView);

circleBackgroundColor = array.getColor(R.styleable.CircleStatisticalView_circleBackgroundColor, circleBackgroundColor);

dotMargin = array.getDimension(R.styleable.CircleStatisticalView_dotMargin, dotMargin);

dotRadius = array.getDimension(R.styleable.CircleStatisticalView_dotRadius, dotRadius);

lineGapX = array.getDimension(R.styleable.CircleStatisticalView_lineGapX, lineGapX);

lineGapY = array.getDimension(R.styleable.CircleStatisticalView_lineGapY, lineGapY);

lineNearTextMargin = array.getDimension(R.styleable.CircleStatisticalView_lineNearTextMargin, lineNearTextMargin);

lineStrokeWidth = array.getDimension(R.styleable.CircleStatisticalView_lineStrokeWidth, lineStrokeWidth);

markTextSize = array.getDimension(R.styleable.CircleStatisticalView_markTextSize, markTextSize);

markTextColor = array.getColor(R.styleable.CircleStatisticalView_markTextColor, markTextColor);

statisticalItems = new ArrayList<>();

array.recycle();

}

private int drawCount = 0;

@Override

protected void onDraw(final Canvas canvas) {

super.onDraw(canvas);

drawCircle(canvas);

int size = statisticalItems == null ? 0 : statisticalItems.size();

float startAngle = 0F;

float markAngle;

for (int i = 0; i < size; i++) {

StatisticalItem item = statisticalItems.get(i);

float sweepAngle = item.getPercent() * 360F;

statisticalItems.get(i).setStartAngle(startAngle);

statisticalItems.get(i).setSweepAngle(sweepAngle);

if (!isUseAnimation) {

drawArc(canvas, startAngle - 1F, sweepAngle + 1F, item.getColor());

}

if (i == 0) {

markAngle = sweepAngle;

} else {

markAngle = 2 * (startAngle + sweepAngle / 2);

}

drawMark(canvas, i, item, markAngle);

startAngle += sweepAngle;

}

userAnimationDraw(canvas, size, drawCount);

drawCount++;

}

/**

* 利用动画绘制

*

* @param canvas 画布

* @param size 数据大小

* @param drawCount 绘制次数

*/

private void userAnimationDraw(Canvas canvas, int size, int drawCount) {

//动画绘制

for (int i = 0; i < size; i++) {

StatisticalItem item = statisticalItems.get(i);

Paint paint = new Paint();

paint.setAntiAlias(true);

paint.setColor(item.getColor());

if (item.getSweepAngleAnim() != 0) {

drawArc(canvas, item.getStartAngle() - 1F, item.getSweepAngleAnim() + 1F, item.getColor());

}

//标记线和标记

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(dpToPx(1));

if (item.getGapAnimationPoint() != null) {

canvas.drawLine(item.getDotPoint().x, item.getDotPoint().y, item.getGapAnimationPoint().x, item.getGapAnimationPoint().y, paint);

}

if (item.getLineEndAnimPoint() != null) {

canvas.drawLine(item.getGapPoint().x, item.getGapPoint().y, item.getLineEndAnimPoint().x, item.getLineEndAnimPoint().y, paint);

}

//文字

paint.setStyle(Paint.Style.FILL);

paint.setTextSize(dpToPx(16));

paint.setColor(item.getColor());

if (item.getTopMarkAnimPoint() != null) {

canvas.drawText(item.getTopMarkText(), item.getTopMarkAnimPoint().x, item.getTopMarkAnimPoint().y, paint);

}

if (item.getBottomMarkAnimPoint() != null) {

canvas.drawText(item.getBottomMarkText(), item.getBottomMarkAnimPoint().x, item.getBottomMarkAnimPoint().y, paint);

}

}

//开启动画

if (drawCount == 0) {

for (int i = 0; i < size; i++) {

final StatisticalItem item = statisticalItems.get(i);

ValueAnimator angleAnim = drawArcAnimation(i, item.getStartAngle(), item.getSweepAngle());

ValueAnimator lineStartAnim = drawMarkLineAnimation(i, item.getDotPoint(), item.getGapPoint(), 1);

ValueAnimator lineEndAnim = drawMarkLineAnimation(i, item.getGapPoint(), item.getLineEndPoint(), 2);

ValueAnimator topMarkAnim = drawMarkLineAnimation(i, item.getGapPoint(), item.getTopMarkPoint(), 3);

Point point = new Point();

point.x = item.getBottomMarkPoint().x;

point.y = (int) (item.getBottomMarkPoint().y + dpToPx(30));

ValueAnimator bottomMarkAnim = drawMarkLineAnimation(i, point, item.getBottomMarkPoint(), 4);

//顶部文字延迟500ms

topMarkAnim.setStartDelay(500);

//动画集

AnimatorSet animatorSet = new AnimatorSet();

animatorSet.play(lineStartAnim).with(angleAnim);

animatorSet.play(lineEndAnim).after(lineStartAnim);

animatorSet.play(topMarkAnim).with(lineStartAnim);

animatorSet.play(bottomMarkAnim).with(topMarkAnim);

animatorSet.start();

}

}

}

/**

* 画饼动画

*

* @param position 位置

* @param startAngle 开始弧度

* @param sweepAngle 结束弧度

* @return

*/

public ValueAnimator drawArcAnimation(final int position, final float startAngle, final float sweepAngle) {

final ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator() {

@Override

public Float evaluate(float fraction, Float startValue, Float endValue) {

return fraction * endValue;

}

}, startAngle, sweepAngle);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

statisticalItems.get(position).setSweepAngleAnim((Float) animation.getAnimatedValue());

invalidate();

}

});

valueAnimator.setDuration(500);

valueAnimator.setRepeatCount(0);

return valueAnimator;

}

/**

* 绘制标记和线的动画

*

* @param position 位置

* @param startPoint 开始点

* @param endPoint 结束点

* @param step 步骤

* @return

*/

public ValueAnimator drawMarkLineAnimation(final int position, final Point startPoint, final Point endPoint, final int step) {

final ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator() {

@Override

public Point evaluate(float fraction, Point startValue, Point endValue) {

Point point = new Point();

point.x = (int) (fraction * (endPoint.x - startPoint.x) + startPoint.x);

point.y = (int) (fraction * (endValue.y - startValue.y) + startPoint.y);

return point;

}

}, startPoint, endPoint);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

switch (step) {

case 1:

valueAnimator.setDuration(250);

statisticalItems.get(position).setGapAnimationPoint((Point) animation.getAnimatedValue());

break;

case 2:

valueAnimator.setDuration(500);

statisticalItems.get(position).setLineEndAnimPoint((Point) animation.getAnimatedValue());

break;

case 3:

valueAnimator.setDuration(500);

statisticalItems.get(position).setTopMarkAnimPoint((Point) animation.getAnimatedValue());

break;

case 4:

valueAnimator.setDuration(500);

statisticalItems.get(position).setBottomMarkAnimPoint((Point) animation.getAnimatedValue());

break;

}

invalidate();

}

});

valueAnimator.setRepeatCount(0);

return valueAnimator;

}

/**

* 创建画笔对象

*

* @return

*/

private Paint buildPaint() {

Paint paint = new Paint();

paint.setAntiAlias(true);

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(circleStrokeWidth);

return paint;

}

/**

* 画出背景圆圈

*

* @param canvas 画布

*/

private void drawCircle(Canvas canvas) {

Paint paint = buildPaint();

paint.setColor(circleBackgroundColor);

canvas.drawCircle(circleX, circleY, circleRadius, paint);

}

/**

* 画圆弧

*

* @param canvas 画布

* @param startAngle 开始弧度

* @param sweepAngle 进过的弧度

* @param color 弧面颜色

*/

private void drawArc(Canvas canvas, float startAngle, float sweepAngle, int color) {

Paint paint = buildPaint();

paint.setColor(color);

RectF rectF = new RectF(circleX - circleRadius, circleY - circleRadius, circleX + circleRadius, circleY + circleRadius);

canvas.drawArc(rectF, -90 + startAngle, sweepAngle, false, paint);

}

/**

* 绘制标记

*

* @param canvas 画布

* @param item 标记数据对象

* @param markAngle 标记弧度

*/

private void drawMark(Canvas canvas, int position, StatisticalItem item, float markAngle) {

final Paint paint = new Paint();

paint.setAntiAlias(true);

paint.setStrokeWidth(lineStrokeWidth);

paint.setColor(item.getColor());

//圆点

float dotToCenterLine = circleRadius + dpToPx(20) + dotMargin;

float dotX;

float dotY;

float dotAngle = (float) ((markAngle / 2 * Math.PI / 180));//角度转弧度

if (markAngle / 2 < 180 * 2) {

dotX = (float) (circleX + Math.sin(dotAngle) * dotToCenterLine);

dotY = (float) (circleY - Math.cos(dotAngle) * dotToCenterLine);

} else {

dotX = (float) (circleX - Math.sin(dotAngle) * dotToCenterLine);

dotY = (float) (circleY + Math.cos(dotAngle) * dotToCenterLine);

}

canvas.drawCircle(dotX, dotY, dotRadius, paint);

//线

float lineMiddleX, lineMiddleY, lineEdnX, lineEndY;

if (circleX < dotX) {

lineMiddleX = markAngle / 2 > 45 && markAngle / 2 < 135 ? dotX : dotX + lineGapX;

lineMiddleY = markAngle / 2 > 45 && markAngle / 2 < 135 ? dotY : dotY + (circleY < dotY ? +lineGapY : -lineGapY);

lineEdnX = markAngle / 2 > 45 && markAngle / 2 < 135 ? lineMiddleX + (width - lineMiddleX - getPaddingRight()) : lineMiddleX + (width - lineMiddleX - getPaddingRight());

lineEndY = lineMiddleY;

} else {

lineMiddleX = markAngle / 2 > 225 && markAngle / 2 < 315 ? dotX : dotX - lineGapX;

lineMiddleY = markAngle / 2 > 225 && markAngle / 2 < 315 ? dotY : dotY + (circleY < dotY ? +lineGapY : -lineGapY);

lineEdnX = 0 + getPaddingLeft();

lineEndY = lineMiddleY;

}

if (!isUseAnimation) {

canvas.drawLine(dotX, dotY, lineMiddleX, lineMiddleY, paint);

canvas.drawLine(lineMiddleX, lineMiddleY, lineEdnX, lineEndY, paint);

}

statisticalItems.get(position).setDotPoint(new Point((int) dotX, (int) dotY));

statisticalItems.get(position).setGapPoint(new Point((int) lineMiddleX, (int) lineMiddleY));

statisticalItems.get(position).setLineEndPoint(new Point((int) lineEdnX, (int) lineEndY));

//文字

paint.setColor(item.getColor());

if (markTextColor != 0) {

paint.setColor(markTextColor);

}

if (item.getMarkTextColor() != 0) {

paint.setColor(item.getMarkTextColor());

}

paint.setTextSize(markTextSize);

String topMark = item.getTopMarkText();

String bottomMark = item.getBottomMarkText();

float topX, topY, bottomX, bottomY;

if (circleX < dotX) {

//上部分文字

topX = lineEdnX - paint.measureText(topMark);

topY = lineEndY - lineNearTextMargin;

//下部分文字

bottomX = lineEdnX - paint.measureText(bottomMark);

bottomY = lineEndY + (paint.measureText(bottomMark) / bottomMark.length()) / 2 + lineNearTextMargin * 1.5F + lineStrokeWidth;

} else {

//上部分文字

topX = lineEdnX;

topY = lineEndY - lineNearTextMargin;

//下部分文字

bottomX = lineEdnX;

bottomY = lineEndY + (paint.measureText(bottomMark) / bottomMark.length()) / 2 + lineNearTextMargin * 1.5F + lineStrokeWidth;

}

if (!isUseAnimation) {

canvas.drawText(topMark, topX, topY, paint);

canvas.drawText(bottomMark, bottomX, bottomY, paint);

}

statisticalItems.get(position).setTopMarkPoint(new Point((int) topX, (int) topY));

statisticalItems.get(position).setBottomMarkPoint(new Point((int) bottomX, (int) bottomY));

}

private static float getScreenDensity() {

return Resources.getSystem().getDisplayMetrics().density;

}

private static float dpToPx(float dp) {

return dp * getScreenDensity();

}

public float getCircleRadius() {

return circleRadius;

}

public void setCircleRadius(float circleRadius) {

this.circleRadius = circleRadius;

}

public int getCircleBackgroundColor() {

return circleBackgroundColor;

}

public void setCircleBackgroundColor(int circleBackgroundColor) {

this.circleBackgroundColor = circleBackgroundColor;

}

public float getCircleStrokeWidth() {

return circleStrokeWidth;

}

public void setCircleStrokeWidth(float circleStrokeWidth) {

this.circleStrokeWidth = circleStrokeWidth;

}

public float getDotMargin() {

return dotMargin;

}

public void setDotMargin(float dotMargin) {

this.dotMargin = dotMargin;

}

public float getDotRadius() {

return dotRadius;

}

public void setDotRadius(float dotRadius) {

this.dotRadius = dotRadius;

}

public float getLineGapX() {

return lineGapX;

}

public void setLineGapX(float lineGapX) {

this.lineGapX = lineGapX;

}

public float getLineGapY() {

return lineGapY;

}

public void setLineGapY(float lineGapY) {

this.lineGapY = lineGapY;

}

public float getLineNearTextMargin() {

return lineNearTextMargin;

}

public void setLineNearTextMargin(float lineNearTextMargin) {

this.lineNearTextMargin = lineNearTextMargin;

}

public float getLineStrokeWidth() {

return lineStrokeWidth;

}

public void setLineStrokeWidth(float lineStrokeWidth) {

this.lineStrokeWidth = lineStrokeWidth;

}

public float getMarkTextSize() {

return markTextSize;

}

public void setMarkTextSize(float markTextSize) {

this.markTextSize = markTextSize;

}

public int getMarkTextColor() {

return markTextColor;

}

public void setMarkTextColor(int markTextColor) {

this.markTextColor = markTextColor;

}

public List getStatisticalItems() {

return statisticalItems;

}

public void setStatisticalItems(List statisticalItems) {

int size = statisticalItems == null ? 0 : statisticalItems.size();

float totalPercent = 0;

List newItems = new ArrayList<>();

for (int i = 0; i < size; i++) {

if (statisticalItems.get(i).getPercent() != 0) {

newItems.add(statisticalItems.get(i));

}

}

size = newItems == null ? 0 : newItems.size();

for (int i = 0; i < size; i++) {

StatisticalItem item = newItems.get(i);

totalPercent += item.getPercent();

}

if (totalPercent != 1) {

for (int i = 0; i < size; i++) {

StatisticalItem item = newItems.get(i);

newItems.get(i).setPercent(item.getPercent() / totalPercent);

}

}

this.statisticalItems = newItems;

}

public boolean isUseAnimation() {

return isUseAnimation;

}

public void setUseAnimation(boolean useAnimation) {

isUseAnimation = useAnimation;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值