Skip to content

Commit a3715ea

Browse files
committed
circular progress bar in process
1 parent c2fe740 commit a3715ea

5 files changed

Lines changed: 416 additions & 14 deletions

File tree

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:vector_math/vector_math_64.dart' as math;
3+
4+
enum CircularStrokeCap { butt, round, square }
5+
6+
enum ArcType {
7+
HALF,
8+
FULL,
9+
}
10+
11+
class CircularPercentIndicator extends StatefulWidget {
12+
///Percent value between 0.0 and 1.0
13+
final double percent;
14+
final double radius;
15+
16+
///Width of the line of the Circle
17+
final double lineWidth;
18+
19+
///Color of the background of the circle , default = transparent
20+
final Color fillColor;
21+
22+
///First color applied to the complete circle
23+
final Color backgroundColor;
24+
25+
Color get progressColor => _progressColor;
26+
27+
Color _progressColor;
28+
29+
///true if you want the circle to have animation
30+
final bool animation;
31+
32+
///duration of the animation in milliseconds, It only applies if animation attribute is true
33+
final int animationDuration;
34+
35+
///widget at the top of the circle
36+
final Widget header;
37+
38+
///widget at the bottom of the circle
39+
final Widget footer;
40+
41+
///widget inside the circle
42+
final Widget center;
43+
44+
final LinearGradient linearGradient;
45+
46+
///The kind of finish to place on the end of lines drawn, values supported: butt, round, square
47+
final CircularStrokeCap circularStrokeCap;
48+
49+
///the angle which the circle will start the progress (in degrees, eg: 0.0, 45.0, 90.0)
50+
final double startAngle;
51+
52+
/// set true if you want to animate the linear from the last percent value you set
53+
final bool animateFromLastPercent;
54+
55+
/// set false if you don't want to preserve the state of the widget
56+
final bool addAutomaticKeepAlive;
57+
58+
/// set the arc type
59+
final ArcType arcType;
60+
61+
/// set a circular background color when use the arcType property
62+
final Color arcBackgroundColor;
63+
64+
/// set true when you want to display the progress in reverse mode
65+
final bool reverse;
66+
67+
/// Creates a mask filter that takes the progress shape being drawn and blurs it.
68+
final MaskFilter maskFilter;
69+
70+
CircularPercentIndicator(
71+
{Key key,
72+
this.percent = 0.0,
73+
this.lineWidth = 5.0,
74+
this.startAngle = 0.0,
75+
@required this.radius,
76+
this.fillColor = Colors.transparent,
77+
this.backgroundColor = const Color(0xFFB8C7CB),
78+
Color progressColor,
79+
this.linearGradient,
80+
this.animation = false,
81+
this.animationDuration = 500,
82+
this.header,
83+
this.footer,
84+
this.center,
85+
this.addAutomaticKeepAlive = true,
86+
this.circularStrokeCap,
87+
this.arcBackgroundColor,
88+
this.arcType,
89+
this.animateFromLastPercent = false,
90+
this.reverse = false,
91+
this.maskFilter})
92+
: super(key: key) {
93+
if (linearGradient != null && progressColor != null) {
94+
throw ArgumentError(
95+
'Cannot provide both linearGradient and progressColor');
96+
}
97+
_progressColor = progressColor ?? Colors.red;
98+
99+
assert(startAngle >= 0.0);
100+
if (percent < 0.0 || percent > 1.0) {
101+
throw Exception("Percent value must be a double between 0.0 and 1.0");
102+
}
103+
104+
if (arcType == null && arcBackgroundColor != null) {
105+
throw ArgumentError('arcType is required when you arcBackgroundColor');
106+
}
107+
}
108+
109+
@override
110+
_CircularPercentIndicatorState createState() =>
111+
_CircularPercentIndicatorState();
112+
}
113+
114+
class _CircularPercentIndicatorState extends State<CircularPercentIndicator>
115+
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
116+
AnimationController _animationController;
117+
Animation _animation;
118+
double _percent = 0.0;
119+
120+
@override
121+
void dispose() {
122+
if (_animationController != null) {
123+
_animationController.dispose();
124+
}
125+
super.dispose();
126+
}
127+
128+
@override
129+
void initState() {
130+
if (widget.animation) {
131+
_animationController = AnimationController(
132+
vsync: this,
133+
duration: Duration(milliseconds: widget.animationDuration));
134+
_animation =
135+
Tween(begin: 0.0, end: widget.percent).animate(_animationController)
136+
..addListener(() {
137+
setState(() {
138+
_percent = _animation.value;
139+
});
140+
});
141+
_animationController.forward();
142+
} else {
143+
_updateProgress();
144+
}
145+
super.initState();
146+
}
147+
148+
@override
149+
void didUpdateWidget(CircularPercentIndicator oldWidget) {
150+
super.didUpdateWidget(oldWidget);
151+
if (oldWidget.percent != widget.percent ||
152+
oldWidget.startAngle != widget.startAngle) {
153+
if (_animationController != null) {
154+
_animationController.duration =
155+
Duration(milliseconds: widget.animationDuration);
156+
_animation = Tween(
157+
begin: widget.animateFromLastPercent ? oldWidget.percent : 0.0,
158+
end: widget.percent)
159+
.animate(_animationController);
160+
_animationController.forward(from: 0.0);
161+
} else {
162+
_updateProgress();
163+
}
164+
}
165+
}
166+
167+
_updateProgress() {
168+
setState(() {
169+
_percent = widget.percent;
170+
});
171+
}
172+
173+
@override
174+
Widget build(BuildContext context) {
175+
super.build(context);
176+
var items = List<Widget>();
177+
if (widget.header != null) {
178+
items.add(widget.header);
179+
}
180+
items.add(Container(
181+
height: widget.radius + widget.lineWidth,
182+
width: widget.radius,
183+
child: CustomPaint(
184+
painter: CirclePainter(
185+
progress: _percent * 360,
186+
progressColor: widget.progressColor,
187+
backgroundColor: widget.backgroundColor,
188+
startAngle: widget.startAngle,
189+
circularStrokeCap: widget.circularStrokeCap,
190+
radius: (widget.radius / 2) - widget.lineWidth / 2,
191+
lineWidth: widget.lineWidth,
192+
arcBackgroundColor: widget.arcBackgroundColor,
193+
arcType: widget.arcType,
194+
reverse: widget.reverse,
195+
linearGradient: widget.linearGradient,
196+
maskFilter: widget.maskFilter),
197+
child: (widget.center != null)
198+
? Center(child: widget.center)
199+
: Container(),
200+
)));
201+
202+
if (widget.footer != null) {
203+
items.add(widget.footer);
204+
}
205+
206+
return Material(
207+
color: widget.fillColor,
208+
child: Container(
209+
child: Column(
210+
mainAxisAlignment: MainAxisAlignment.center,
211+
mainAxisSize: MainAxisSize.min,
212+
children: items,
213+
)),
214+
);
215+
}
216+
217+
@override
218+
bool get wantKeepAlive => widget.addAutomaticKeepAlive;
219+
}
220+
221+
class CirclePainter extends CustomPainter {
222+
final Paint _paintBackground = Paint();
223+
final Paint _paintLine = Paint();
224+
final Paint _paintBackgroundStartAngle = Paint();
225+
final double lineWidth;
226+
final double progress;
227+
final double radius;
228+
final Color progressColor;
229+
final Color backgroundColor;
230+
final CircularStrokeCap circularStrokeCap;
231+
final double startAngle;
232+
final LinearGradient linearGradient;
233+
final Color arcBackgroundColor;
234+
final ArcType arcType;
235+
final bool reverse;
236+
final MaskFilter maskFilter;
237+
238+
CirclePainter(
239+
{this.lineWidth,
240+
this.progress,
241+
@required this.radius,
242+
this.progressColor,
243+
this.backgroundColor,
244+
this.startAngle = 0.0,
245+
this.circularStrokeCap = CircularStrokeCap.round,
246+
this.linearGradient,
247+
this.reverse,
248+
this.arcBackgroundColor,
249+
this.arcType,
250+
this.maskFilter}) {
251+
_paintBackground.color = backgroundColor;
252+
_paintBackground.style = PaintingStyle.stroke;
253+
_paintBackground.strokeWidth = lineWidth;
254+
255+
if (arcBackgroundColor != null) {
256+
_paintBackgroundStartAngle.color = arcBackgroundColor;
257+
_paintBackgroundStartAngle.style = PaintingStyle.stroke;
258+
_paintBackgroundStartAngle.strokeWidth = lineWidth;
259+
}
260+
261+
_paintLine.color = progressColor;
262+
_paintLine.style = PaintingStyle.stroke;
263+
_paintLine.strokeWidth = lineWidth;
264+
if (circularStrokeCap == CircularStrokeCap.round) {
265+
_paintLine.strokeCap = StrokeCap.round;
266+
} else if (circularStrokeCap == CircularStrokeCap.butt) {
267+
_paintLine.strokeCap = StrokeCap.butt;
268+
} else {
269+
_paintLine.strokeCap = StrokeCap.square;
270+
}
271+
}
272+
273+
@override
274+
void paint(Canvas canvas, Size size) {
275+
final center = Offset(size.width / 2, size.height / 2);
276+
canvas.drawCircle(center, radius, _paintBackground);
277+
278+
if (maskFilter != null) {
279+
_paintLine.maskFilter = maskFilter;
280+
}
281+
if (linearGradient != null) {
282+
/*
283+
_paintLine.shader = SweepGradient(
284+
center: FractionalOffset.center,
285+
startAngle: math.radians(-90.0 + startAngle),
286+
endAngle: math.radians(progress),
287+
//tileMode: TileMode.mirror,
288+
colors: linearGradient.colors)
289+
.createShader(
290+
Rect.fromCircle(
291+
center: center,
292+
radius: radius,
293+
),
294+
);*/
295+
_paintLine.shader = linearGradient.createShader(
296+
Rect.fromCircle(
297+
center: center,
298+
radius: radius,
299+
),
300+
);
301+
}
302+
303+
double fixedStartAngle = startAngle;
304+
305+
double startAngleFixedMargin = 1.0;
306+
if (arcType != null) {
307+
if (arcType == ArcType.FULL) {
308+
fixedStartAngle = 220;
309+
startAngleFixedMargin = 172 / fixedStartAngle;
310+
} else {
311+
fixedStartAngle = 270;
312+
startAngleFixedMargin = 135 / fixedStartAngle;
313+
}
314+
}
315+
316+
if (arcBackgroundColor != null) {
317+
canvas.drawArc(
318+
Rect.fromCircle(center: center, radius: radius),
319+
math.radians(-90.0 + fixedStartAngle),
320+
math.radians(360 * startAngleFixedMargin),
321+
false,
322+
_paintBackgroundStartAngle,
323+
);
324+
}
325+
326+
if (reverse) {
327+
final start =
328+
math.radians(360 * startAngleFixedMargin - 90.0 + fixedStartAngle);
329+
final end = math.radians(-progress * startAngleFixedMargin);
330+
canvas.drawArc(
331+
Rect.fromCircle(
332+
center: center,
333+
radius: radius,
334+
),
335+
start,
336+
end,
337+
false,
338+
_paintLine,
339+
);
340+
} else {
341+
final start = math.radians(-90.0 + fixedStartAngle);
342+
final end = math.radians(progress * startAngleFixedMargin);
343+
canvas.drawArc(
344+
Rect.fromCircle(
345+
center: center,
346+
radius: radius,
347+
),
348+
start,
349+
end,
350+
false,
351+
_paintLine,
352+
);
353+
}
354+
}
355+
356+
@override
357+
bool shouldRepaint(CustomPainter oldDelegate) {
358+
return true;
359+
}
360+
}

0 commit comments

Comments
 (0)