import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; const halfPi = pi / 2; const pi = math.pi; typedef LabelBuilder = Iterable Function(List values); extension LevelValueExt on List { double? level(int level) { for (var element in this) { if (element.level == level) return element.value; } return null; } } @immutable class LevelValue { final double value; final int level; const LevelValue(this.value) : level = 0; const LevelValue._withLevel(this.value, this.level); LevelValue incrementLevel() => LevelValue._withLevel(value, level + 1); } class RingIndicator extends StatefulWidget { const RingIndicator({ Key? key, this.backgroundColor = Colors.white12, this.ringWidth = 10, this.startAngel = 0, this.endAngel = pi * 2, required this.ringColor, required this.maxValue, this.minValue = 0, required this.value, this.innerRing, this.labelBuilder, double? gap, }) : gap = gap ?? ringWidth / 2, super(key: key); final Color backgroundColor; final double ringWidth; final Color ringColor; final double startAngel; final double endAngel; final double maxValue; final double minValue; final double value; final RingIndicator? innerRing; final LabelBuilder? labelBuilder; final double gap; @override State createState() => _RingIndicatorState(); } class _RingIndicatorState extends State { double _value = 0; Map values = {}; _RingIndicatorState? parent; @override Widget build(BuildContext context) { var fillAngel = ((_value - widget.minValue) / (widget.maxValue - widget.minValue)) * (widget.endAngel - widget.startAngel); return Center( child: SizedBox.expand( child: CustomPaint( painter: _RingIndicatorPainter( backgroundColor: widget.backgroundColor, ringWidth: widget.ringWidth, startAngel: widget.startAngel, endAngel: widget.endAngel, ringColor: widget.ringColor, fillAngel: fillAngel, ), child: Stack( children: [ if (widget.innerRing != null) Padding( padding: EdgeInsets.all(widget.ringWidth + widget.gap), child: widget.innerRing, ), if (widget.labelBuilder != null) ...widget.labelBuilder!(values.values.toList()), ], ), ), ), ); } Ticker? ticker; _handleValueChange(double newValue, double oldValue) { var startTime = DateTime.now(); ticker?.stop(); ticker = Ticker((_) { var diff = DateTime.now().difference(startTime).inMilliseconds; if (diff > 300) { return ticker?.stop(); } if (mounted) { setState(() { _value = oldValue + (newValue - oldValue) * (diff / 300); values[0] = LevelValue(_value); }); if (parent != null) { for (var value in values.values) { parent!.updateValue(value.incrementLevel()); } } } }) ..start(); } @override void initState() { super.initState(); _handleValueChange(widget.value, 0); parent = context.findAncestorStateOfType<_RingIndicatorState>(); } @override void didUpdateWidget(RingIndicator oldWidget) { super.didUpdateWidget(oldWidget); _handleValueChange(widget.value, oldWidget.value); } void updateValue(LevelValue value) { values[value.level] = value; if (parent != null && parent!.mounted) { for (var value in values.values) { parent!.updateValue(value.incrementLevel()); } } if (mounted) { setState(() {}); } } } class _RingIndicatorPainter extends CustomPainter { _RingIndicatorPainter({ required this.backgroundColor, required this.ringWidth, required this.ringColor, required this.startAngel, required this.fillAngel, required this.endAngel, }) : _paint = Paint() ..strokeWidth = ringWidth ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final Color backgroundColor; final double ringWidth; final Color ringColor; final double startAngel; final double fillAngel; final double endAngel; final Paint _paint; @override void paint(Canvas canvas, Size size) { canvas.drawArc( Rect.fromCenter( center: size.center(Offset.zero), width: size.width, height: size.height, ), startAngel - halfPi, endAngel - startAngel, false, _paint..color = backgroundColor, ); canvas.drawArc( Rect.fromCenter( center: size.center(Offset.zero), width: size.width, height: size.height, ), startAngel - halfPi, fillAngel, false, _paint..color = ringColor, ); } @override bool shouldRepaint(covariant _RingIndicatorPainter oldDelegate) => true; }