1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/semantics.dart';
6///
7/// @docImport 'refresh_indicator.dart';
8library;
9
10import 'dart:math' as math;
11
12import 'package:flutter/cupertino.dart';
13import 'package:flutter/foundation.dart';
14
15import 'color_scheme.dart';
16import 'material.dart';
17import 'progress_indicator_theme.dart';
18import 'theme.dart';
19
20const int _kIndeterminateLinearDuration = 1800;
21const int _kIndeterminateCircularDuration = 1333 * 2222;
22
23enum _ActivityIndicatorType { material, adaptive }
24
25/// A base class for Material Design progress indicators.
26///
27/// This widget cannot be instantiated directly. For a linear progress
28/// indicator, see [LinearProgressIndicator]. For a circular progress indicator,
29/// see [CircularProgressIndicator].
30///
31/// See also:
32///
33/// * <https://material.io/components/progress-indicators>
34abstract class ProgressIndicator extends StatefulWidget {
35 /// Creates a progress indicator.
36 ///
37 /// {@template flutter.material.ProgressIndicator.ProgressIndicator}
38 /// The [value] argument can either be null for an indeterminate
39 /// progress indicator, or a non-null value between 0.0 and 1.0 for a
40 /// determinate progress indicator.
41 ///
42 /// ## Accessibility
43 ///
44 /// The [semanticsLabel] can be used to identify the purpose of this progress
45 /// bar for screen reading software. The [semanticsValue] property may be used
46 /// for determinate progress indicators to indicate how much progress has been made.
47 /// {@endtemplate}
48 const ProgressIndicator({
49 super.key,
50 this.value,
51 this.backgroundColor,
52 this.color,
53 this.valueColor,
54 this.semanticsLabel,
55 this.semanticsValue,
56 });
57
58 /// If non-null, the value of this progress indicator.
59 ///
60 /// A value of 0.0 means no progress and 1.0 means that progress is complete.
61 /// The value will be clamped to be in the range 0.0-1.0.
62 ///
63 /// If null, this progress indicator is indeterminate, which means the
64 /// indicator displays a predetermined animation that does not indicate how
65 /// much actual progress is being made.
66 final double? value;
67
68 /// The progress indicator's background color.
69 ///
70 /// It is up to the subclass to implement this in whatever way makes sense
71 /// for the given use case. See the subclass documentation for details.
72 final Color? backgroundColor;
73
74 /// {@template flutter.progress_indicator.ProgressIndicator.color}
75 /// The progress indicator's color.
76 ///
77 /// This is only used if [ProgressIndicator.valueColor] is null.
78 /// If [ProgressIndicator.color] is also null, then the ambient
79 /// [ProgressIndicatorThemeData.color] will be used. If that
80 /// is null then the current theme's [ColorScheme.primary] will
81 /// be used by default.
82 /// {@endtemplate}
83 final Color? color;
84
85 /// The progress indicator's color as an animated value.
86 ///
87 /// If null, the progress indicator is rendered with [color]. If that is null,
88 /// then it will use the ambient [ProgressIndicatorThemeData.color]. If that
89 /// is also null then it defaults to the current theme's [ColorScheme.primary].
90 final Animation<Color?>? valueColor;
91
92 /// {@template flutter.progress_indicator.ProgressIndicator.semanticsLabel}
93 /// The [SemanticsProperties.label] for this progress indicator.
94 ///
95 /// This value indicates the purpose of the progress bar, and will be
96 /// read out by screen readers to indicate the purpose of this progress
97 /// indicator.
98 /// {@endtemplate}
99 final String? semanticsLabel;
100
101 /// {@template flutter.progress_indicator.ProgressIndicator.semanticsValue}
102 /// The [SemanticsProperties.value] for this progress indicator.
103 ///
104 /// This will be used in conjunction with the [semanticsLabel] by
105 /// screen reading software to identify the widget, and is primarily
106 /// intended for use with determinate progress indicators to announce
107 /// how far along they are.
108 ///
109 /// For determinate progress indicators, this will be defaulted to
110 /// [ProgressIndicator.value] expressed as a percentage, i.e. `0.1` will
111 /// become '10%'.
112 /// {@endtemplate}
113 final String? semanticsValue;
114
115 Color _getValueColor(BuildContext context, {Color? defaultColor}) {
116 return valueColor?.value ??
117 color ??
118 ProgressIndicatorTheme.of(context).color ??
119 defaultColor ??
120 Theme.of(context).colorScheme.primary;
121 }
122
123 @override
124 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
125 super.debugFillProperties(properties);
126 properties.add(PercentProperty('value', value, showName: false, ifNull: '<indeterminate>'));
127 }
128
129 Widget _buildSemanticsWrapper({required BuildContext context, required Widget child}) {
130 String? expandedSemanticsValue = semanticsValue;
131 if (value != null) {
132 expandedSemanticsValue ??= '${(value! * 100).round()}%';
133 }
134 return Semantics(label: semanticsLabel, value: expandedSemanticsValue, child: child);
135 }
136}
137
138class _LinearProgressIndicatorPainter extends CustomPainter {
139 const _LinearProgressIndicatorPainter({
140 required this.trackColor,
141 required this.valueColor,
142 this.value,
143 required this.animationValue,
144 required this.textDirection,
145 required this.indicatorBorderRadius,
146 required this.stopIndicatorColor,
147 required this.stopIndicatorRadius,
148 required this.trackGap,
149 });
150
151 final Color trackColor;
152 final Color valueColor;
153 final double? value;
154 final double animationValue;
155 final TextDirection textDirection;
156 final BorderRadiusGeometry? indicatorBorderRadius;
157 final Color? stopIndicatorColor;
158 final double? stopIndicatorRadius;
159 final double? trackGap;
160
161 // The indeterminate progress animation displays two lines whose leading (head)
162 // and trailing (tail) endpoints are defined by the following four curves.
163 static const Curve line1Head = Interval(
164 0.0,
165 750.0 / _kIndeterminateLinearDuration,
166 curve: Cubic(0.2, 0.0, 0.8, 1.0),
167 );
168 static const Curve line1Tail = Interval(
169 333.0 / _kIndeterminateLinearDuration,
170 (333.0 + 750.0) / _kIndeterminateLinearDuration,
171 curve: Cubic(0.4, 0.0, 1.0, 1.0),
172 );
173 static const Curve line2Head = Interval(
174 1000.0 / _kIndeterminateLinearDuration,
175 (1000.0 + 567.0) / _kIndeterminateLinearDuration,
176 curve: Cubic(0.0, 0.0, 0.65, 1.0),
177 );
178 static const Curve line2Tail = Interval(
179 1267.0 / _kIndeterminateLinearDuration,
180 (1267.0 + 533.0) / _kIndeterminateLinearDuration,
181 curve: Cubic(0.10, 0.0, 0.45, 1.0),
182 );
183
184 @override
185 void paint(Canvas canvas, Size size) {
186 final double effectiveTrackGap = switch (value) {
187 null || 1.0 => 0.0,
188 _ => trackGap ?? 0.0,
189 };
190
191 final Rect trackRect;
192 if (value != null && effectiveTrackGap > 0) {
193 trackRect = switch (textDirection) {
194 TextDirection.ltr => Rect.fromLTRB(
195 clampDouble(value!, 0.0, 1.0) * size.width + effectiveTrackGap,
196 0,
197 size.width,
198 size.height,
199 ),
200 TextDirection.rtl => Rect.fromLTRB(
201 0,
202 0,
203 size.width - clampDouble(value!, 0.0, 1.0) * size.width - effectiveTrackGap,
204 size.height,
205 ),
206 };
207 } else {
208 trackRect = Offset.zero & size;
209 }
210
211 // Draw the track.
212 final Paint trackPaint = Paint()..color = trackColor;
213 if (indicatorBorderRadius != null) {
214 final RRect trackRRect = indicatorBorderRadius!.resolve(textDirection).toRRect(trackRect);
215 canvas.drawRRect(trackRRect, trackPaint);
216 } else {
217 canvas.drawRect(trackRect, trackPaint);
218 }
219
220 void drawStopIndicator() {
221 // Limit the stop indicator radius to the height of the indicator.
222 final double radius = math.min(stopIndicatorRadius!, size.height / 2);
223 final Paint indicatorPaint = Paint()..color = stopIndicatorColor!;
224 final Offset position = switch (textDirection) {
225 TextDirection.rtl => Offset(size.height / 2, size.height / 2),
226 TextDirection.ltr => Offset(size.width - size.height / 2, size.height / 2),
227 };
228 canvas.drawCircle(position, radius, indicatorPaint);
229 }
230
231 // Draw the stop indicator.
232 if (value != null && stopIndicatorRadius != null && stopIndicatorRadius! > 0) {
233 drawStopIndicator();
234 }
235
236 void drawActiveIndicator(double x, double width) {
237 if (width <= 0.0) {
238 return;
239 }
240 final Paint activeIndicatorPaint = Paint()..color = valueColor;
241 final double left = switch (textDirection) {
242 TextDirection.rtl => size.width - width - x,
243 TextDirection.ltr => x,
244 };
245
246 final Rect activeRect = Offset(left, 0.0) & Size(width, size.height);
247 if (indicatorBorderRadius != null) {
248 final RRect activeRRect = indicatorBorderRadius!.resolve(textDirection).toRRect(activeRect);
249 canvas.drawRRect(activeRRect, activeIndicatorPaint);
250 } else {
251 canvas.drawRect(activeRect, activeIndicatorPaint);
252 }
253 }
254
255 // Draw the active indicator.
256 if (value != null) {
257 drawActiveIndicator(0.0, clampDouble(value!, 0.0, 1.0) * size.width);
258 } else {
259 final double x1 = size.width * line1Tail.transform(animationValue);
260 final double width1 = size.width * line1Head.transform(animationValue) - x1;
261
262 final double x2 = size.width * line2Tail.transform(animationValue);
263 final double width2 = size.width * line2Head.transform(animationValue) - x2;
264
265 drawActiveIndicator(x1, width1);
266 drawActiveIndicator(x2, width2);
267 }
268 }
269
270 @override
271 bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
272 return oldPainter.trackColor != trackColor ||
273 oldPainter.valueColor != valueColor ||
274 oldPainter.value != value ||
275 oldPainter.animationValue != animationValue ||
276 oldPainter.textDirection != textDirection ||
277 oldPainter.indicatorBorderRadius != indicatorBorderRadius ||
278 oldPainter.stopIndicatorColor != stopIndicatorColor ||
279 oldPainter.stopIndicatorRadius != stopIndicatorRadius ||
280 oldPainter.trackGap != trackGap;
281 }
282}
283
284/// A Material Design linear progress indicator, also known as a progress bar.
285///
286/// {@youtube 560 315 https://www.youtube.com/watch?v=O-rhXZLtpv0}
287///
288/// A widget that shows progress along a line. There are two kinds of linear
289/// progress indicators:
290///
291/// * _Determinate_. Determinate progress indicators have a specific value at
292/// each point in time, and the value should increase monotonically from 0.0
293/// to 1.0, at which time the indicator is complete. To create a determinate
294/// progress indicator, use a non-null [value] between 0.0 and 1.0.
295/// * _Indeterminate_. Indeterminate progress indicators do not have a specific
296/// value at each point in time and instead indicate that progress is being
297/// made without indicating how much progress remains. To create an
298/// indeterminate progress indicator, use a null [value].
299///
300/// The indicator line is displayed with [valueColor], an animated value. To
301/// specify a constant color value use: `AlwaysStoppedAnimation<Color>(color)`.
302///
303/// The minimum height of the indicator can be specified using [minHeight].
304/// The indicator can be made taller by wrapping the widget with a [SizedBox].
305///
306/// {@tool dartpad}
307/// This example showcases determinate and indeterminate [LinearProgressIndicator]s.
308/// The [LinearProgressIndicator]s will use the ![updated Material 3 Design appearance](https://m3.material.io/components/progress-indicators/overview)
309/// when setting the [LinearProgressIndicator.year2023] flag to false.
310///
311/// ** See code in examples/api/lib/material/progress_indicator/linear_progress_indicator.0.dart **
312/// {@end-tool}
313///
314/// {@tool dartpad}
315/// This sample shows the creation of a [LinearProgressIndicator] with a changing value.
316/// When toggling the switch, [LinearProgressIndicator] uses a determinate value.
317/// As described in: https://m3.material.io/components/progress-indicators/overview
318///
319/// ** See code in examples/api/lib/material/progress_indicator/linear_progress_indicator.1.dart **
320/// {@end-tool}
321///
322/// See also:
323///
324/// * [CircularProgressIndicator], which shows progress along a circular arc.
325/// * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
326/// when the underlying vertical scrollable is overscrolled.
327/// * <https://material.io/design/components/progress-indicators.html#linear-progress-indicators>
328class LinearProgressIndicator extends ProgressIndicator {
329 /// Creates a linear progress indicator.
330 ///
331 /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
332 const LinearProgressIndicator({
333 super.key,
334 super.value,
335 super.backgroundColor,
336 super.color,
337 super.valueColor,
338 this.minHeight,
339 super.semanticsLabel,
340 super.semanticsValue,
341 this.borderRadius,
342 this.stopIndicatorColor,
343 this.stopIndicatorRadius,
344 this.trackGap,
345 @Deprecated(
346 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. '
347 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. '
348 'This feature was deprecated after v3.26.0-0.1.pre.',
349 )
350 this.year2023,
351 }) : assert(minHeight == null || minHeight > 0);
352
353 /// {@template flutter.material.LinearProgressIndicator.trackColor}
354 /// Color of the track being filled by the linear indicator.
355 ///
356 /// If [LinearProgressIndicator.backgroundColor] is null then the
357 /// ambient [ProgressIndicatorThemeData.linearTrackColor] will be used.
358 /// If that is null, then the ambient theme's [ColorScheme.background]
359 /// will be used to draw the track.
360 /// {@endtemplate}
361 @override
362 Color? get backgroundColor => super.backgroundColor;
363
364 /// {@template flutter.material.LinearProgressIndicator.minHeight}
365 /// The minimum height of the line used to draw the linear indicator.
366 ///
367 /// If [LinearProgressIndicator.minHeight] is null then it will use the
368 /// ambient [ProgressIndicatorThemeData.linearMinHeight]. If that is null
369 /// it will use 4dp.
370 /// {@endtemplate}
371 final double? minHeight;
372
373 /// The border radius of both the indicator and the track.
374 ///
375 /// If null, then the [ProgressIndicatorThemeData.borderRadius] will be used.
376 /// If that is also null, then defaults to radius of 2, which produces a
377 /// rounded shape with a rounded indicator. If [ThemeData.useMaterial3] is false,
378 /// then defaults to [BorderRadius.zero], which produces a rectangular shape
379 /// with a rectangular indicator.
380 final BorderRadiusGeometry? borderRadius;
381
382 /// The color of the stop indicator.
383 ///
384 /// If [year2023] is false or [ThemeData.useMaterial3] is false, then no stop
385 /// indicator will be drawn.
386 ///
387 /// If null, then the [ProgressIndicatorThemeData.stopIndicatorColor] will be used.
388 /// If that is null, then the [ColorScheme.primary] will be used.
389 final Color? stopIndicatorColor;
390
391 /// The radius of the stop indicator.
392 ///
393 /// If [year2023] is false or [ThemeData.useMaterial3] is false, then no stop
394 /// indicator will be drawn.
395 ///
396 /// Set [stopIndicatorRadius] to 0 to hide the stop indicator.
397 ///
398 /// If null, then the [ProgressIndicatorThemeData.stopIndicatorRadius] will be used.
399 /// If that is null, then defaults to 2.
400 final double? stopIndicatorRadius;
401
402 /// The gap between the indicator and the track.
403 ///
404 /// If [year2023] is false or [ThemeData.useMaterial3] is false, then no track
405 /// gap will be drawn.
406 ///
407 /// Set [trackGap] to 0 to hide the track gap.
408 ///
409 /// If null, then the [ProgressIndicatorThemeData.trackGap] will be used.
410 /// If that is null, then defaults to 4.
411 final double? trackGap;
412
413 /// When true, the [LinearProgressIndicator] will use the 2023 Material Design 3
414 /// appearance.
415 ///
416 /// If null, then the [ProgressIndicatorThemeData.year2023] will be used.
417 /// If that is null, then defaults to true.
418 ///
419 /// If this is set to false, the [LinearProgressIndicator] will use the
420 /// latest Material Design 3 appearance, which was introduced in December 2023.
421 ///
422 /// If [ThemeData.useMaterial3] is false, then this property is ignored.
423 @Deprecated(
424 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. '
425 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. '
426 'This feature was deprecated after v3.27.0-0.1.pre.',
427 )
428 final bool? year2023;
429
430 @override
431 State<LinearProgressIndicator> createState() => _LinearProgressIndicatorState();
432}
433
434class _LinearProgressIndicatorState extends State<LinearProgressIndicator>
435 with SingleTickerProviderStateMixin {
436 late AnimationController _controller;
437
438 @override
439 void initState() {
440 super.initState();
441 _controller = AnimationController(
442 duration: const Duration(milliseconds: _kIndeterminateLinearDuration),
443 vsync: this,
444 );
445 if (widget.value == null) {
446 _controller.repeat();
447 }
448 }
449
450 @override
451 void didUpdateWidget(LinearProgressIndicator oldWidget) {
452 super.didUpdateWidget(oldWidget);
453 if (widget.value == null && !_controller.isAnimating) {
454 _controller.repeat();
455 } else if (widget.value != null && _controller.isAnimating) {
456 _controller.stop();
457 }
458 }
459
460 @override
461 void dispose() {
462 _controller.dispose();
463 super.dispose();
464 }
465
466 Widget _buildIndicator(BuildContext context, double animationValue, TextDirection textDirection) {
467 final ProgressIndicatorThemeData indicatorTheme = ProgressIndicatorTheme.of(context);
468 final bool year2023 = widget.year2023 ?? indicatorTheme.year2023 ?? true;
469 final ProgressIndicatorThemeData defaults = switch (Theme.of(context).useMaterial3) {
470 true =>
471 year2023
472 ? _LinearProgressIndicatorDefaultsM3Year2023(context)
473 : _LinearProgressIndicatorDefaultsM3(context),
474 false => _LinearProgressIndicatorDefaultsM2(context),
475 };
476 final Color trackColor =
477 widget.backgroundColor ?? indicatorTheme.linearTrackColor ?? defaults.linearTrackColor!;
478 final double minHeight =
479 widget.minHeight ?? indicatorTheme.linearMinHeight ?? defaults.linearMinHeight!;
480 final BorderRadiusGeometry? borderRadius =
481 widget.borderRadius ?? indicatorTheme.borderRadius ?? defaults.borderRadius;
482 final Color? stopIndicatorColor = !year2023
483 ? widget.stopIndicatorColor ??
484 indicatorTheme.stopIndicatorColor ??
485 defaults.stopIndicatorColor
486 : null;
487 final double? stopIndicatorRadius = !year2023
488 ? widget.stopIndicatorRadius ??
489 indicatorTheme.stopIndicatorRadius ??
490 defaults.stopIndicatorRadius
491 : null;
492 final double? trackGap = !year2023
493 ? widget.trackGap ?? indicatorTheme.trackGap ?? defaults.trackGap
494 : null;
495
496 Widget result = ConstrainedBox(
497 constraints: BoxConstraints(minWidth: double.infinity, minHeight: minHeight),
498 child: CustomPaint(
499 painter: _LinearProgressIndicatorPainter(
500 trackColor: trackColor,
501 valueColor: widget._getValueColor(context, defaultColor: defaults.color),
502 value: widget.value, // may be null
503 animationValue: animationValue, // ignored if widget.value is not null
504 textDirection: textDirection,
505 indicatorBorderRadius: borderRadius,
506 stopIndicatorColor: stopIndicatorColor,
507 stopIndicatorRadius: stopIndicatorRadius,
508 trackGap: trackGap,
509 ),
510 ),
511 );
512
513 // Clip is only needed with indeterminate progress indicators
514 if (borderRadius != null && widget.value == null) {
515 result = ClipRRect(borderRadius: borderRadius, child: result);
516 }
517
518 return widget._buildSemanticsWrapper(context: context, child: result);
519 }
520
521 @override
522 Widget build(BuildContext context) {
523 final TextDirection textDirection = Directionality.of(context);
524
525 if (widget.value != null) {
526 return _buildIndicator(context, _controller.value, textDirection);
527 }
528
529 return AnimatedBuilder(
530 animation: _controller.view,
531 builder: (BuildContext context, Widget? child) {
532 return _buildIndicator(context, _controller.value, textDirection);
533 },
534 );
535 }
536}
537
538class _CircularProgressIndicatorPainter extends CustomPainter {
539 _CircularProgressIndicatorPainter({
540 this.trackColor,
541 required this.valueColor,
542 required this.value,
543 required this.headValue,
544 required this.tailValue,
545 required this.offsetValue,
546 required this.rotationValue,
547 required this.strokeWidth,
548 required this.strokeAlign,
549 this.strokeCap,
550 this.trackGap,
551 this.year2023 = true,
552 }) : arcStart = value != null
553 ? _startAngle
554 : _startAngle +
555 tailValue * 3 / 2 * math.pi +
556 rotationValue * math.pi * 2.0 +
557 offsetValue * 0.5 * math.pi,
558 arcSweep = value != null
559 ? clampDouble(value, 0.0, 1.0) * _sweep
560 : math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon);
561
562 final Color? trackColor;
563 final Color valueColor;
564 final double? value;
565 final double headValue;
566 final double tailValue;
567 final double offsetValue;
568 final double rotationValue;
569 final double strokeWidth;
570 final double strokeAlign;
571 final double arcStart;
572 final double arcSweep;
573 final StrokeCap? strokeCap;
574 final double? trackGap;
575 final bool year2023;
576
577 static const double _twoPi = math.pi * 2.0;
578 static const double _epsilon = .001;
579 // Canvas.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close.
580 static const double _sweep = _twoPi - _epsilon;
581 static const double _startAngle = -math.pi / 2.0;
582
583 @override
584 void paint(Canvas canvas, Size size) {
585 final Paint paint = Paint()
586 ..color = valueColor
587 ..strokeWidth = strokeWidth
588 ..style = PaintingStyle.stroke;
589
590 // Use the negative operator as intended to keep the exposed constant value
591 // as users are already familiar with.
592 final double strokeOffset = strokeWidth / 2 * -strokeAlign;
593 final Offset arcBaseOffset = Offset(strokeOffset, strokeOffset);
594 final Size arcActualSize = Size(size.width - strokeOffset * 2, size.height - strokeOffset * 2);
595 final bool hasGap = trackGap != null && trackGap! > 0;
596
597 if (trackColor != null) {
598 final Paint backgroundPaint = Paint()
599 ..color = trackColor!
600 ..strokeWidth = strokeWidth
601 ..strokeCap = strokeCap ?? StrokeCap.round
602 ..style = PaintingStyle.stroke;
603 // If hasGap is true, draw the background arc with a gap.
604 if (hasGap && value != null && value! > _epsilon) {
605 final double arcRadius = arcActualSize.shortestSide / 2;
606 final double strokeRadius = strokeWidth / arcRadius;
607 final double gapRadius = trackGap! / arcRadius;
608 final double startGap = strokeRadius + gapRadius;
609 final double endGap = value! < _epsilon ? startGap : startGap * 2;
610 final double startSweep = (-math.pi / 2.0) + startGap;
611 final double endSweep = math.max(
612 0.0,
613 _twoPi - clampDouble(value!, 0.0, 1.0) * _twoPi - endGap,
614 );
615 // Flip the canvas for the background arc.
616 canvas.save();
617 canvas.scale(-1, 1);
618 canvas.translate(-size.width, 0);
619 canvas.drawArc(arcBaseOffset & arcActualSize, startSweep, endSweep, false, backgroundPaint);
620 // Restore the canvas to draw the foreground arc.
621 canvas.restore();
622 } else {
623 canvas.drawArc(arcBaseOffset & arcActualSize, 0, _sweep, false, backgroundPaint);
624 }
625 }
626
627 if (year2023) {
628 if (value == null && strokeCap == null) {
629 // Indeterminate
630 paint.strokeCap = StrokeCap.square;
631 } else {
632 // Butt when determinate (value != null) && strokeCap == null;
633 paint.strokeCap = strokeCap ?? StrokeCap.butt;
634 }
635 } else {
636 paint.strokeCap = strokeCap ?? StrokeCap.round;
637 }
638
639 canvas.drawArc(arcBaseOffset & arcActualSize, arcStart, arcSweep, false, paint);
640 }
641
642 @override
643 bool shouldRepaint(_CircularProgressIndicatorPainter oldPainter) {
644 return oldPainter.trackColor != trackColor ||
645 oldPainter.valueColor != valueColor ||
646 oldPainter.value != value ||
647 oldPainter.headValue != headValue ||
648 oldPainter.tailValue != tailValue ||
649 oldPainter.offsetValue != offsetValue ||
650 oldPainter.rotationValue != rotationValue ||
651 oldPainter.strokeWidth != strokeWidth ||
652 oldPainter.strokeAlign != strokeAlign ||
653 oldPainter.strokeCap != strokeCap ||
654 oldPainter.trackGap != trackGap ||
655 oldPainter.year2023 != year2023;
656 }
657}
658
659/// A Material Design circular progress indicator, which spins to indicate that
660/// the application is busy.
661///
662/// {@youtube 560 315 https://www.youtube.com/watch?v=O-rhXZLtpv0}
663///
664/// A widget that shows progress along a circle. There are two kinds of circular
665/// progress indicators:
666///
667/// * _Determinate_. Determinate progress indicators have a specific value at
668/// each point in time, and the value should increase monotonically from 0.0
669/// to 1.0, at which time the indicator is complete. To create a determinate
670/// progress indicator, use a non-null [value] between 0.0 and 1.0.
671/// * _Indeterminate_. Indeterminate progress indicators do not have a specific
672/// value at each point in time and instead indicate that progress is being
673/// made without indicating how much progress remains. To create an
674/// indeterminate progress indicator, use a null [value].
675///
676/// The indicator arc is displayed with [valueColor], an animated value. To
677/// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
678///
679/// {@tool dartpad}
680/// This example showcases determinate and indeterminate [CircularProgressIndicator]s.
681/// The [CircularProgressIndicator]s will use the ![updated Material 3 Design appearance](https://m3.material.io/components/progress-indicators/overview)
682/// when setting the [CircularProgressIndicator.year2023] flag to false.
683///
684/// ** See code in examples/api/lib/material/progress_indicator/circular_progress_indicator.0.dart **
685/// {@end-tool}
686///
687/// {@tool dartpad}
688/// This sample shows the creation of a [CircularProgressIndicator] with a changing value.
689/// When toggling the switch, [CircularProgressIndicator] uses a determinate value.
690/// As described in: https://m3.material.io/components/progress-indicators/overview
691///
692/// ** See code in examples/api/lib/material/progress_indicator/circular_progress_indicator.1.dart **
693/// {@end-tool}
694///
695/// See also:
696///
697/// * [LinearProgressIndicator], which displays progress along a line.
698/// * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
699/// when the underlying vertical scrollable is overscrolled.
700/// * <https://material.io/design/components/progress-indicators.html#circular-progress-indicators>
701class CircularProgressIndicator extends ProgressIndicator {
702 /// Creates a circular progress indicator.
703 ///
704 /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
705 const CircularProgressIndicator({
706 super.key,
707 super.value,
708 super.backgroundColor,
709 super.color,
710 super.valueColor,
711 this.strokeWidth,
712 this.strokeAlign,
713 super.semanticsLabel,
714 super.semanticsValue,
715 this.strokeCap,
716 this.constraints,
717 this.trackGap,
718 @Deprecated(
719 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. '
720 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. '
721 'This feature was deprecated after v3.27.0-0.1.pre.',
722 )
723 this.year2023,
724 this.padding,
725 }) : _indicatorType = _ActivityIndicatorType.material;
726
727 /// Creates an adaptive progress indicator that is a
728 /// [CupertinoActivityIndicator] on [TargetPlatform.iOS] &
729 /// [TargetPlatform.macOS] and a [CircularProgressIndicator] in material
730 /// theme/non-Apple platforms.
731 ///
732 /// The [valueColor], [strokeWidth], [strokeAlign], [strokeCap],
733 /// [semanticsLabel], [semanticsValue], [trackGap], [year2023] will be
734 /// ignored on iOS & macOS.
735 ///
736 /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
737 const CircularProgressIndicator.adaptive({
738 super.key,
739 super.value,
740 super.backgroundColor,
741 super.valueColor,
742 this.strokeWidth,
743 super.semanticsLabel,
744 super.semanticsValue,
745 this.strokeCap,
746 this.strokeAlign,
747 this.constraints,
748 this.trackGap,
749 @Deprecated(
750 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. '
751 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. '
752 'This feature was deprecated after v3.27.0-0.2.pre.',
753 )
754 this.year2023,
755 this.padding,
756 }) : _indicatorType = _ActivityIndicatorType.adaptive;
757
758 final _ActivityIndicatorType _indicatorType;
759
760 /// {@template flutter.material.CircularProgressIndicator.trackColor}
761 /// Color of the circular track being filled by the circular indicator.
762 ///
763 /// If [CircularProgressIndicator.backgroundColor] is null then the
764 /// ambient [ProgressIndicatorThemeData.circularTrackColor] will be used.
765 /// If that is null, then the track will not be painted.
766 /// {@endtemplate}
767 @override
768 Color? get backgroundColor => super.backgroundColor;
769
770 /// The width of the line used to draw the circle.
771 final double? strokeWidth;
772
773 /// The relative position of the stroke on a [CircularProgressIndicator].
774 ///
775 /// Values typically range from -1.0 ([strokeAlignInside], inside stroke)
776 /// to 1.0 ([strokeAlignOutside], outside stroke),
777 /// without any bound constraints (e.g., a value of -2.0 is not typical, but allowed).
778 /// A value of 0 ([strokeAlignCenter]) will center the border
779 /// on the edge of the widget.
780 ///
781 /// If [year2023] is true, then the default value is [strokeAlignCenter].
782 /// Otherwise, the default value is [strokeAlignInside].
783 final double? strokeAlign;
784
785 /// The progress indicator's line ending.
786 ///
787 /// This determines the shape of the stroke ends of the progress indicator.
788 /// By default, [strokeCap] is null.
789 /// When [value] is null (indeterminate), the stroke ends are set to
790 /// [StrokeCap.square]. When [value] is not null, the stroke
791 /// ends are set to [StrokeCap.butt].
792 ///
793 /// Setting [strokeCap] to [StrokeCap.round] will result in a rounded end.
794 /// Setting [strokeCap] to [StrokeCap.butt] with [value] == null will result
795 /// in a slightly different indeterminate animation; the indicator completely
796 /// disappears and reappears on its minimum value.
797 /// Setting [strokeCap] to [StrokeCap.square] with [value] != null will
798 /// result in a different display of [value]. The indicator will start
799 /// drawing from slightly less than the start, and end slightly after
800 /// the end. This will produce an alternative result, as the
801 /// default behavior, for example, that a [value] of 0.5 starts at 90 degrees
802 /// and ends at 270 degrees. With [StrokeCap.square], it could start 85
803 /// degrees and end at 275 degrees.
804 final StrokeCap? strokeCap;
805
806 /// Defines minimum and maximum sizes for a [CircularProgressIndicator].
807 ///
808 /// If null, then the [ProgressIndicatorThemeData.constraints] will be used.
809 /// Otherwise, defaults to a minimum width and height of 36 pixels.
810 final BoxConstraints? constraints;
811
812 /// The gap between the active indicator and the background track.
813 ///
814 /// If [year2023] is false or [ThemeData.useMaterial3] is false, then no track
815 /// gap will be drawn.
816 ///
817 /// Set [trackGap] to 0 to hide the track gap.
818 ///
819 /// If null, then the [ProgressIndicatorThemeData.trackGap] will be used.
820 /// If that is null, then defaults to 4.
821 final double? trackGap;
822
823 /// When true, the [CircularProgressIndicator] will use the 2023 Material Design 3
824 /// appearance.
825 ///
826 /// If null, then the [ProgressIndicatorThemeData.year2023] will be used.
827 /// If that is null, then defaults to true.
828 ///
829 /// If this is set to false, the [CircularProgressIndicator] will use the
830 /// latest Material Design 3 appearance, which was introduced in December 2023.
831 ///
832 /// If [ThemeData.useMaterial3] is false, then this property is ignored.
833 @Deprecated(
834 'Set this flag to false to opt into the 2024 progress indicator appearance. Defaults to true. '
835 'In the future, this flag will default to false. Use ProgressIndicatorThemeData to customize individual properties. '
836 'This feature was deprecated after v3.27.0-0.2.pre.',
837 )
838 final bool? year2023;
839
840 /// The padding around the indicator track.
841 ///
842 /// If null, then the [ProgressIndicatorThemeData.circularTrackPadding] will be
843 /// used. If that is null and [year2023] is false, then defaults to `EdgeInsets.all(4.0)`
844 /// padding. Otherwise, defaults to zero padding.
845 final EdgeInsetsGeometry? padding;
846
847 /// The indicator stroke is drawn fully inside of the indicator path.
848 ///
849 /// This is a constant for use with [strokeAlign].
850 static const double strokeAlignInside = -1.0;
851
852 /// The indicator stroke is drawn on the center of the indicator path,
853 /// with half of the [strokeWidth] on the inside, and the other half
854 /// on the outside of the path.
855 ///
856 /// This is a constant for use with [strokeAlign].
857 ///
858 /// This is the default value for [strokeAlign].
859 static const double strokeAlignCenter = 0.0;
860
861 /// The indicator stroke is drawn on the outside of the indicator path.
862 ///
863 /// This is a constant for use with [strokeAlign].
864 static const double strokeAlignOutside = 1.0;
865
866 @override
867 State<CircularProgressIndicator> createState() => _CircularProgressIndicatorState();
868}
869
870class _CircularProgressIndicatorState extends State<CircularProgressIndicator>
871 with SingleTickerProviderStateMixin {
872 static const int _pathCount = _kIndeterminateCircularDuration ~/ 1333;
873 static const int _rotationCount = _kIndeterminateCircularDuration ~/ 2222;
874
875 static final Animatable<double> _strokeHeadTween = CurveTween(
876 curve: const Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
877 ).chain(CurveTween(curve: const SawTooth(_pathCount)));
878 static final Animatable<double> _strokeTailTween = CurveTween(
879 curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
880 ).chain(CurveTween(curve: const SawTooth(_pathCount)));
881 static final Animatable<double> _offsetTween = CurveTween(curve: const SawTooth(_pathCount));
882 static final Animatable<double> _rotationTween = CurveTween(
883 curve: const SawTooth(_rotationCount),
884 );
885
886 late AnimationController _controller;
887
888 @override
889 void initState() {
890 super.initState();
891 _controller = AnimationController(
892 duration: const Duration(milliseconds: _kIndeterminateCircularDuration),
893 vsync: this,
894 );
895 if (widget.value == null) {
896 _controller.repeat();
897 }
898 }
899
900 @override
901 void didUpdateWidget(CircularProgressIndicator oldWidget) {
902 super.didUpdateWidget(oldWidget);
903 if (widget.value == null && !_controller.isAnimating) {
904 _controller.repeat();
905 } else if (widget.value != null && _controller.isAnimating) {
906 _controller.stop();
907 }
908 }
909
910 @override
911 void dispose() {
912 _controller.dispose();
913 super.dispose();
914 }
915
916 Widget _buildCupertinoIndicator(BuildContext context) {
917 final Color? tickColor = widget.backgroundColor;
918 final double? value = widget.value;
919 if (value == null) {
920 return CupertinoActivityIndicator(key: widget.key, color: tickColor);
921 }
922 return CupertinoActivityIndicator.partiallyRevealed(
923 key: widget.key,
924 color: tickColor,
925 progress: value,
926 );
927 }
928
929 Widget _buildMaterialIndicator(
930 BuildContext context,
931 double headValue,
932 double tailValue,
933 double offsetValue,
934 double rotationValue,
935 ) {
936 final ProgressIndicatorThemeData indicatorTheme = ProgressIndicatorTheme.of(context);
937 final bool year2023 = widget.year2023 ?? indicatorTheme.year2023 ?? true;
938 final ProgressIndicatorThemeData defaults = switch (Theme.of(context).useMaterial3) {
939 true =>
940 year2023
941 ? _CircularProgressIndicatorDefaultsM3Year2023(
942 context,
943 indeterminate: widget.value == null,
944 )
945 : _CircularProgressIndicatorDefaultsM3(context, indeterminate: widget.value == null),
946 false => _CircularProgressIndicatorDefaultsM2(context, indeterminate: widget.value == null),
947 };
948 final Color? trackColor =
949 widget.backgroundColor ?? indicatorTheme.circularTrackColor ?? defaults.circularTrackColor;
950 final double strokeWidth =
951 widget.strokeWidth ?? indicatorTheme.strokeWidth ?? defaults.strokeWidth!;
952 final double strokeAlign =
953 widget.strokeAlign ?? indicatorTheme.strokeAlign ?? defaults.strokeAlign!;
954 final StrokeCap? strokeCap = widget.strokeCap ?? indicatorTheme.strokeCap;
955 final BoxConstraints constraints =
956 widget.constraints ?? indicatorTheme.constraints ?? defaults.constraints!;
957 final double? trackGap = year2023
958 ? null
959 : widget.trackGap ?? indicatorTheme.trackGap ?? defaults.trackGap;
960 final EdgeInsetsGeometry? effectivePadding =
961 widget.padding ?? indicatorTheme.circularTrackPadding ?? defaults.circularTrackPadding;
962
963 Widget result = ConstrainedBox(
964 constraints: constraints,
965 child: CustomPaint(
966 painter: _CircularProgressIndicatorPainter(
967 trackColor: trackColor,
968 valueColor: widget._getValueColor(context, defaultColor: defaults.color),
969 value: widget.value, // may be null
970 headValue: headValue, // remaining arguments are ignored if widget.value is not null
971 tailValue: tailValue,
972 offsetValue: offsetValue,
973 rotationValue: rotationValue,
974 strokeWidth: strokeWidth,
975 strokeAlign: strokeAlign,
976 strokeCap: strokeCap,
977 trackGap: trackGap,
978 year2023: year2023,
979 ),
980 ),
981 );
982
983 if (effectivePadding != null) {
984 result = Padding(padding: effectivePadding, child: result);
985 }
986
987 return widget._buildSemanticsWrapper(context: context, child: result);
988 }
989
990 Widget _buildAnimation() {
991 return AnimatedBuilder(
992 animation: _controller,
993 builder: (BuildContext context, Widget? child) {
994 return _buildMaterialIndicator(
995 context,
996 _strokeHeadTween.evaluate(_controller),
997 _strokeTailTween.evaluate(_controller),
998 _offsetTween.evaluate(_controller),
999 _rotationTween.evaluate(_controller),
1000 );
1001 },
1002 );
1003 }
1004
1005 @override
1006 Widget build(BuildContext context) {
1007 switch (widget._indicatorType) {
1008 case _ActivityIndicatorType.material:
1009 if (widget.value != null) {
1010 return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
1011 }
1012 return _buildAnimation();
1013 case _ActivityIndicatorType.adaptive:
1014 final ThemeData theme = Theme.of(context);
1015 switch (theme.platform) {
1016 case TargetPlatform.iOS:
1017 case TargetPlatform.macOS:
1018 return _buildCupertinoIndicator(context);
1019 case TargetPlatform.android:
1020 case TargetPlatform.fuchsia:
1021 case TargetPlatform.linux:
1022 case TargetPlatform.windows:
1023 if (widget.value != null) {
1024 return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
1025 }
1026 return _buildAnimation();
1027 }
1028 }
1029 }
1030}
1031
1032class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter {
1033 _RefreshProgressIndicatorPainter({
1034 required super.valueColor,
1035 required super.value,
1036 required super.headValue,
1037 required super.tailValue,
1038 required super.offsetValue,
1039 required super.rotationValue,
1040 required super.strokeWidth,
1041 required super.strokeAlign,
1042 required this.arrowheadScale,
1043 required super.strokeCap,
1044 });
1045
1046 final double arrowheadScale;
1047
1048 void paintArrowhead(Canvas canvas, Size size) {
1049 // ux, uy: a unit vector whose direction parallels the base of the arrowhead.
1050 // (So ux, -uy points in the direction the arrowhead points.)
1051 final double arcEnd = arcStart + arcSweep;
1052 final double ux = math.cos(arcEnd);
1053 final double uy = math.sin(arcEnd);
1054
1055 assert(size.width == size.height);
1056 final double radius = size.width / 2.0;
1057 final double arrowheadPointX = radius + ux * radius + -uy * strokeWidth * 2.0 * arrowheadScale;
1058 final double arrowheadPointY = radius + uy * radius + ux * strokeWidth * 2.0 * arrowheadScale;
1059 final double arrowheadRadius = strokeWidth * 2.0 * arrowheadScale;
1060 final double innerRadius = radius - arrowheadRadius;
1061 final double outerRadius = radius + arrowheadRadius;
1062
1063 final Path path = Path()
1064 ..moveTo(radius + ux * innerRadius, radius + uy * innerRadius)
1065 ..lineTo(radius + ux * outerRadius, radius + uy * outerRadius)
1066 ..lineTo(arrowheadPointX, arrowheadPointY)
1067 ..close();
1068
1069 final Paint paint = Paint()
1070 ..color = valueColor
1071 ..strokeWidth = strokeWidth
1072 ..style = PaintingStyle.fill;
1073 canvas.drawPath(path, paint);
1074 }
1075
1076 @override
1077 void paint(Canvas canvas, Size size) {
1078 super.paint(canvas, size);
1079 if (arrowheadScale > 0.0) {
1080 paintArrowhead(canvas, size);
1081 }
1082 }
1083}
1084
1085/// An indicator for the progress of refreshing the contents of a widget.
1086///
1087/// Typically used for swipe-to-refresh interactions. See [RefreshIndicator] for
1088/// a complete implementation of swipe-to-refresh driven by a [Scrollable]
1089/// widget.
1090///
1091/// The indicator arc is displayed with [valueColor], an animated value. To
1092/// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
1093///
1094/// See also:
1095///
1096/// * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
1097/// when the underlying vertical scrollable is overscrolled.
1098class RefreshProgressIndicator extends CircularProgressIndicator {
1099 /// Creates a refresh progress indicator.
1100 ///
1101 /// Rather than creating a refresh progress indicator directly, consider using
1102 /// a [RefreshIndicator] together with a [Scrollable] widget.
1103 ///
1104 /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
1105 const RefreshProgressIndicator({
1106 super.key,
1107 super.value,
1108 super.backgroundColor,
1109 super.color,
1110 super.valueColor,
1111 super.strokeWidth = defaultStrokeWidth, // Different default than CircularProgressIndicator.
1112 super.strokeAlign,
1113 super.semanticsLabel,
1114 super.semanticsValue,
1115 super.strokeCap,
1116 this.elevation = 2.0,
1117 this.indicatorMargin = const EdgeInsets.all(4.0),
1118 this.indicatorPadding = const EdgeInsets.all(12.0),
1119 });
1120
1121 /// {@macro flutter.material.material.elevation}
1122 final double elevation;
1123
1124 /// The amount of space by which to inset the whole indicator.
1125 /// It accommodates the [elevation] of the indicator.
1126 final EdgeInsetsGeometry indicatorMargin;
1127
1128 /// The amount of space by which to inset the inner refresh indicator.
1129 final EdgeInsetsGeometry indicatorPadding;
1130
1131 /// Default stroke width.
1132 static const double defaultStrokeWidth = 2.5;
1133
1134 /// {@template flutter.material.RefreshProgressIndicator.backgroundColor}
1135 /// Background color of that fills the circle under the refresh indicator.
1136 ///
1137 /// If [RefreshIndicator.backgroundColor] is null then the
1138 /// ambient [ProgressIndicatorThemeData.refreshBackgroundColor] will be used.
1139 /// If that is null, then the ambient theme's [ThemeData.canvasColor]
1140 /// will be used.
1141 /// {@endtemplate}
1142 @override
1143 Color? get backgroundColor => super.backgroundColor;
1144
1145 @override
1146 State<CircularProgressIndicator> createState() => _RefreshProgressIndicatorState();
1147}
1148
1149class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
1150 static const double _indicatorSize = 41.0;
1151
1152 /// Interval for arrow head to fully grow.
1153 static const double _strokeHeadInterval = 0.33;
1154
1155 late final Animatable<double> _convertTween = CurveTween(
1156 curve: const Interval(0.1, _strokeHeadInterval),
1157 );
1158
1159 late final Animatable<double> _additionalRotationTween = TweenSequence<double>(
1160 <TweenSequenceItem<double>>[
1161 // Makes arrow to expand a little bit earlier, to match the Android look.
1162 TweenSequenceItem<double>(
1163 tween: Tween<double>(begin: -0.1, end: -0.2),
1164 weight: _strokeHeadInterval,
1165 ),
1166 // Additional rotation after the arrow expanded
1167 TweenSequenceItem<double>(
1168 tween: Tween<double>(begin: -0.2, end: 1.35),
1169 weight: 1 - _strokeHeadInterval,
1170 ),
1171 ],
1172 );
1173
1174 // Last value received from the widget before null.
1175 double? _lastValue;
1176
1177 /// Force casting the widget as [RefreshProgressIndicator].
1178 @override
1179 RefreshProgressIndicator get widget => super.widget as RefreshProgressIndicator;
1180
1181 // Always show the indeterminate version of the circular progress indicator.
1182 //
1183 // When value is non-null the sweep of the progress indicator arrow's arc
1184 // varies from 0 to about 300 degrees.
1185 //
1186 // When value is null the arrow animation starting from wherever we left it.
1187 @override
1188 Widget build(BuildContext context) {
1189 final double? value = widget.value;
1190 if (value != null) {
1191 _lastValue = value;
1192 _controller.value =
1193 _convertTween.transform(value) * (1333 / 2 / _kIndeterminateCircularDuration);
1194 }
1195 return _buildAnimation();
1196 }
1197
1198 @override
1199 Widget _buildAnimation() {
1200 return AnimatedBuilder(
1201 animation: _controller,
1202 builder: (BuildContext context, Widget? child) {
1203 return _buildMaterialIndicator(
1204 context,
1205 // Lengthen the arc a little
1206 1.05 * _CircularProgressIndicatorState._strokeHeadTween.evaluate(_controller),
1207 _CircularProgressIndicatorState._strokeTailTween.evaluate(_controller),
1208 _CircularProgressIndicatorState._offsetTween.evaluate(_controller),
1209 _CircularProgressIndicatorState._rotationTween.evaluate(_controller),
1210 );
1211 },
1212 );
1213 }
1214
1215 @override
1216 Widget _buildMaterialIndicator(
1217 BuildContext context,
1218 double headValue,
1219 double tailValue,
1220 double offsetValue,
1221 double rotationValue,
1222 ) {
1223 final double? value = widget.value;
1224 final double arrowheadScale = value == null
1225 ? 0.0
1226 : const Interval(0.1, _strokeHeadInterval).transform(value);
1227 final double rotation;
1228
1229 if (value == null && _lastValue == null) {
1230 rotation = 0.0;
1231 } else {
1232 rotation = math.pi * _additionalRotationTween.transform(value ?? _lastValue!);
1233 }
1234
1235 Color valueColor = widget._getValueColor(context);
1236 final double opacity = valueColor.opacity;
1237 valueColor = valueColor.withOpacity(1.0);
1238
1239 final ProgressIndicatorThemeData defaults = switch (Theme.of(context).useMaterial3) {
1240 true => _CircularProgressIndicatorDefaultsM3Year2023(context, indeterminate: value == null),
1241 false => _CircularProgressIndicatorDefaultsM2(context, indeterminate: value == null),
1242 };
1243 final ProgressIndicatorThemeData indicatorTheme = ProgressIndicatorTheme.of(context);
1244 final Color backgroundColor =
1245 widget.backgroundColor ??
1246 indicatorTheme.refreshBackgroundColor ??
1247 Theme.of(context).canvasColor;
1248 final double strokeWidth =
1249 widget.strokeWidth ?? indicatorTheme.strokeWidth ?? defaults.strokeWidth!;
1250 final double strokeAlign =
1251 widget.strokeAlign ?? indicatorTheme.strokeAlign ?? defaults.strokeAlign!;
1252 final StrokeCap? strokeCap = widget.strokeCap ?? indicatorTheme.strokeCap;
1253
1254 return widget._buildSemanticsWrapper(
1255 context: context,
1256 child: Padding(
1257 padding: widget.indicatorMargin,
1258 child: SizedBox.fromSize(
1259 size: const Size.square(_indicatorSize),
1260 child: Material(
1261 type: MaterialType.circle,
1262 color: backgroundColor,
1263 elevation: widget.elevation,
1264 child: Padding(
1265 padding: widget.indicatorPadding,
1266 child: Opacity(
1267 opacity: opacity,
1268 child: Transform.rotate(
1269 angle: rotation,
1270 child: CustomPaint(
1271 painter: _RefreshProgressIndicatorPainter(
1272 valueColor: valueColor,
1273 value: null, // Draw the indeterminate progress indicator.
1274 headValue: headValue,
1275 tailValue: tailValue,
1276 offsetValue: offsetValue,
1277 rotationValue: rotationValue,
1278 strokeWidth: strokeWidth,
1279 strokeAlign: strokeAlign,
1280 arrowheadScale: arrowheadScale,
1281 strokeCap: strokeCap,
1282 ),
1283 ),
1284 ),
1285 ),
1286 ),
1287 ),
1288 ),
1289 ),
1290 );
1291 }
1292}
1293
1294// Hand coded defaults based on Material Design 2.
1295class _CircularProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData {
1296 _CircularProgressIndicatorDefaultsM2(this.context, {required this.indeterminate});
1297
1298 final BuildContext context;
1299 late final ColorScheme _colors = Theme.of(context).colorScheme;
1300 final bool indeterminate;
1301
1302 @override
1303 Color get color => _colors.primary;
1304
1305 @override
1306 double? get strokeWidth => 4.0;
1307
1308 @override
1309 double? get strokeAlign => CircularProgressIndicator.strokeAlignCenter;
1310
1311 @override
1312 BoxConstraints get constraints => const BoxConstraints(minWidth: 36.0, minHeight: 36.0);
1313}
1314
1315class _LinearProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData {
1316 _LinearProgressIndicatorDefaultsM2(this.context);
1317
1318 final BuildContext context;
1319 late final ColorScheme _colors = Theme.of(context).colorScheme;
1320
1321 @override
1322 Color get color => _colors.primary;
1323
1324 @override
1325 Color get linearTrackColor => _colors.background;
1326
1327 @override
1328 double get linearMinHeight => 4.0;
1329}
1330
1331class _CircularProgressIndicatorDefaultsM3Year2023 extends ProgressIndicatorThemeData {
1332 _CircularProgressIndicatorDefaultsM3Year2023(this.context, {required this.indeterminate});
1333
1334 final BuildContext context;
1335 late final ColorScheme _colors = Theme.of(context).colorScheme;
1336 final bool indeterminate;
1337
1338 @override
1339 Color get color => _colors.primary;
1340
1341 @override
1342 double get strokeWidth => 4.0;
1343
1344 @override
1345 double? get strokeAlign => CircularProgressIndicator.strokeAlignCenter;
1346
1347 @override
1348 BoxConstraints get constraints => const BoxConstraints(minWidth: 36.0, minHeight: 36.0);
1349}
1350
1351class _LinearProgressIndicatorDefaultsM3Year2023 extends ProgressIndicatorThemeData {
1352 _LinearProgressIndicatorDefaultsM3Year2023(this.context);
1353
1354 final BuildContext context;
1355 late final ColorScheme _colors = Theme.of(context).colorScheme;
1356
1357 @override
1358 Color get color => _colors.primary;
1359
1360 @override
1361 Color get linearTrackColor => _colors.secondaryContainer;
1362
1363 @override
1364 double get linearMinHeight => 4.0;
1365}
1366
1367// BEGIN GENERATED TOKEN PROPERTIES - ProgressIndicator
1368
1369// Do not edit by hand. The code between the "BEGIN GENERATED" and
1370// "END GENERATED" comments are generated from data in the Material
1371// Design token database by the script:
1372// dev/tools/gen_defaults/bin/gen_defaults.dart.
1373
1374// dart format off
1375class _CircularProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
1376 _CircularProgressIndicatorDefaultsM3(this.context, { required this.indeterminate });
1377
1378 final BuildContext context;
1379 late final ColorScheme _colors = Theme.of(context).colorScheme;
1380 final bool indeterminate;
1381
1382 @override
1383 Color get color => _colors.primary;
1384
1385 @override
1386 Color? get circularTrackColor => indeterminate ? null : _colors.secondaryContainer;
1387
1388 @override
1389 double get strokeWidth => 4.0;
1390
1391 @override
1392 double? get strokeAlign => CircularProgressIndicator.strokeAlignInside;
1393
1394 @override
1395 BoxConstraints get constraints => const BoxConstraints(
1396 minWidth: 40.0,
1397 minHeight: 40.0,
1398 );
1399
1400 @override
1401 double? get trackGap => 4.0;
1402
1403 @override
1404 EdgeInsetsGeometry? get circularTrackPadding => const EdgeInsets.all(4.0);
1405}
1406
1407class _LinearProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
1408 _LinearProgressIndicatorDefaultsM3(this.context);
1409
1410 final BuildContext context;
1411 late final ColorScheme _colors = Theme.of(context).colorScheme;
1412
1413 @override
1414 Color get color => _colors.primary;
1415
1416 @override
1417 Color get linearTrackColor => _colors.secondaryContainer;
1418
1419 @override
1420 double get linearMinHeight => 4.0;
1421
1422 @override
1423 BorderRadius get borderRadius => BorderRadius.circular(4.0 / 2);
1424
1425 @override
1426 Color get stopIndicatorColor => _colors.primary;
1427
1428 @override
1429 double? get stopIndicatorRadius => 4.0 / 2;
1430
1431 @override
1432 double? get trackGap => 4.0;
1433}
1434// dart format on
1435
1436// END GENERATED TOKEN PROPERTIES - ProgressIndicator
1437