Update Material 3 CircularProgressIndicator for new visual style (#158104)

Related [Update both `ProgressIndicator` for Material 3
redesign](https://github.com/flutter/flutter/issues/141340)
Fixes [Issue: Cannot theme progress indicators, many properties
missing](https://github.com/flutter/flutter/issues/131690)
Fixes [Cannot override default `CircularProgressIndicator`
size](https://github.com/flutter/flutter/issues/158106)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
          // progressIndicatorTheme: const ProgressIndicatorThemeData(
          //   constraints: BoxConstraints.tightFor(width: 100, height: 100),
          //   strokeWidth: 12
          // ),
          ),
      home: Scaffold(
        appBar: AppBar(title: const Text('CircularProgressIndicator')),
        body: const Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              CircularProgressIndicator(year2023: false, value: 0.5),
              CircularProgressIndicator(year2023: false),
            ],
          ),
        ),
      ),
    );
  }
}
```

</details>

### Preview 

<img width="579" alt="Screenshot 2024-11-04 at 16 01 57"
src="https://github.com/user-attachments/assets/d27768c6-5570-48d0-9eed-565e02be8041">

### New custom `CircularProgressIndicator.constraints` and stroke  width

<img width="579" alt="Screenshot 2024-11-04 at 16 02 40"
src="https://github.com/user-attachments/assets/c67c4a31-58f4-4f82-bfb6-f1b78a000bac">


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Taha Tesser 2024-11-20 01:23:26 +02:00 committed by GitHub
parent a1c7a0dda4
commit 1686fa7eb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 607 additions and 60 deletions

View File

@ -12,16 +12,32 @@ class ProgressIndicatorTemplate extends TokenTemplate {
@override
String generate() => '''
class _Circular${blockName}DefaultsM3 extends ProgressIndicatorThemeData {
_Circular${blockName}DefaultsM3(this.context);
_Circular${blockName}DefaultsM3(this.context, { required this.indeterminate });
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
final bool indeterminate;
@override
Color get color => ${componentColor('md.comp.progress-indicator.active-indicator')};
@override
Color get circularTrackColor => ${componentColor('md.comp.progress-indicator.track')};
Color? get circularTrackColor => indeterminate ? null : ${componentColor('md.comp.progress-indicator.track')};
@override
double get strokeWidth => ${getToken('md.comp.progress-indicator.track.thickness')};
@override
double? get strokeAlign => CircularProgressIndicator.strokeAlignInside;
@override
BoxConstraints get constraints => const BoxConstraints(
minWidth: 48.0,
minHeight: 48.0,
);
@override
double? get trackGap => ${getToken('md.comp.progress-indicator.active-indicator-track-space')};
}
class _Linear${blockName}DefaultsM3 extends ProgressIndicatorThemeData {

View File

@ -17,7 +17,6 @@ import 'material.dart';
import 'progress_indicator_theme.dart';
import 'theme.dart';
const double _kMinCircularProgressIndicatorSize = 36.0;
const int _kIndeterminateLinearDuration = 1800;
const int _kIndeterminateCircularDuration = 1333 * 2222;
@ -550,7 +549,7 @@ class _LinearProgressIndicatorState extends State<LinearProgressIndicator> with
class _CircularProgressIndicatorPainter extends CustomPainter {
_CircularProgressIndicatorPainter({
this.backgroundColor,
this.trackColor,
required this.valueColor,
required this.value,
required this.headValue,
@ -560,6 +559,8 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
required this.strokeWidth,
required this.strokeAlign,
this.strokeCap,
this.trackGap,
this.year2023 = true,
}) : arcStart = value != null
? _startAngle
: _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi,
@ -567,7 +568,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
? clampDouble(value, 0.0, 1.0) * _sweep
: math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon);
final Color? backgroundColor;
final Color? trackColor;
final Color valueColor;
final double? value;
final double headValue;
@ -579,6 +580,8 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
final double arcStart;
final double arcSweep;
final StrokeCap? strokeCap;
final double? trackGap;
final bool year2023;
static const double _twoPi = math.pi * 2.0;
static const double _epsilon = .001;
@ -601,27 +604,57 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
size.width - strokeOffset * 2,
size.height - strokeOffset * 2,
);
final bool hasGap = trackGap != null && trackGap! > 0;
if (backgroundColor != null) {
if (trackColor != null) {
final Paint backgroundPaint = Paint()
..color = backgroundColor!
..color = trackColor!
..strokeWidth = strokeWidth
..strokeCap = strokeCap ?? StrokeCap.round
..style = PaintingStyle.stroke;
canvas.drawArc(
arcBaseOffset & arcActualSize,
0,
_sweep,
false,
backgroundPaint,
);
// If hasGap is true, draw the background arc with a gap.
if (hasGap && value! > _epsilon) {
final double arcRadius = arcActualSize.shortestSide / 2;
final double strokeRadius = strokeWidth / arcRadius;
final double gapRadius = trackGap! / arcRadius;
final double startGap = strokeRadius + gapRadius;
final double endGap = value! < _epsilon ? startGap : startGap * 2;
final double startSweep = (-math.pi / 2.0) + startGap;
final double endSweep = math.max(0.0, _twoPi - clampDouble(value!, 0.0, 1.0) * _twoPi - endGap);
// Flip the canvas for the background arc.
canvas.save();
canvas.scale(-1, 1);
canvas.translate(-size.width, 0);
canvas.drawArc(
arcBaseOffset & arcActualSize,
startSweep,
endSweep,
false,
backgroundPaint,
);
// Restore the canvas to draw the foreground arc.
canvas.restore();
} else {
canvas.drawArc(
arcBaseOffset & arcActualSize,
0,
_sweep,
false,
backgroundPaint,
);
}
}
if (value == null && strokeCap == null) {
// Indeterminate
paint.strokeCap = StrokeCap.square;
if (year2023) {
if (value == null && strokeCap == null) {
// Indeterminate
paint.strokeCap = StrokeCap.square;
} else {
// Butt when determinate (value != null) && strokeCap == null;
paint.strokeCap = strokeCap ?? StrokeCap.butt;
}
} else {
// Butt when determinate (value != null) && strokeCap == null;
paint.strokeCap = strokeCap ?? StrokeCap.butt;
paint.strokeCap = strokeCap ?? StrokeCap.round;
}
canvas.drawArc(
@ -635,7 +668,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
@override
bool shouldRepaint(_CircularProgressIndicatorPainter oldPainter) {
return oldPainter.backgroundColor != backgroundColor
return oldPainter.trackColor != trackColor
|| oldPainter.valueColor != valueColor
|| oldPainter.value != value
|| oldPainter.headValue != headValue
@ -644,7 +677,9 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
|| oldPainter.rotationValue != rotationValue
|| oldPainter.strokeWidth != strokeWidth
|| oldPainter.strokeAlign != strokeAlign
|| oldPainter.strokeCap != strokeCap;
|| oldPainter.strokeCap != strokeCap
|| oldPainter.trackGap != trackGap
|| oldPainter.year2023 != year2023;
}
}
@ -698,19 +733,28 @@ class CircularProgressIndicator extends ProgressIndicator {
super.backgroundColor,
super.color,
super.valueColor,
this.strokeWidth = 4.0,
this.strokeAlign = strokeAlignCenter,
this.strokeWidth,
this.strokeAlign,
super.semanticsLabel,
super.semanticsValue,
this.strokeCap,
this.constraints,
this.trackGap,
@Deprecated(
'Use ProgressIndicatorTheme to customize the ProgressIndicator appearance. '
'This feature was deprecated after v3.27.0-0.1.pre.'
)
this.year2023 = true,
}) : _indicatorType = _ActivityIndicatorType.material;
/// Creates an adaptive progress indicator that is a
/// [CupertinoActivityIndicator] in [TargetPlatform.iOS] & [TargetPlatform.macOS] and [CircularProgressIndicator] in
/// material theme/non-Apple platforms.
/// [CupertinoActivityIndicator] on [TargetPlatform.iOS] &
/// [TargetPlatform.macOS] and a [CircularProgressIndicator] in material
/// theme/non-Apple platforms.
///
/// The [value], [valueColor], [strokeWidth], [semanticsLabel], and
/// [semanticsValue] will be ignored in iOS & macOS.
/// The [valueColor], [strokeWidth], [strokeAlign], [strokeCap],
/// [semanticsLabel], [semanticsValue], [trackGap], [year2023] will be
/// ignored on iOS & macOS.
///
/// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
const CircularProgressIndicator.adaptive({
@ -722,7 +766,14 @@ class CircularProgressIndicator extends ProgressIndicator {
super.semanticsLabel,
super.semanticsValue,
this.strokeCap,
this.strokeAlign = strokeAlignCenter,
this.strokeAlign,
this.constraints,
this.trackGap,
@Deprecated(
'Use ProgressIndicatorTheme to customize the ProgressIndicator appearance. '
'This feature was deprecated after v3.27.0-0.2.pre.'
)
this.year2023 = true,
}) : _indicatorType = _ActivityIndicatorType.adaptive;
final _ActivityIndicatorType _indicatorType;
@ -738,16 +789,19 @@ class CircularProgressIndicator extends ProgressIndicator {
Color? get backgroundColor => super.backgroundColor;
/// The width of the line used to draw the circle.
final double strokeWidth;
final double? strokeWidth;
/// The relative position of the stroke on a [CircularProgressIndicator].
///
/// Values typically range from -1.0 ([strokeAlignInside], inside stroke)
/// to 1.0 ([strokeAlignOutside], outside stroke),
/// without any bound constraints (e.g., a value of -2.0 is not typical, but allowed).
/// A value of 0 ([strokeAlignCenter], default) will center the border
/// A value of 0 ([strokeAlignCenter]) will center the border
/// on the edge of the widget.
final double strokeAlign;
///
/// If [year2023] is true, then the default value is [strokeAlignCenter].
/// Otherwise, the default value is [strokeAlignInside].
final double? strokeAlign;
/// The progress indicator's line ending.
///
@ -770,6 +824,36 @@ class CircularProgressIndicator extends ProgressIndicator {
/// degrees and end at 275 degrees.
final StrokeCap? strokeCap;
/// Defines minimum and maximum sizes for a [CircularProgressIndicator].
///
/// If null, then the [ProgressIndicatorThemeData.constraints] will be used.
/// Otherwise, defaults to a minimum width and height of 36 pixels.
final BoxConstraints? constraints;
/// The gap between the active indicator and the background track.
///
/// If [year2023] is false or [ThemeData.useMaterial3] is false, then no track
/// gap will be drawn.
///
/// Set [trackGap] to 0 to hide the track gap.
///
/// If null, then the [ProgressIndicatorThemeData.trackGap] will be used.
/// If that is null, then defaults to 4.
final double? trackGap;
/// When true, the [CircularProgressIndicator] will use the 2023 Material 3
/// Design appearance.
///
/// Defaults to true. If false, the [CircularProgressIndicator] will use the
/// latest Material 3 Design appearance, which was introduced in December 2023.
///
/// If [ThemeData.useMaterial3] is false, then this property is ignored.
@Deprecated(
'Use ProgressIndicatorTheme to customize the ProgressIndicator appearance. '
'This feature was deprecated after v3.27.0-0.2.pre.'
)
final bool year2023;
/// The indicator stroke is drawn fully inside of the indicator path.
///
/// This is a constant for use with [strokeAlign].
@ -857,30 +941,50 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
}
Widget _buildMaterialIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
final ProgressIndicatorThemeData defaults = Theme.of(context).useMaterial3
? _CircularProgressIndicatorDefaultsM3(context)
: _CircularProgressIndicatorDefaultsM2(context);
final Color? trackColor = widget.backgroundColor ?? ProgressIndicatorTheme.of(context).circularTrackColor;
final ProgressIndicatorThemeData defaults = switch (Theme.of(context).useMaterial3) {
true => widget.year2023
? _CircularProgressIndicatorDefaultsM3Year2023(context, indeterminate: widget.value == null)
: _CircularProgressIndicatorDefaultsM3(context, indeterminate: widget.value == null),
false => _CircularProgressIndicatorDefaultsM2(context, indeterminate: widget.value == null),
};
final ProgressIndicatorThemeData indicatorTheme = ProgressIndicatorTheme.of(context);
final Color? trackColor = widget.backgroundColor
?? indicatorTheme.circularTrackColor
?? defaults.circularTrackColor;
final double strokeWidth = widget.strokeWidth
?? indicatorTheme.strokeWidth
?? defaults.strokeWidth!;
final double strokeAlign = widget.strokeAlign
?? indicatorTheme.strokeAlign
?? defaults.strokeAlign!;
final StrokeCap? strokeCap = widget.strokeCap
?? indicatorTheme.strokeCap;
final BoxConstraints constraints = widget.constraints
?? indicatorTheme.constraints
?? defaults.constraints!;
final double? trackGap = widget.year2023
? null
: widget.trackGap ??
indicatorTheme.trackGap ??
defaults.trackGap;
return widget._buildSemanticsWrapper(
context: context,
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: _kMinCircularProgressIndicatorSize,
minHeight: _kMinCircularProgressIndicatorSize,
),
constraints: constraints,
child: CustomPaint(
painter: _CircularProgressIndicatorPainter(
backgroundColor: trackColor,
trackColor: trackColor,
valueColor: widget._getValueColor(context, defaultColor: defaults.color),
value: widget.value, // may be null
headValue: headValue, // remaining arguments are ignored if widget.value is not null
tailValue: tailValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
strokeAlign: widget.strokeAlign,
strokeCap: widget.strokeCap,
strokeWidth: strokeWidth,
strokeAlign: strokeAlign,
strokeCap: strokeCap,
trackGap: trackGap,
year2023: widget.year2023,
),
),
),
@ -1128,10 +1232,22 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
final double opacity = valueColor.opacity;
valueColor = valueColor.withOpacity(1.0);
final Color backgroundColor =
widget.backgroundColor ??
ProgressIndicatorTheme.of(context).refreshBackgroundColor ??
Theme.of(context).canvasColor;
final ProgressIndicatorThemeData defaults = switch (Theme.of(context).useMaterial3) {
true => _CircularProgressIndicatorDefaultsM3Year2023(context, indeterminate: value == null),
false => _CircularProgressIndicatorDefaultsM2(context, indeterminate: value == null),
};
final ProgressIndicatorThemeData indicatorTheme = ProgressIndicatorTheme.of(context);
final Color backgroundColor = widget.backgroundColor
?? indicatorTheme.refreshBackgroundColor
?? Theme.of(context).canvasColor;
final double strokeWidth = widget.strokeWidth
?? indicatorTheme.strokeWidth
?? defaults.strokeWidth!;
final double strokeAlign = widget.strokeAlign
?? indicatorTheme.strokeAlign
?? defaults.strokeAlign!;
final StrokeCap? strokeCap = widget.strokeCap
?? indicatorTheme.strokeCap;
return widget._buildSemanticsWrapper(
context: context,
@ -1157,10 +1273,10 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
tailValue: tailValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
strokeAlign: widget.strokeAlign,
strokeWidth: strokeWidth,
strokeAlign: strokeAlign,
arrowheadScale: arrowheadScale,
strokeCap: widget.strokeCap,
strokeCap: strokeCap,
),
),
),
@ -1175,13 +1291,26 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
// Hand coded defaults based on Material Design 2.
class _CircularProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData {
_CircularProgressIndicatorDefaultsM2(this.context);
_CircularProgressIndicatorDefaultsM2(this.context, { required this.indeterminate });
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
final bool indeterminate;
@override
Color get color => _colors.primary;
@override
double? get strokeWidth => 4.0;
@override
double? get strokeAlign => CircularProgressIndicator.strokeAlignCenter;
@override
BoxConstraints get constraints => const BoxConstraints(
minWidth: 36.0,
minHeight: 36.0,
);
}
class _LinearProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData {
@ -1200,6 +1329,29 @@ class _LinearProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData {
double get linearMinHeight => 4.0;
}
class _CircularProgressIndicatorDefaultsM3Year2023 extends ProgressIndicatorThemeData {
_CircularProgressIndicatorDefaultsM3Year2023(this.context, { required this.indeterminate });
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
final bool indeterminate;
@override
Color get color => _colors.primary;
@override
double get strokeWidth => 4.0;
@override
double? get strokeAlign => CircularProgressIndicator.strokeAlignCenter;
@override
BoxConstraints get constraints => const BoxConstraints(
minWidth: 36.0,
minHeight: 36.0,
);
}
class _LinearProgressIndicatorDefaultsM3Year2023 extends ProgressIndicatorThemeData {
_LinearProgressIndicatorDefaultsM3Year2023(this.context);
@ -1224,16 +1376,32 @@ class _LinearProgressIndicatorDefaultsM3Year2023 extends ProgressIndicatorThemeD
// dev/tools/gen_defaults/bin/gen_defaults.dart.
class _CircularProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
_CircularProgressIndicatorDefaultsM3(this.context);
_CircularProgressIndicatorDefaultsM3(this.context, { required this.indeterminate });
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
final bool indeterminate;
@override
Color get color => _colors.primary;
@override
Color get circularTrackColor => _colors.secondaryContainer;
Color? get circularTrackColor => indeterminate ? null : _colors.secondaryContainer;
@override
double get strokeWidth => 4.0;
@override
double? get strokeAlign => CircularProgressIndicator.strokeAlignInside;
@override
BoxConstraints get constraints => const BoxConstraints(
minWidth: 48.0,
minHeight: 48.0,
);
@override
double? get trackGap => 4.0;
}
class _LinearProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {

View File

@ -42,6 +42,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
this.borderRadius,
this.stopIndicatorColor,
this.stopIndicatorRadius,
this.strokeWidth,
this.strokeAlign,
this.strokeCap,
this.constraints,
this.trackGap,
});
@ -85,7 +89,22 @@ class ProgressIndicatorThemeData with Diagnosticable {
/// is false, then no stop indicator will be drawn.
final double? stopIndicatorRadius;
/// Overrides the gap between the [LinearProgressIndicator].
/// Overrides the stroke width of the [CircularProgressIndicator].
final double? strokeWidth;
/// Overrides the stroke align of the [CircularProgressIndicator].
final double? strokeAlign;
/// Overrides the stroke cap of the [CircularProgressIndicator].
final StrokeCap? strokeCap;
/// Overrides the constraints of the [CircularProgressIndicator].
final BoxConstraints? constraints;
/// Overrides the active indicator and the background track.
///
/// If [CircularProgressIndicator.year2023] is false or [ThemeData.useMaterial3]
/// is false, then no track gap will be drawn.
///
/// If [LinearProgressIndicator.year2023] is false or [ThemeData.useMaterial3]
/// is false, then no track gap will be drawn.
@ -102,6 +121,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
BorderRadiusGeometry? borderRadius,
Color? stopIndicatorColor,
double? stopIndicatorRadius,
double? strokeWidth,
double? strokeAlign,
StrokeCap? strokeCap,
BoxConstraints? constraints,
double? trackGap,
}) {
return ProgressIndicatorThemeData(
@ -113,6 +136,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
borderRadius : borderRadius ?? this.borderRadius,
stopIndicatorColor : stopIndicatorColor ?? this.stopIndicatorColor,
stopIndicatorRadius : stopIndicatorRadius ?? this.stopIndicatorRadius,
strokeWidth : strokeWidth ?? this.strokeWidth,
strokeAlign : strokeAlign ?? this.strokeAlign,
strokeCap : strokeCap ?? this.strokeCap,
constraints: constraints ?? this.constraints,
trackGap : trackGap ?? this.trackGap,
);
}
@ -133,6 +160,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
borderRadius : BorderRadiusGeometry.lerp(a?.borderRadius, b?.borderRadius, t),
stopIndicatorColor : Color.lerp(a?.stopIndicatorColor, b?.stopIndicatorColor, t),
stopIndicatorRadius : lerpDouble(a?.stopIndicatorRadius, b?.stopIndicatorRadius, t),
strokeWidth : lerpDouble(a?.strokeWidth, b?.strokeWidth, t),
strokeAlign : lerpDouble(a?.strokeAlign, b?.strokeAlign, t),
strokeCap : t < 0.5 ? a?.strokeCap : b?.strokeCap,
constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
trackGap : lerpDouble(a?.trackGap, b?.trackGap, t),
);
}
@ -147,6 +178,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
borderRadius,
stopIndicatorColor,
stopIndicatorRadius,
strokeAlign,
strokeWidth,
strokeCap,
constraints,
trackGap,
);
@ -167,6 +202,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
&& other.borderRadius == borderRadius
&& other.stopIndicatorColor == stopIndicatorColor
&& other.stopIndicatorRadius == stopIndicatorRadius
&& other.strokeAlign == strokeAlign
&& other.strokeWidth == strokeWidth
&& other.strokeCap == strokeCap
&& other.constraints == constraints
&& other.trackGap == trackGap;
}
@ -181,6 +220,10 @@ class ProgressIndicatorThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<BorderRadiusGeometry>('borderRadius', borderRadius, defaultValue: null));
properties.add(ColorProperty('stopIndicatorColor', stopIndicatorColor, defaultValue: null));
properties.add(DoubleProperty('stopIndicatorRadius', stopIndicatorRadius, defaultValue: null));
properties.add(DoubleProperty('strokeWidth', strokeWidth, defaultValue: null));
properties.add(DoubleProperty('strokeAlign', strokeAlign, defaultValue: null));
properties.add(DiagnosticsProperty<StrokeCap>('strokeCap', strokeCap, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
properties.add(DoubleProperty('trackGap', trackGap, defaultValue: null));
}
}

View File

@ -1217,11 +1217,10 @@ void main() {
expect((wrappedTheme as ProgressIndicatorTheme).data, themeData);
});
testWidgets('default size of CircularProgressIndicator is 36x36 - M3', (WidgetTester tester) async {
testWidgets('Material3 - Default size of CircularProgressIndicator', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: theme.copyWith(useMaterial3: true),
home: const Scaffold(
const MaterialApp(
home: Scaffold(
body: Material(
child: CircularProgressIndicator(),
),
@ -1232,6 +1231,20 @@ void main() {
expect(tester.getSize(find.byType(CircularProgressIndicator)), const Size(36, 36));
});
testWidgets('Material3 - Default size of CircularProgressIndicator when year2023 is false', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Material(
child: CircularProgressIndicator(year2023: false),
),
),
),
);
expect(tester.getSize(find.byType(CircularProgressIndicator)), const Size(48, 48));
});
testWidgets('RefreshProgressIndicator using fields correctly', (WidgetTester tester) async {
Future<void> pumpIndicator(RefreshProgressIndicator indicator) {
return tester.pumpWidget(Theme(data: theme, child: indicator));
@ -1551,6 +1564,151 @@ void main() {
),
);
});
testWidgets('Default determinate CircularProgressIndicator when year2023 is false', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Center(
child: CircularProgressIndicator(
year2023: false,
value: 0.5,
),
),
));
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(const Size(48, 48)));
expect(
find.byType(CircularProgressIndicator),
paints
// Track.
..arc(
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
color: theme.colorScheme.secondaryContainer,
strokeWidth: 4.0,
strokeCap: StrokeCap.round,
style: PaintingStyle.stroke,
)
// Active indicator.
..arc(
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
color: theme.colorScheme.primary,
strokeWidth: 4.0,
strokeCap: StrokeCap.round,
style: PaintingStyle.stroke,
),
);
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_determinate_year2023_false.png'),
);
});
testWidgets('Default indeterminate CircularProgressIndicator when year2023 is false', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Center(child: CircularProgressIndicator(year2023: false)),
));
// Advance the animation.
await tester.pump(const Duration(milliseconds: 200));
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(const Size(48, 48)));
expect(
find.byType(CircularProgressIndicator),
paints
// Active indicator.
..arc(
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
color: theme.colorScheme.primary,
strokeWidth: 4.0,
strokeCap: StrokeCap.round,
style: PaintingStyle.stroke,
),
);
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_indeterminate_year2023_false.png'),
);
});
testWidgets('CircularProgressIndicator track gap can be adjusted when year2023 is false', (WidgetTester tester) async {
Widget buildIndicator({ double? trackGap }) {
return MaterialApp(
home: Center(
child: CircularProgressIndicator(
year2023: false,
trackGap: trackGap,
value: 0.5,
),
),
);
}
await tester.pumpWidget(buildIndicator());
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_default_track_gap_year2023_false.png'),
);
await tester.pumpWidget(buildIndicator(trackGap: 12.0));
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_custom_track_gap_year2023_false.png'),
);
await tester.pumpWidget(buildIndicator(trackGap: 0.0));
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_no_track_gap_year2023_false.png'),
);
});
testWidgets('Can override CircularProgressIndicator stroke cap when year2023 is false', (WidgetTester tester) async {
const StrokeCap strokeCap = StrokeCap.square;
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: CircularProgressIndicator(
year2023: false,
strokeCap: strokeCap,
value: 0.5,
),
),
)
);
expect(
find.byType(CircularProgressIndicator),
paints
// Track.
..arc(strokeCap: strokeCap)
// Active indicator.
..arc(strokeCap: strokeCap)
);
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_custom_stroke_cap_year2023_false.png'),
);
});
testWidgets('CircularProgressIndicator.constraints can override default size', (WidgetTester tester) async {
const Size size = Size(64, 64);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: CircularProgressIndicator(
constraints: BoxConstraints(
minWidth: size.width,
minHeight: size.height
),
value: 0.5,
),
),
)
);
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(size));
});
}
class _RefreshProgressIndicatorGolden extends StatefulWidget {

View File

@ -2,7 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// reduced-test-set:
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
@ -17,6 +24,46 @@ void main() {
expect(identical(ProgressIndicatorThemeData.lerp(data, data, 0.5), data), true);
});
testWidgets('ProgressIndicatorThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ProgressIndicatorThemeData(
color: Color(0XFF0000F1),
linearTrackColor: Color(0XFF0000F2),
linearMinHeight: 25.0,
circularTrackColor: Color(0XFF0000F3),
refreshBackgroundColor: Color(0XFF0000F4),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
stopIndicatorColor: Color(0XFF0000F5),
stopIndicatorRadius: 10.0,
strokeWidth: 8.0,
strokeAlign: BorderSide.strokeAlignOutside,
strokeCap: StrokeCap.butt,
constraints: BoxConstraints.tightFor(width: 80.0, height: 80.0),
trackGap: 16.0,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, equalsIgnoringHashCodes(<String>[
'color: Color(alpha: 1.0000, red: 0.0000, green: 0.0000, blue: 0.9451, colorSpace: ColorSpace.sRGB)',
'linearTrackColor: Color(alpha: 1.0000, red: 0.0000, green: 0.0000, blue: 0.9490, colorSpace: ColorSpace.sRGB)',
'linearMinHeight: 25.0',
'circularTrackColor: Color(alpha: 1.0000, red: 0.0000, green: 0.0000, blue: 0.9529, colorSpace: ColorSpace.sRGB)',
'refreshBackgroundColor: Color(alpha: 1.0000, red: 0.0000, green: 0.0000, blue: 0.9569, colorSpace: ColorSpace.sRGB)',
'borderRadius: BorderRadius.circular(8.0)',
'stopIndicatorColor: Color(alpha: 1.0000, red: 0.0000, green: 0.0000, blue: 0.9608, colorSpace: ColorSpace.sRGB)',
'stopIndicatorRadius: 10.0',
'strokeWidth: 8.0',
'strokeAlign: 1.0',
'strokeCap: StrokeCap.butt',
'constraints: BoxConstraints(w=80.0, h=80.0)',
'trackGap: 16.0'
]));
});
testWidgets('Can theme LinearProgressIndicator using ProgressIndicatorTheme', (WidgetTester tester) async {
const Color color = Color(0XFF00FF00);
const Color linearTrackColor = Color(0XFFFF0000);
@ -62,7 +109,7 @@ void main() {
);
});
testWidgets('Can theme LinearProgressIndicator with year2023 to false', (WidgetTester tester) async {
testWidgets('Can theme LinearProgressIndicator when year2023 to false', (WidgetTester tester) async {
const Color color = Color(0XFF00FF00);
const Color linearTrackColor = Color(0XFFFF0000);
const double linearMinHeight = 25.0;
@ -182,4 +229,119 @@ void main() {
),
);
});
testWidgets('Can theme CircularProgressIndicator using ProgressIndicatorTheme', (WidgetTester tester) async {
const Color color = Color(0XFFFF0000);
const Color circularTrackColor = Color(0XFF0000FF);
const double strokeWidth = 8.0;
const double strokeAlign = BorderSide.strokeAlignOutside;
const StrokeCap strokeCap = StrokeCap.butt;
const BoxConstraints constraints = BoxConstraints.tightFor(width: 80.0, height: 80.0);
final ThemeData theme = ThemeData(
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: color,
circularTrackColor: circularTrackColor,
strokeWidth: strokeWidth,
strokeAlign: strokeAlign,
strokeCap: strokeCap,
constraints: constraints,
),
);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Scaffold(
body: Center(
child: CircularProgressIndicator(
value: 0.5,
),
),
),
),
);
expect(
tester.getSize(find.byType(CircularProgressIndicator)),
equals(Size(constraints.maxWidth, constraints.maxHeight)),
);
expect(
find.byType(CircularProgressIndicator),
paints
// Track.
..arc(
color: circularTrackColor,
strokeWidth: strokeWidth,
strokeCap: strokeCap,
)
// Active indicator.
..arc(
color: color,
strokeWidth: strokeWidth,
strokeCap: strokeCap,
),
);
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_theme.png'),
);
});
testWidgets('Can theme CircularProgressIndicator when year2023 to false', (WidgetTester tester) async {
const Color color = Color(0XFFFF0000);
const Color circularTrackColor = Color(0XFF0000FF);
const double strokeWidth = 8.0;
const double strokeAlign = BorderSide.strokeAlignOutside;
const StrokeCap strokeCap = StrokeCap.butt;
const BoxConstraints constraints = BoxConstraints.tightFor(width: 80.0, height: 80.0);
const double trackGap = 12.0;
final ThemeData theme = ThemeData(
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: color,
circularTrackColor: circularTrackColor,
strokeWidth: strokeWidth,
strokeAlign: strokeAlign,
strokeCap: strokeCap,
constraints: constraints,
trackGap: trackGap,
),
);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Scaffold(
body: Center(
child: CircularProgressIndicator(
year2023: false,
value: 0.5,
),
),
),
),
);
expect(
tester.getSize(find.byType(CircularProgressIndicator)),
equals(Size(constraints.maxWidth, constraints.maxHeight)),
);
expect(
find.byType(CircularProgressIndicator),
paints
// Track.
..arc(
color: circularTrackColor,
strokeWidth: strokeWidth,
strokeCap: strokeCap,
)
// Active indicator.
..arc(
color: color,
strokeWidth: strokeWidth,
strokeCap: strokeCap,
),
);
await expectLater(
find.byType(CircularProgressIndicator),
matchesGoldenFile('circular_progress_indicator_theme_year2023_false.png'),
);
});
}