因项目需要,需要加入蛛网图的显示,虽然GitHub上早已有很多大神的nb框架,但是还是想自己写写练练手。
项目中显示的蛛网图也比较简单,只需要控制四个进度值,这样就不需要考虑角度旋转的问题了 ,只需要控制上下左右四个进度值得变化就可以了,也就是只取上下左右四个进度值的点就可以了。
首先先获取各个重要点位信息:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//初始化各边信息 获取各方向位置点起始位置信息
left = leftRightPadding;
top = topBottomPadding;
right = w - leftRightPadding;
bottom = h - topBottomPadding;
if (centerPoint == null) {
centerPoint = new PointF();
}
centerPoint.x = w / 2;
centerPoint.y = h / 2;
progressSpaceX = (centerPoint.x - left)/4;
progressSpaceY = (centerPoint.y - top)/4;
if (shader == null) {
//初始化渐变色背景
shader = new LinearGradient(centerPoint.x,top,centerPoint.x,bottom,Color.parseColor("#18C2B1"),
Color.parseColor("#FDA23E"), Shader.TileMode.MIRROR);
}
setProgressValue();
}
然后实现蛛网图的刻度效果,主要代码
//画横竖两条背景线
path.moveTo(left,centerPoint.y);
path.lineTo(right,centerPoint.y);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#cccccc"));
paint.setStrokeWidth(3);
canvas.drawPath(path,paint);
path.reset();
path.moveTo(centerPoint.x,top);
path.lineTo(centerPoint.x,bottom);
canvas.drawPath(path,paint);
//画蛛网线
paint.setShader(shader);
paint.setStrokeWidth(3);
for (int i = 0; i < 4; i++) {
path.reset();
path.moveTo(left + progressSpaceX * i,centerPoint.y);
path.lineTo(centerPoint.x,top + progressSpaceY * i);
path.lineTo(right - progressSpaceX * i,centerPoint.y);
path.lineTo(centerPoint.x,bottom - progressSpaceY * i);
path.lineTo(left + progressSpaceX * i,centerPoint.y);
canvas.drawPath(path,paint);
paint.setStrokeWidth(2);
}
然后画个进度标题(示例代码写死,实际应用可自定义标题):
//画标题
paint.setShader(null);
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
paint.setTextAlign(Paint.Align.CENTER);
paint.getTextBounds("节能",0,2,rect);
//计算文字高度 左右标题居中显示
float de = rect.height();
canvas.drawText("节能",centerPoint.x, topBottomPadding /2,paint);
canvas.drawText("灾害",centerPoint.x,bottom + topBottomPadding /2,paint);
canvas.drawText("卫生",leftRightPadding / 2,centerPoint.y + de/2,paint);
canvas.drawText("效能",leftRightPadding / 2 + right,centerPoint.y + de/2,paint);
之后画进度值:
//画四个方向进度圆点
paint.setColor(Color.parseColor("#00BDAA"));
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(topPoint.x,topPoint.y,pointCircleRadius,paint);
canvas.drawCircle(leftPoint.x,leftPoint.y,pointCircleRadius,paint);
canvas.drawCircle(rightPoint.x,rightPoint.y,pointCircleRadius,paint);
canvas.drawCircle(bottomPoint.x,bottomPoint.y,pointCircleRadius,paint);
//画四个方向进度区域
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path.reset();
path.moveTo(leftPoint.x,leftPoint.y);
path.lineTo(topPoint.x,topPoint.y);
path.lineTo(rightPoint.x,rightPoint.y);
path.lineTo(bottomPoint.x,bottomPoint.y);
path.lineTo(leftPoint.x,leftPoint.y);
canvas.drawPath(path,paint);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor("#b355C7C2"));
canvas.drawPath(path,paint);
最后加上动态效果:
/**
* 启动动画
* @param topProgress 上方进度值
* @param rightProgress 右方进度值
* @param bottomProgress 底部进度值
* @param leftProgress 左方进度值
*/
private void startAnimator(final double topProgress,final double rightProgress,final double bottomProgress, final double leftProgress){
ValueAnimator topAnimator = ValueAnimator.ofInt(0, 100);
topAnimator.setDuration(2000);
topAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer v = (Integer) animation.getAnimatedValue();
DayHealthProgressView.this.topProgress = v * topProgress/100 ;
DayHealthProgressView.this.rightProgress = v* rightProgress/100 ;
DayHealthProgressView.this.bottomProgress = v* bottomProgress/100 ;
DayHealthProgressView.this.leftProgress = v* leftProgress/100 ;
setProgressValue();
invalidate();
}
});
topAnimator.start();
}
最终效果如下:
完整代码:
package com.zc.tt;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.blankj.utilcode.util.ConvertUtils;
/**
* 自定义每日健康蛛网图
* @author zc
*/
public class DayHealthProgressView extends View {
/**画笔*/
private Paint paint;
private Path path;
private Rect rect;
/**
* 控制四个点信息,中心点、上方进度值点、下方进度值点、左方进度值点、右方进度值点
*/
private PointF centerPoint,topPoint, bottomPoint,leftPoint,rightPoint;
/**蛛网图各方向起始位置*/
private float left,right,top,bottom;
/**上下间距*/
private float topBottomPadding;
/**左右间距*/
private float leftRightPadding;
/**左右进度值单位长度、上下进度值单位长度*/
private float progressSpaceX,progressSpaceY;
/**背景shader*/
private LinearGradient shader;
/**字体大小*/
private float textSize;
/**字体颜色*/
private int textColor;
/**上方进度值*/
private double topProgress;
/**下方进度值*/
private double bottomProgress;
/**左方进度值*/
private double leftProgress;
/**右方进度值*/
private double rightProgress;
/**中心店圆的Radius*/
private float pointCircleRadius;
public DayHealthProgressView(Context context) {
super(context);
init();
}
public DayHealthProgressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//初始化各边信息 获取各方向位置点起始位置信息
left = leftRightPadding;
top = topBottomPadding;
right = w - leftRightPadding;
bottom = h - topBottomPadding;
if (centerPoint == null) {
centerPoint = new PointF();
}
centerPoint.x = w / 2;
centerPoint.y = h / 2;
progressSpaceX = (centerPoint.x - left)/4;
progressSpaceY = (centerPoint.y - top)/4;
if (shader == null) {
//初始化渐变色背景
shader = new LinearGradient(centerPoint.x,top,centerPoint.x,bottom,Color.parseColor("#18C2B1"),
Color.parseColor("#FDA23E"), Shader.TileMode.MIRROR);
}
setProgressValue();
}
/**
* 初始化
*/
private void init(){
if (topPoint == null) {
topPoint = new PointF();
}
if (leftPoint == null) {
leftPoint = new PointF();
}
if (bottomPoint == null) {
bottomPoint = new PointF();
}
if (rightPoint == null) {
rightPoint = new PointF();
}
if (centerPoint == null) {
centerPoint = new PointF();
}
paint = new Paint();
path = new Path();
paint.setAntiAlias(true);
//上下间距和左右间距不一样 左右间距过小会导致文字信息显示不全
topBottomPadding = ConvertUtils.dp2px(30);
leftRightPadding = ConvertUtils.dp2px(36);
textSize = ConvertUtils.sp2px(16);
textColor = Color.parseColor("#333333");
rect = new Rect();
pointCircleRadius = ConvertUtils.dp2px(3);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
//画横竖两条线
path.moveTo(left,centerPoint.y);
path.lineTo(right,centerPoint.y);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#cccccc"));
paint.setStrokeWidth(3);
canvas.drawPath(path,paint);
path.reset();
path.moveTo(centerPoint.x,top);
path.lineTo(centerPoint.x,bottom);
canvas.drawPath(path,paint);
//画蛛网线
paint.setShader(shader);
paint.setStrokeWidth(3);
for (int i = 0; i < 4; i++) {
path.reset();
path.moveTo(left + progressSpaceX * i,centerPoint.y);
path.lineTo(centerPoint.x,top + progressSpaceY * i);
path.lineTo(right - progressSpaceX * i,centerPoint.y);
path.lineTo(centerPoint.x,bottom - progressSpaceY * i);
path.lineTo(left + progressSpaceX * i,centerPoint.y);
canvas.drawPath(path,paint);
paint.setStrokeWidth(2);
}
//画标题
paint.setShader(null);
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
paint.setTextAlign(Paint.Align.CENTER);
paint.getTextBounds("节能",0,2,rect);
//计算文字高度 左右标题居中显示
float de = rect.height();
canvas.drawText("节能",centerPoint.x, topBottomPadding /2,paint);
canvas.drawText("灾害",centerPoint.x,bottom + topBottomPadding /2,paint);
canvas.drawText("卫生",leftRightPadding / 2,centerPoint.y + de/2,paint);
canvas.drawText("效能",leftRightPadding / 2 + right,centerPoint.y + de/2,paint);
//画四个方向进度圆点
paint.setColor(Color.parseColor("#00BDAA"));
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(topPoint.x,topPoint.y,pointCircleRadius,paint);
canvas.drawCircle(leftPoint.x,leftPoint.y,pointCircleRadius,paint);
canvas.drawCircle(rightPoint.x,rightPoint.y,pointCircleRadius,paint);
canvas.drawCircle(bottomPoint.x,bottomPoint.y,pointCircleRadius,paint);
//画四个方向进度区域
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path.reset();
path.moveTo(leftPoint.x,leftPoint.y);
path.lineTo(topPoint.x,topPoint.y);
path.lineTo(rightPoint.x,rightPoint.y);
path.lineTo(bottomPoint.x,bottomPoint.y);
path.lineTo(leftPoint.x,leftPoint.y);
canvas.drawPath(path,paint);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor("#b355C7C2"));
canvas.drawPath(path,paint);
}
/**
* 确定进度圆点值
*/
private void setProgressValue() {
if (topPoint == null) {
topPoint = new PointF();
}
if (leftPoint == null) {
leftPoint = new PointF();
}
if (bottomPoint == null) {
bottomPoint = new PointF();
}
if (rightPoint == null) {
rightPoint = new PointF();
}
topPoint.y = (float) (((centerPoint.y - top) * (100 - topProgress)) / 100 + top);
topPoint.x = centerPoint.x;
bottomPoint.y = (float) ((bottomProgress * (centerPoint.y - topBottomPadding))/100 + centerPoint.y);
bottomPoint.x = centerPoint.x;
leftPoint.x = (float) (((centerPoint.x - left) * (100 - leftProgress)) / 100 + left);
leftPoint.y = centerPoint.y;
rightPoint.x = (float) ((rightProgress * (centerPoint.x - leftRightPadding))/100 + centerPoint.x);
rightPoint.y = centerPoint.y;
}
public void setProgress(double topProgress,double rightProgress,double bottomProgress,double leftProgress){
startAnimator(topProgress,rightProgress,bottomProgress,leftProgress);
}
/**
* 启动动画
* @param topProgress 上方进度值
* @param rightProgress 右方进度值
* @param bottomProgress 底部进度值
* @param leftProgress 左方进度值
*/
private void startAnimator(final double topProgress,final double rightProgress,final double bottomProgress, final double leftProgress){
ValueAnimator topAnimator = ValueAnimator.ofInt(0, 100);
topAnimator.setDuration(2000);
topAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer v = (Integer) animation.getAnimatedValue();
DayHealthProgressView.this.topProgress = v * topProgress/100 ;
DayHealthProgressView.this.rightProgress = v* rightProgress/100 ;
DayHealthProgressView.this.bottomProgress = v* bottomProgress/100 ;
DayHealthProgressView.this.leftProgress = v* leftProgress/100 ;
setProgressValue();
invalidate();
}
});
topAnimator.start();
}
public double getTopProgress() {
return topProgress;
}
public void setTopProgress(double topProgress) {
this.topProgress = topProgress;
setProgressValue();
invalidate();
}
public double getBottomProgress() {
return bottomProgress;
}
public void setBottomProgress(double bottomProgress) {
this.bottomProgress = bottomProgress;
setProgressValue();
invalidate();
}
public double getLeftProgress() {
return leftProgress;
}
public void setLeftProgress(double leftProgress) {
this.leftProgress = leftProgress;
setProgressValue();
invalidate();
}
public double getRightProgress() {
return rightProgress;
}
public void setRightProgress(double rightProgress) {
this.rightProgress = rightProgress;
setProgressValue();
invalidate();
}
public float getPointCircleRadius() {
return pointCircleRadius;
}
public void setPointCircleRadius(float pointCircleRadius) {
this.pointCircleRadius = pointCircleRadius;
invalidate();
}
}