组件效果:
由于需求上需要显示值和文字,该组件在https://www.jianshu.com/p/54aa09ce67a2 基础上进行修改。缺陷:文字显示坐标没有精确计算(有精确度误差),绘制文字内容超过父widget,可通过父widget Container加padding或者加Padding组件进行屏蔽。修改后对应组件代码如下
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SpiderWidget extends CustomPainter {
//多边形边数
int mEdgeSize;
final double CIRCLE_ANGLE = 360;
//中心坐标
double mCenterX;
double mCenterY;
//线画笔
Paint mPaint;
//覆盖物画笔
Paint mCoverPaint;
//文本画笔
Paint mTextPaint;
Path mPath;
List<String> texts = ["测试", "测试", "测试", "测试"];
List<int> values = [10, 8, 9, 9];
SpiderWidget() {
// 初始化画笔
mPaint = new Paint();
mPaint.color = Colors.grey;
// 设置抗锯齿
mPaint.isAntiAlias = true;
// 样式为描边
mPaint.style = PaintingStyle.stroke;
mPath = new Path();
mCoverPaint = new Paint();
mCoverPaint.isAntiAlias = true;
mCoverPaint.style = PaintingStyle.fill;
mCoverPaint.color = Colors.yellow;
mTextPaint = new Paint();
mTextPaint.isAntiAlias = true;
mTextPaint.style = PaintingStyle.fill;
mTextPaint.color = Colors.blue;
}
@override
void paint(Canvas canvas, Size size) {
init(canvas, size);
}
void init(Canvas canvas, Size size) {
mCenterX = size.width / 2;
mCenterY = size.height / 2;
//设置边数
mEdgeSize = texts.length;
// 图层 防止刷新属性结构
canvas.save();
drawSpiderEdge(canvas);
drawCover(canvas);
drawText(canvas);
drawValues(canvas);
canvas.restore();
}
void drawText(Canvas canvas) {
double radius = mCenterX > mCenterY ? mCenterX : mCenterY;
double angle = CIRCLE_ANGLE / mEdgeSize;
for (int j = 0; j < mEdgeSize; j++) {
// 移动
double x = mCenterX + radius * cos(degToRad(angle * j));
double y = mCenterY + radius * sin(degToRad(angle * j));
ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
textAlign: TextAlign.center,
fontStyle: FontStyle.normal,
fontSize: 12,
))
..pushStyle(ui.TextStyle(color: Colors.green))
..addText(texts[j]);
ui.ParagraphConstraints pc =
ui.ParagraphConstraints(width: double.infinity);
ui.Paragraph paragraph = pb.build()..layout(pc);
Offset offset;
if (x < mCenterX && y < mCenterY) {
offset = Offset(x - paragraph.minIntrinsicWidth - 8, y - 12);
} else if (x < mCenterX && y > mCenterY) {
offset = Offset(x - paragraph.minIntrinsicWidth - 8, y - 12);
} else if (x > mCenterX && y < mCenterY) {
offset = Offset(x +8, y - 12);
} else if (x > mCenterX && y > mCenterY) {
offset = Offset(x - 8, y +8);
} else if (x == mCenterX && y > mCenterY) {
offset = Offset(x, y );
} else if (x == mCenterX && y < mCenterY) {
offset = Offset(x+paragraph.minIntrinsicWidth, y - 12);
} else if (y == mCenterY && x > mCenterX) {
offset = Offset(x +8, y -8);
} else {
offset = Offset(x - 12, y);
}
canvas.drawParagraph(paragraph, offset);
}
}
/// 绘制边线
void drawSpiderEdge(Canvas canvas) {
double angle = CIRCLE_ANGLE / mEdgeSize;
double radius = 0;
double radiusMaxLimit = mCenterX > mCenterY ? mCenterX : mCenterY;
for (int i = 0; i < mEdgeSize; i++) {
mPath.reset();
radius = radiusMaxLimit / mEdgeSize * (i + 1);
for (int j = 0; j < mEdgeSize + 1; j++) {
// 移动
if (j == 0) {
mPath.moveTo(mCenterX + radius, mCenterY);
} else {
double x = mCenterX + radius * cos(degToRad(angle * j));
double y = mCenterY + radius * sin(degToRad(angle * j));
mPath.lineTo(x, y);
}
}
mPath.close();
canvas.drawPath(mPath, mPaint);
}
drawSpiderAxis(canvas, radiusMaxLimit, angle);
}
/// 绘制轴线
void drawSpiderAxis(Canvas canvas, double radius, double angle) {
for (int i = 0; i < mEdgeSize; i++) {
mPath.reset();
mPath.moveTo(mCenterX, mCenterY);
double x = mCenterX + radius * cos(degToRad(angle * i));
double y = mCenterY + radius * sin(degToRad(angle * i));
mPath.lineTo(x, y);
canvas.drawPath(mPath, mPaint);
}
}
/// 绘制覆盖区域
void drawCover(Canvas canvas) {
mPath.reset();
// Random random = new Random();
double angle = CIRCLE_ANGLE / mEdgeSize;
double radiusMaxLimit = min(mCenterY, mCenterY);
for (int i = 0; i < mEdgeSize; i++) {
// double value = (random.nextInt(10) + 1) / 10;
double value = values[i] / 10;
double x = mCenterX + radiusMaxLimit * cos(degToRad(angle * i)) * value;
double y = mCenterY + radiusMaxLimit * sin(degToRad(angle * i)) * value;
if (i == 0) {
mPath.moveTo(x, mCenterY);
} else {
mPath.lineTo(x, y);
}
}
mPath.close();
canvas.drawPath(mPath, mCoverPaint);
}
void drawValues(Canvas canvas) {
double angle = CIRCLE_ANGLE / mEdgeSize;
double radiusMaxLimit = min(mCenterY, mCenterY);
for (int i = 0; i < mEdgeSize; i++) {
double value = values[i] / 10;
double x = mCenterX + radiusMaxLimit * cos(degToRad(angle * i)) * value;
double y = mCenterY + radiusMaxLimit * sin(degToRad(angle * i)) * value;
//画值
ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
textAlign: TextAlign.center,
fontStyle: FontStyle.normal,
fontSize: 12,
))
..pushStyle(ui.TextStyle(color: Colors.grey))
..addText("${values[i]}");
ui.ParagraphConstraints pc =
ui.ParagraphConstraints(width: double.infinity);
ui.Paragraph paragraph = pb.build()..layout(pc);
Offset offset;
if (x < mCenterX && y < mCenterY) {
offset = Offset(x, y + 8);
} else if (x < mCenterX && y > mCenterY) {
offset = Offset(x + paragraph.minIntrinsicWidth, y - 12);
} else if (x > mCenterX && y < mCenterY) {
offset = Offset(x - paragraph.minIntrinsicWidth, y + 6);
} else if (x > mCenterX && y > mCenterY) {
offset = Offset(x - paragraph.minIntrinsicWidth, y - 20);
} else if (x == mCenterX && y > mCenterY) {
offset = Offset(x, y - 12);
} else if (x == mCenterX && y < mCenterY) {
offset = Offset(x, y + 12);
} else if (y == mCenterY && x > mCenterX) {
offset = Offset(x - paragraph.minIntrinsicWidth - 12, y - 4);
} else {
offset = Offset(x + 12, y);
}
canvas.drawParagraph(paragraph, offset);
}
}
num degToRad(num deg) => deg * (pi / 180.0);
num radToDeg(num rad) => rad * (180.0 / pi);
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
}