Introduce CircularProgressIndicator.padding
for the updated M3 specs (#159271)
Fix [Add `CircularProgressIndicator` padding to match M3 specs](https://github.com/flutter/flutter/issues/159267) ### 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 StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 40, height: 40, color: Colors.red, alignment: Alignment.center, child: const Text( '40x40px', style: TextStyle(fontSize: 8, color: Colors.white), ), ), const ColoredBox( color: Colors.amber, child: CircularProgressIndicator( year2023: false, value: 0.4, ), ), Container( width: 48, height: 48, color: Colors.red, alignment: Alignment.center, child: const Text( '48x48px', style: TextStyle(fontSize: 10, color: Colors.white), ), ) ], ), ), ), ); } } ``` </details> ### Preview <img width="454" alt="Screenshot 2024-11-21 at 17 13 25" src="https://github.com/user-attachments/assets/6f7520f1-a213-4814-8116-6dd996639eec"> ### Specs  ## 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:
parent
a77aad7517
commit
e21db87817
@ -32,12 +32,15 @@ class _Circular${blockName}DefaultsM3 extends ProgressIndicatorThemeData {
|
||||
|
||||
@override
|
||||
BoxConstraints get constraints => const BoxConstraints(
|
||||
minWidth: 48.0,
|
||||
minHeight: 48.0,
|
||||
minWidth: 40.0,
|
||||
minHeight: 40.0,
|
||||
);
|
||||
|
||||
@override
|
||||
double? get trackGap => ${getToken('md.comp.progress-indicator.active-indicator-track-space')};
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get circularTrackPadding => const EdgeInsets.all(4.0);
|
||||
}
|
||||
|
||||
class _Linear${blockName}DefaultsM3 extends ProgressIndicatorThemeData {
|
||||
|
@ -745,6 +745,7 @@ class CircularProgressIndicator extends ProgressIndicator {
|
||||
'This feature was deprecated after v3.27.0-0.1.pre.'
|
||||
)
|
||||
this.year2023 = true,
|
||||
this.padding,
|
||||
}) : _indicatorType = _ActivityIndicatorType.material;
|
||||
|
||||
/// Creates an adaptive progress indicator that is a
|
||||
@ -774,6 +775,7 @@ class CircularProgressIndicator extends ProgressIndicator {
|
||||
'This feature was deprecated after v3.27.0-0.2.pre.'
|
||||
)
|
||||
this.year2023 = true,
|
||||
this.padding,
|
||||
}) : _indicatorType = _ActivityIndicatorType.adaptive;
|
||||
|
||||
final _ActivityIndicatorType _indicatorType;
|
||||
@ -854,6 +856,13 @@ class CircularProgressIndicator extends ProgressIndicator {
|
||||
)
|
||||
final bool year2023;
|
||||
|
||||
/// The padding around the indicator track.
|
||||
///
|
||||
/// If null, then the [ProgressIndicatorThemeData.circularTrackPadding] will be
|
||||
/// used. If that is null and [year2023] is false, then defaults to `EdgeInsets.all(4.0)`
|
||||
/// padding. Otherwise, defaults to zero padding.
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// The indicator stroke is drawn fully inside of the indicator path.
|
||||
///
|
||||
/// This is a constant for use with [strokeAlign].
|
||||
@ -967,9 +976,11 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
|
||||
: widget.trackGap ??
|
||||
indicatorTheme.trackGap ??
|
||||
defaults.trackGap;
|
||||
return widget._buildSemanticsWrapper(
|
||||
context: context,
|
||||
child: ConstrainedBox(
|
||||
final EdgeInsetsGeometry? effectivePadding = widget.padding
|
||||
?? indicatorTheme.circularTrackPadding
|
||||
?? defaults.circularTrackPadding;
|
||||
|
||||
Widget result = ConstrainedBox(
|
||||
constraints: constraints,
|
||||
child: CustomPaint(
|
||||
painter: _CircularProgressIndicatorPainter(
|
||||
@ -987,7 +998,18 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
|
||||
year2023: widget.year2023,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (effectivePadding != null) {
|
||||
result = Padding(
|
||||
padding: effectivePadding,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
return widget._buildSemanticsWrapper(
|
||||
context: context,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1396,12 +1418,15 @@ class _CircularProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
|
||||
|
||||
@override
|
||||
BoxConstraints get constraints => const BoxConstraints(
|
||||
minWidth: 48.0,
|
||||
minHeight: 48.0,
|
||||
minWidth: 40.0,
|
||||
minHeight: 40.0,
|
||||
);
|
||||
|
||||
@override
|
||||
double? get trackGap => 4.0;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get circularTrackPadding => const EdgeInsets.all(4.0);
|
||||
}
|
||||
|
||||
class _LinearProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
|
||||
|
@ -47,6 +47,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
this.strokeCap,
|
||||
this.constraints,
|
||||
this.trackGap,
|
||||
this.circularTrackPadding,
|
||||
});
|
||||
|
||||
/// The color of the [ProgressIndicator]'s indicator.
|
||||
@ -110,6 +111,9 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
/// is false, then no track gap will be drawn.
|
||||
final double? trackGap;
|
||||
|
||||
/// Overrides the padding of the [CircularProgressIndicator].
|
||||
final EdgeInsetsGeometry? circularTrackPadding;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
ProgressIndicatorThemeData copyWith({
|
||||
@ -126,6 +130,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
StrokeCap? strokeCap,
|
||||
BoxConstraints? constraints,
|
||||
double? trackGap,
|
||||
EdgeInsetsGeometry? circularTrackPadding,
|
||||
}) {
|
||||
return ProgressIndicatorThemeData(
|
||||
color: color ?? this.color,
|
||||
@ -141,6 +146,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
strokeCap : strokeCap ?? this.strokeCap,
|
||||
constraints: constraints ?? this.constraints,
|
||||
trackGap : trackGap ?? this.trackGap,
|
||||
circularTrackPadding: circularTrackPadding ?? this.circularTrackPadding,
|
||||
);
|
||||
}
|
||||
|
||||
@ -165,6 +171,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
strokeCap : t < 0.5 ? a?.strokeCap : b?.strokeCap,
|
||||
constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
|
||||
trackGap : lerpDouble(a?.trackGap, b?.trackGap, t),
|
||||
circularTrackPadding: EdgeInsetsGeometry.lerp(a?.circularTrackPadding, b?.circularTrackPadding, t),
|
||||
);
|
||||
}
|
||||
|
||||
@ -183,6 +190,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
strokeCap,
|
||||
constraints,
|
||||
trackGap,
|
||||
circularTrackPadding,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -206,7 +214,8 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
&& other.strokeWidth == strokeWidth
|
||||
&& other.strokeCap == strokeCap
|
||||
&& other.constraints == constraints
|
||||
&& other.trackGap == trackGap;
|
||||
&& other.trackGap == trackGap
|
||||
&& other.circularTrackPadding == circularTrackPadding;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -225,6 +234,7 @@ class ProgressIndicatorThemeData with Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<StrokeCap>('strokeCap', strokeCap, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
|
||||
properties.add(DoubleProperty('trackGap', trackGap, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('circularTrackPadding', circularTrackPadding, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1566,6 +1566,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Default determinate CircularProgressIndicator when year2023 is false', (WidgetTester tester) async {
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.all(4.0);
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: const Center(
|
||||
@ -1576,13 +1577,23 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(const Size(48, 48)));
|
||||
final Size indicatorBoxSize = tester.getSize(find.descendant(
|
||||
of: find.byType(CircularProgressIndicator),
|
||||
matching: find.byType(ConstrainedBox),
|
||||
));
|
||||
expect(
|
||||
tester.getSize(find.byType(CircularProgressIndicator)),
|
||||
equals(Size(
|
||||
indicatorBoxSize.width + padding.horizontal,
|
||||
indicatorBoxSize.height + padding.vertical,
|
||||
)),
|
||||
);
|
||||
expect(
|
||||
find.byType(CircularProgressIndicator),
|
||||
paints
|
||||
// Track.
|
||||
..arc(
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0),
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
strokeWidth: 4.0,
|
||||
strokeCap: StrokeCap.round,
|
||||
@ -1590,7 +1601,7 @@ void main() {
|
||||
)
|
||||
// Active indicator.
|
||||
..arc(
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0),
|
||||
color: theme.colorScheme.primary,
|
||||
strokeWidth: 4.0,
|
||||
strokeCap: StrokeCap.round,
|
||||
@ -1604,6 +1615,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Default indeterminate CircularProgressIndicator when year2023 is false', (WidgetTester tester) async {
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.all(4.0);
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: const Center(child: CircularProgressIndicator(year2023: false)),
|
||||
@ -1612,13 +1624,23 @@ void main() {
|
||||
// Advance the animation.
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(const Size(48, 48)));
|
||||
final Size indicatorBoxSize = tester.getSize(find.descendant(
|
||||
of: find.byType(CircularProgressIndicator),
|
||||
matching: find.byType(ConstrainedBox),
|
||||
));
|
||||
expect(
|
||||
tester.getSize(find.byType(CircularProgressIndicator)),
|
||||
equals(Size(
|
||||
indicatorBoxSize.width + padding.horizontal,
|
||||
indicatorBoxSize.height + padding.vertical,
|
||||
)),
|
||||
);
|
||||
expect(
|
||||
find.byType(CircularProgressIndicator),
|
||||
paints
|
||||
// Active indicator.
|
||||
..arc(
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 46.0, 46.0),
|
||||
rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0),
|
||||
color: theme.colorScheme.primary,
|
||||
strokeWidth: 4.0,
|
||||
strokeCap: StrokeCap.round,
|
||||
@ -1709,6 +1731,33 @@ void main() {
|
||||
|
||||
expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(size));
|
||||
});
|
||||
|
||||
testWidgets('CircularProgressIndicator padding can be customized', (WidgetTester tester) async {
|
||||
const EdgeInsetsGeometry padding = EdgeInsets.all(12.0);
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Center(
|
||||
child: CircularProgressIndicator(
|
||||
padding: padding,
|
||||
year2023: false,
|
||||
value: 0.5,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final Size indicatorBoxSize = tester.getSize(find.descendant(
|
||||
of: find.byType(CircularProgressIndicator),
|
||||
matching: find.byType(ConstrainedBox),
|
||||
));
|
||||
expect(
|
||||
tester.getSize(find.byType(CircularProgressIndicator)),
|
||||
equals(Size(
|
||||
indicatorBoxSize.width + padding.horizontal,
|
||||
indicatorBoxSize.height + padding.vertical,
|
||||
)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class _RefreshProgressIndicatorGolden extends StatefulWidget {
|
||||
|
@ -40,6 +40,7 @@ void main() {
|
||||
strokeCap: StrokeCap.butt,
|
||||
constraints: BoxConstraints.tightFor(width: 80.0, height: 80.0),
|
||||
trackGap: 16.0,
|
||||
circularTrackPadding: EdgeInsets.all(12.0),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
@ -60,7 +61,8 @@ void main() {
|
||||
'strokeAlign: 1.0',
|
||||
'strokeCap: StrokeCap.butt',
|
||||
'constraints: BoxConstraints(w=80.0, h=80.0)',
|
||||
'trackGap: 16.0'
|
||||
'trackGap: 16.0',
|
||||
'circularTrackPadding: EdgeInsets.all(12.0)'
|
||||
]));
|
||||
});
|
||||
|
||||
@ -237,6 +239,7 @@ void main() {
|
||||
const double strokeAlign = BorderSide.strokeAlignOutside;
|
||||
const StrokeCap strokeCap = StrokeCap.butt;
|
||||
const BoxConstraints constraints = BoxConstraints.tightFor(width: 80.0, height: 80.0);
|
||||
const EdgeInsets padding = EdgeInsets.all(14.0);
|
||||
final ThemeData theme = ThemeData(
|
||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||
color: color,
|
||||
@ -245,6 +248,7 @@ void main() {
|
||||
strokeAlign: strokeAlign,
|
||||
strokeCap: strokeCap,
|
||||
constraints: constraints,
|
||||
circularTrackPadding: padding,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
@ -262,7 +266,10 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.getSize(find.byType(CircularProgressIndicator)),
|
||||
equals(Size(constraints.maxWidth, constraints.maxHeight)),
|
||||
equals(Size(
|
||||
constraints.maxWidth + padding.horizontal,
|
||||
constraints.maxHeight + padding.vertical,
|
||||
)),
|
||||
);
|
||||
expect(
|
||||
find.byType(CircularProgressIndicator),
|
||||
@ -294,6 +301,7 @@ void main() {
|
||||
const StrokeCap strokeCap = StrokeCap.butt;
|
||||
const BoxConstraints constraints = BoxConstraints.tightFor(width: 80.0, height: 80.0);
|
||||
const double trackGap = 12.0;
|
||||
const EdgeInsets padding = EdgeInsets.all(18.0);
|
||||
final ThemeData theme = ThemeData(
|
||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||
color: color,
|
||||
@ -303,6 +311,7 @@ void main() {
|
||||
strokeCap: strokeCap,
|
||||
constraints: constraints,
|
||||
trackGap: trackGap,
|
||||
circularTrackPadding: padding,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
@ -319,9 +328,17 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final Size indicatorBoxSize = tester.getSize(find.descendant(
|
||||
of: find.byType(CircularProgressIndicator),
|
||||
matching: find.byType(ConstrainedBox),
|
||||
));
|
||||
expect(indicatorBoxSize, constraints.biggest);
|
||||
expect(
|
||||
tester.getSize(find.byType(CircularProgressIndicator)),
|
||||
equals(Size(constraints.maxWidth, constraints.maxHeight)),
|
||||
equals(Size(
|
||||
indicatorBoxSize.width + padding.horizontal,
|
||||
indicatorBoxSize.height + padding.vertical,
|
||||
)),
|
||||
);
|
||||
expect(
|
||||
find.byType(CircularProgressIndicator),
|
||||
|
Loading…
x
Reference in New Issue
Block a user