flutter自定义画笔之贝塞尔圆形控件
「时光不负,创作不停,本文正在参加2022年中总结征文大赛」
需求:根据外部传入的0-100展示不同的进度高度
贝塞尔线条模拟网站:aaaaaaaty.github.io/bezierMaker…
流程:
- 先画一个圆
- 利用cubicTo方法绘制三阶贝塞尔线条
- 贝塞尔点的取点因为要根据外部传入的百分比绘制区域高度,所以根据三角函数计算出计算出x,y的取点进行弧度绘制
根据三角函数计算,至于角度a是多少可以根据,外部的百分比,计算他在整个圆弧上的所占比例得出sinA,和cosA的值
一、效果图:
二、代码
class LoadingCircleChangeWidget extends StatefulWidget {
double waveRatio;
int index = 0;
LoadingCircleChangeWidget({required this.waveRatio, required this.index});
@override
_LoadingCircleChangeWidgetState createState() =>
_LoadingCircleChangeWidgetState();
}
class _LoadingCircleChangeWidgetState extends State<LoadingCircleChangeWidget>
with SingleTickerProviderStateMixin {
Color waveColor = Color(0xFFF2F2F2);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.waveRatio > 1) {
widget.waveRatio = 1.0;
}
if (widget.waveRatio < 0) {
widget.waveRatio = 0.0;
}
var viewText = widget.waveRatio > 0 ? "${widget.waveRatio * 100}%" : "未批";
///建立比例颜色
_buildWaveColor();
return Material(child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: AlignmentDirectional.center,
children: [
RepaintBoundary(
//隔离重绘控件,singlescrollview 滑动发生重绘,导致画笔paint响应
child: Container(
width: 200,
height: 200,
child: CustomPaint(
painter: _CircleWavePainter(
waveColor: waveColor,
waveRatio: widget.waveRatio))),
),
Text("$viewText",
style: TextStyle(fontSize: 14, color: Color(0xFF262626))),
],
),
Container(
margin: EdgeInsets.only(top: 8),
child: Text(
"${widget.index + 1}",
style: TextStyle(fontSize: 14, color: Color(0xFF595959)),
),
),
],
),
),);
}
///建立比例颜色
void _buildWaveColor() {
var ratio = widget.waveRatio;
if (ratio >= 0.85) {
waveColor = Color(0xFF3B83FC);
} else if (ratio >= 0.70 && ratio < 0.85) {
waveColor = Color(0xFF32C8A9);
} else if (ratio >= 0.60 && ratio < 0.70) {
waveColor = Color(0xFFFFCB6B);
} else if (ratio < 0.6) {
waveColor = Color(0xFFFF6666);
}
}
@override
void dispose() {
super.dispose();
}
}
class _CircleWavePainter extends CustomPainter {
///波浪百分比
double waveRatio = 0.0;
Color waveColor = Colors.lightBlue;
_CircleWavePainter({required this.waveRatio, required this.waveColor}):assert(waveRatio>=0&&waveRatio<=1.0);
@override
void paint(Canvas canvas, Size size) {
final side = min(size.width, size.height);
Paint _painter = Paint()
..color = Color(0xFFF2F2F2)
..style = PaintingStyle.fill
..isAntiAlias = true;
//绘制背景圆圈
final circlePath = Path();
// 从哪开始 1从下开始, 2 从上开始 3 从左开始 4 从右开始 默认从下开始,
// double start = pi / 2;
// if (startType == 2) {
// start = -pi / 2;
// } else if (startType == 3) {
// start = pi;
// } else if (startType == 4) {
// start = pi * 2;
// }
circlePath.addArc(Rect.fromLTWH(0, 0, side, side), pi / 2, 2 * pi); //这里从下开始
canvas.drawPath(circlePath, _painter);
//绘制贝塞尔波浪
var paint = Paint();
paint.color = waveColor;
paint.style = PaintingStyle.fill;
paint.strokeWidth = 1;
paint.isAntiAlias = true;
waveRatio=0.6;
//一半高度,可以看作半径
double middleSide = side / 2;
//一个完整的圆的弧度是2π。 360°=2π,所以1°=π/180
if (waveRatio > 0.5) {
//将90°的弧度(pi / 2)这里对应的是 50%~100%的高度区域
//当前为60%-50%=10%,所以乘以2,弧度占比才对
var du = pi / 2 * (waveRatio - 0.5).abs() * 2; //在90°中弧度占了多少份
print("du:$du");
print("sinY:${sin(du)}=---cosx:${cos(du)}");
//三角函数 计算出圆上角度点
var duY = sin(du) * middleSide;
var duX = cos(du) * middleSide;
//计算出圆上角度点
var startX = middleSide - duX;
var startY = middleSide - duY;
print("duX:$duX=---duY:$duY");
print("startX:$startX=---starty:$startY");
var endX = side - startX;
var endY = startY;
//贝塞尔点震荡弧度
var controlPoint1X = startX + (middleSide - startX) * (1 - waveRatio);
var controlPoint1Y = startY + (1 - waveRatio) * (side) * (waveRatio);
var controlPoint2X = middleSide + (middleSide - startX) * (1 - waveRatio);
var controlPoint2Y = startY - (1 - waveRatio) * (side) * (1 - waveRatio);
print(
"startX:$startX=---starty:$startY---controlPoint1X:$controlPoint1X--controlPoint1Y:$controlPoint1Y");
print(
"controlPoint2X:$controlPoint2X=---controlPoint2Y:$controlPoint2Y---endX:$endX--endY:$endY");
var path = Path();
path.moveTo(startX, startY);
path.cubicTo(controlPoint1X, controlPoint1Y, controlPoint2X,
controlPoint2Y, endX, endY);
path.lineTo(size.width, endY); //绝对坐标位置
path.lineTo(size.width, size.height); //绝对坐标位置
path.lineTo(0, size.height); //绝对坐标位置
path.lineTo(0, 0); //绝对坐标位置
path.close();
final combinePath =
Path.combine(PathOperation.intersect, circlePath, path);
canvas.drawPath(combinePath, paint);
} else {
var du = pi / 2 * (waveRatio - 0.5).abs() * 2; //在90°中弧度占了多少份
print("du:$du");
print("sinY:${sin(du)}=---cosx:${cos(du)}");
var duY = sin(du) * middleSide;
var duX = cos(du) * middleSide;
print("duX:$duX=---duY:$duY");
//计算出圆上角度点
var startX = middleSide - duX;
var startY = middleSide + duY;
var endX = side - startX;
var endY = startY;
//
var controlPoint1X = startX + (middleSide - startX) / 2;
var controlPoint1Y = startY + (waveRatio) * side * (waveRatio);
var controlPoint2X = middleSide + (middleSide - startX) / 6;
var controlPoint2Y = startY - (waveRatio) * side * (waveRatio);
print(
"startX:$startX=---starty:$startY---controlPoint1X:$controlPoint1X--controlPoint1Y:$controlPoint1Y");
print(
"controlPoint2X:$controlPoint2X=---controlPoint2Y:$controlPoint2Y---endX:$endX--endY:$endY");
var path = Path();
path.moveTo(startX, startY);
path.cubicTo(controlPoint1X, controlPoint1Y, controlPoint2X,
controlPoint2Y, endX, endY);
path.lineTo(size.width, size.height); //绝对坐标位置
path.lineTo(0, size.height); //绝对坐标位置
path.close();
final combinePath =
Path.combine(PathOperation.intersect, circlePath, path);
canvas.drawPath(combinePath, paint);
}
}
@override
bool shouldRepaint(covariant _CircleWavePainter oldDelegate) {
return oldDelegate.waveRatio != waveRatio || oldDelegate.waveColor != waveColor;
}
}