图一
图二
因为是录屏转gif,效果一般,请担待。
//总的来说就是使用drawArc来画出图形,然后使用动画来更改初始角度和扇形
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = valueColor
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
if (backgroundColor != null) {
final Paint backgroundPaint = Paint()
..color = backgroundColor
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint);
}
if (value == null) // Indeterminate
paint.strokeCap = StrokeCap.square;
canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint);
}
@override
void paint(Canvas canvas, Size size) {
valueColorPaint = Paint()
..color = valueColor
..style = PaintingStyle.fill;
if (backgroundColor != null) {
valueColorPaint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
}
if (oneLeadColor != null) {
oneLeadCirclePaint = Paint()
..color = oneLeadColor
..style = PaintingStyle.fill;
} else {
oneLeadCirclePaint = valueColorPaint;
}
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180);
double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180);
double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180);
double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180);
double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180);
double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180);
canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint);
canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint);
canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint);
canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint);
}
其中使用了三个Paint,分别来区分颜色,因为我们是来画圆的,就需要确定圆心坐标,最关键的代码就是这个了
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
来,不要客气,先看张图
正方形为辅助图形,确定初始位置4个圆的位置,这个时候圆的位置是不动的,然后圆心的坐标为(x,y),位置已经确认,是时候让小圆动起来了,而小圆的圆心移动路径就是图中的那个大圆了,逻辑图示神马的都已经分析好,剩下的就都是数学计算了,我们是以正方形的一半为基础来进行计算的,而圆心的移动轨迹就是大圆半径扫过的轨迹,以大圆半径扫过的角度为变量,就可以轻松让小圆动起来,所以第一象限圆的圆心坐标就是(arcSweepOne是大圆扫过的角度)
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
然后结合Tween动画,动态的把变化的坐标传进去,小圆的位置就会实时改变啦
看一下完整代码,就不贴GitHub了,就是一个简单的小例子。
import 'dart:math' as math;
import 'package:flutter/material.dart';
const double _kMinCircularProgressIndicatorSize = 8.0;
class FlowerLoadingIndicator extends ProgressIndicator {
/// Creates a Flower progress indicator.
///
/// {@macro flutter.material.progressIndicator.parameters}
const FlowerLoadingIndicator({
Key key,
double value,
Color backgroundColor,
Animation valueColor,
this.oneLeadColor,
this.milliseconds,
String semanticsLabel,
String semanticsValue,
}) : super(
key: key,
value: value,
backgroundColor: backgroundColor,
valueColor: valueColor,
semanticsLabel: semanticsLabel,
semanticsValue: semanticsValue,
);
final Color oneLeadColor;
final int milliseconds;
Color getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
Color getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
Color getOneLeadColor(BuildContext context) => oneLeadColor ?? Theme.of(context).primaryColorLight;
Widget _buildSemanticsWrapper({
@required BuildContext context,
@required Widget child,
}) {
String expandedSemanticsValue = semanticsValue;
if (value != null) {
expandedSemanticsValue ??= '${(value * 100).round()}%';
}
return Semantics(
label: semanticsLabel,
value: expandedSemanticsValue,
child: child,
);
}
@override
State createState() => _FlowerLoadingIndicator();
}
final Tween _kRotationTweenOne = new Tween(begin: 0.0, end: 360.0);
final Tween _kRotationTweenTwo = new Tween(begin: -90.0, end: 270.0);
final Tween _kRotationTweenThree = new Tween(begin: 90, end: 450.0);
final Tween _kRotationTweenFour = new Tween(begin: 180, end: 540.0);
class _FlowerLoadingIndicator extends State with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: widget.milliseconds ?? 1500),
vsync: this,
);
_controller.repeat();
}
@override
void didUpdateWidget(FlowerLoadingIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return _buildAnimation();
}
Widget _buildIndicator(BuildContext context, double arcSweepOne, double arcSweepTwo, double arcSweepThree, double arcSweepFour) {
return widget._buildSemanticsWrapper(
context: context,
child: Container(
constraints: const BoxConstraints(
minWidth: _kMinCircularProgressIndicatorSize,
minHeight: _kMinCircularProgressIndicatorSize,
),
child: CustomPaint(
painter: _FlowerLoadingIndicatorPainter(
backgroundColor: widget.backgroundColor,
valueColor: widget.getValueColor(context),
oneLeadColor: widget.getOneLeadColor(context),
arcSweepOne: arcSweepOne,
arcSweepTwo: arcSweepTwo,
arcSweepThree: arcSweepThree,
arcSweepFour: arcSweepFour,
),
),
),
);
}
Widget _buildAnimation() {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return _buildIndicator(
context,
_kRotationTweenOne.evaluate(_controller),
_kRotationTweenTwo.evaluate(_controller),
_kRotationTweenThree.evaluate(_controller),
_kRotationTweenFour.evaluate(_controller),
);
},
);
}
}
class _FlowerLoadingIndicatorPainter extends CustomPainter {
_FlowerLoadingIndicatorPainter({
this.backgroundColor,
this.valueColor,
this.arcSweepOne,
this.arcSweepTwo,
this.arcSweepThree,
this.arcSweepFour,
this.oneLeadColor,
});
final Color backgroundColor;
final Color valueColor;
final Color oneLeadColor;
final double arcSweepOne;
final double arcSweepTwo;
final double arcSweepThree;
final double arcSweepFour;
Paint valueColorPaint;
Paint oneLeadCirclePaint;
@override
void paint(Canvas canvas, Size size) {
valueColorPaint = Paint()
..color = valueColor
..style = PaintingStyle.fill;
if (backgroundColor != null) {
valueColorPaint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
}
if (oneLeadColor != null) {
oneLeadCirclePaint = Paint()
..color = oneLeadColor
..style = PaintingStyle.fill;
} else {
oneLeadCirclePaint = valueColorPaint;
}
double dx1 = size.width + size.width * math.cos(arcSweepOne * math.pi / 180);
double dy1 = size.height + size.width * math.sin(arcSweepOne * math.pi / 180);
double dx2 = size.width + size.width * math.cos(arcSweepTwo * math.pi / 180);
double dy2 = size.height + size.width * math.sin(arcSweepTwo * math.pi / 180);
double dx3 = size.width + size.width * math.cos(arcSweepThree * math.pi / 180);
double dy3 = size.height + size.width * math.sin(arcSweepThree * math.pi / 180);
double dx4 = size.width + size.width * math.cos(arcSweepFour * math.pi / 180);
double dy4 = size.height + size.width * math.sin(arcSweepFour * math.pi / 180);
canvas.drawCircle(Offset(dx1, dy1), size.width / 2, valueColorPaint);
canvas.drawCircle(Offset(dx2, dy2), size.width / 2, oneLeadCirclePaint);
canvas.drawCircle(Offset(dx3, dy3), size.width / 2, valueColorPaint);
canvas.drawCircle(Offset(dx4, dy4), size.width / 2, valueColorPaint);
}
@override
bool shouldRepaint(_FlowerLoadingIndicatorPainter oldPainter) {
return true;
}
}