Unverified Commit 56e4f8e0 authored by Bernardo Ferrari's avatar Bernardo Ferrari Committed by GitHub

Add `StrokeCap` to `CircularProgressIndicator` (#122664)

Add `StrokeCap` to `CircularProgressIndicator`
parent 0854583a
...@@ -396,6 +396,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ...@@ -396,6 +396,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
required this.offsetValue, required this.offsetValue,
required this.rotationValue, required this.rotationValue,
required this.strokeWidth, required this.strokeWidth,
this.strokeCap,
}) : arcStart = value != null }) : arcStart = value != null
? _startAngle ? _startAngle
: _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi, : _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi,
...@@ -413,6 +414,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ...@@ -413,6 +414,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
final double strokeWidth; final double strokeWidth;
final double arcStart; final double arcStart;
final double arcSweep; final double arcSweep;
final StrokeCap? strokeCap;
static const double _twoPi = math.pi * 2.0; static const double _twoPi = math.pi * 2.0;
static const double _epsilon = .001; static const double _epsilon = .001;
...@@ -426,6 +428,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ...@@ -426,6 +428,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
..color = valueColor ..color = valueColor
..strokeWidth = strokeWidth ..strokeWidth = strokeWidth
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
if (backgroundColor != null) { if (backgroundColor != null) {
final Paint backgroundPaint = Paint() final Paint backgroundPaint = Paint()
..color = backgroundColor! ..color = backgroundColor!
...@@ -434,8 +437,12 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ...@@ -434,8 +437,12 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint); canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint);
} }
if (value == null) { // Indeterminate if (value == null && strokeCap == null) {
// Indeterminate
paint.strokeCap = StrokeCap.square; paint.strokeCap = StrokeCap.square;
} else {
// Butt when determinate (value != null) && strokeCap == null;
paint.strokeCap = strokeCap ?? StrokeCap.butt;
} }
canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint); canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint);
...@@ -450,7 +457,8 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ...@@ -450,7 +457,8 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
|| oldPainter.tailValue != tailValue || oldPainter.tailValue != tailValue
|| oldPainter.offsetValue != offsetValue || oldPainter.offsetValue != offsetValue
|| oldPainter.rotationValue != rotationValue || oldPainter.rotationValue != rotationValue
|| oldPainter.strokeWidth != strokeWidth; || oldPainter.strokeWidth != strokeWidth
|| oldPainter.strokeCap != strokeCap;
} }
} }
...@@ -507,6 +515,7 @@ class CircularProgressIndicator extends ProgressIndicator { ...@@ -507,6 +515,7 @@ class CircularProgressIndicator extends ProgressIndicator {
this.strokeWidth = 4.0, this.strokeWidth = 4.0,
super.semanticsLabel, super.semanticsLabel,
super.semanticsValue, super.semanticsValue,
this.strokeCap,
}) : _indicatorType = _ActivityIndicatorType.material; }) : _indicatorType = _ActivityIndicatorType.material;
/// Creates an adaptive progress indicator that is a /// Creates an adaptive progress indicator that is a
...@@ -525,6 +534,7 @@ class CircularProgressIndicator extends ProgressIndicator { ...@@ -525,6 +534,7 @@ class CircularProgressIndicator extends ProgressIndicator {
this.strokeWidth = 4.0, this.strokeWidth = 4.0,
super.semanticsLabel, super.semanticsLabel,
super.semanticsValue, super.semanticsValue,
this.strokeCap,
}) : _indicatorType = _ActivityIndicatorType.adaptive; }) : _indicatorType = _ActivityIndicatorType.adaptive;
final _ActivityIndicatorType _indicatorType; final _ActivityIndicatorType _indicatorType;
...@@ -542,6 +552,27 @@ class CircularProgressIndicator extends ProgressIndicator { ...@@ -542,6 +552,27 @@ class CircularProgressIndicator extends ProgressIndicator {
/// The width of the line used to draw the circle. /// The width of the line used to draw the circle.
final double strokeWidth; final double strokeWidth;
/// The progress indicator's line ending.
///
/// This determines the shape of the stroke ends of the progress indicator.
/// By default, [strokeCap] is null.
/// When [value] is null (indeterminate), the stroke ends are set to
/// [StrokeCap.square]. When [value] is not null, the stroke
/// ends are set to [StrokeCap.butt].
///
/// Setting [strokeCap] to [StrokeCap.round] will result in a rounded end.
/// Setting [strokeCap] to [StrokeCap.butt] with [value] == null will result
/// in a slightly different indeterminate animation; the indicator completely
/// disappears and reappears on its minimum value.
/// Setting [strokeCap] to [StrokeCap.square] with [value] != null will
/// result in a different display of [value]. The indicator will start
/// drawing from slightly less than the start, and end slightly after
/// the end. This will produce an alternative result, as the
/// default behavior, for example, that a [value] of 0.5 starts at 90 degrees
/// and ends at 270 degrees. With [StrokeCap.square], it could start 85
/// degrees and end at 275 degrees.
final StrokeCap? strokeCap;
@override @override
State<CircularProgressIndicator> createState() => _CircularProgressIndicatorState(); State<CircularProgressIndicator> createState() => _CircularProgressIndicatorState();
} }
...@@ -621,6 +652,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w ...@@ -621,6 +652,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
offsetValue: offsetValue, offsetValue: offsetValue,
rotationValue: rotationValue, rotationValue: rotationValue,
strokeWidth: widget.strokeWidth, strokeWidth: widget.strokeWidth,
strokeCap: widget.strokeCap,
), ),
), ),
), ),
...@@ -679,6 +711,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter ...@@ -679,6 +711,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
required super.rotationValue, required super.rotationValue,
required super.strokeWidth, required super.strokeWidth,
required this.arrowheadScale, required this.arrowheadScale,
required super.strokeCap,
}); });
final double arrowheadScale; final double arrowheadScale;
...@@ -703,6 +736,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter ...@@ -703,6 +736,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
..lineTo(radius + ux * outerRadius, radius + uy * outerRadius) ..lineTo(radius + ux * outerRadius, radius + uy * outerRadius)
..lineTo(arrowheadPointX, arrowheadPointY) ..lineTo(arrowheadPointX, arrowheadPointY)
..close(); ..close();
final Paint paint = Paint() final Paint paint = Paint()
..color = valueColor ..color = valueColor
..strokeWidth = strokeWidth ..strokeWidth = strokeWidth
...@@ -748,6 +782,7 @@ class RefreshProgressIndicator extends CircularProgressIndicator { ...@@ -748,6 +782,7 @@ class RefreshProgressIndicator extends CircularProgressIndicator {
super.strokeWidth = defaultStrokeWidth, // Different default than CircularProgressIndicator. super.strokeWidth = defaultStrokeWidth, // Different default than CircularProgressIndicator.
super.semanticsLabel, super.semanticsLabel,
super.semanticsValue, super.semanticsValue,
super.strokeCap,
}); });
/// Default stroke width. /// Default stroke width.
...@@ -877,6 +912,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState { ...@@ -877,6 +912,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
rotationValue: rotationValue, rotationValue: rotationValue,
strokeWidth: widget.strokeWidth, strokeWidth: widget.strokeWidth,
arrowheadScale: arrowheadScale, arrowheadScale: arrowheadScale,
strokeCap: widget.strokeCap,
), ),
), ),
), ),
......
...@@ -433,7 +433,7 @@ void main() { ...@@ -433,7 +433,7 @@ void main() {
expect(layers1, isNot(equals(layers2))); expect(layers1, isNot(equals(layers2)));
}); });
testWidgets('CircularProgressIndicator stoke width', (WidgetTester tester) async { testWidgets('CircularProgressIndicator stroke width', (WidgetTester tester) async {
await tester.pumpWidget(Theme(data: theme, child: const CircularProgressIndicator())); await tester.pumpWidget(Theme(data: theme, child: const CircularProgressIndicator()));
expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0)); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0));
...@@ -443,6 +443,28 @@ void main() { ...@@ -443,6 +443,28 @@ void main() {
expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0)); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0));
}); });
testWidgets('CircularProgressIndicator with strokeCap', (WidgetTester tester) async {
await tester.pumpWidget(const CircularProgressIndicator());
expect(find.byType(CircularProgressIndicator),
paints..arc(strokeCap: StrokeCap.square),
reason: 'Default indeterminate strokeCap is StrokeCap.square.');
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: CircularProgressIndicator(value: 0.5)));
expect(find.byType(CircularProgressIndicator),
paints..arc(strokeCap: StrokeCap.butt),
reason: 'Default determinate strokeCap is StrokeCap.butt.');
await tester.pumpWidget(const CircularProgressIndicator(strokeCap: StrokeCap.butt));
expect(find.byType(CircularProgressIndicator),
paints..arc(strokeCap: StrokeCap.butt),
reason: 'strokeCap can be set to StrokeCap.butt, and will not be overidden.');
await tester.pumpWidget(const CircularProgressIndicator(strokeCap: StrokeCap.round));
expect(find.byType(CircularProgressIndicator), paints..arc(strokeCap: StrokeCap.round));
});
testWidgets('CircularProgressIndicator paint colors', (WidgetTester tester) async { testWidgets('CircularProgressIndicator paint colors', (WidgetTester tester) async {
const Color green = Color(0xFF00FF00); const Color green = Color(0xFF00FF00);
const Color blue = Color(0xFF0000FF); const Color blue = Color(0xFF0000FF);
...@@ -569,6 +591,29 @@ void main() { ...@@ -569,6 +591,29 @@ void main() {
expect(themeBackgroundMaterial.color, blue); expect(themeBackgroundMaterial.color, blue);
}); });
testWidgets('RefreshProgressIndicator with a round indicator', (WidgetTester tester) async {
await tester.pumpWidget(const RefreshProgressIndicator());
expect(find.byType(RefreshProgressIndicator),
paints..arc(strokeCap: StrokeCap.square),
reason: 'Default indeterminate strokeCap is StrokeCap.square');
await tester.pumpWidget(
Theme(
data: theme,
child: const Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: 200.0,
child: RefreshProgressIndicator(strokeCap: StrokeCap.round),
),
),
),
),
);
expect(find.byType(RefreshProgressIndicator), paints..arc(strokeCap: StrokeCap.round));
});
testWidgets('Indeterminate RefreshProgressIndicator keeps spinning until end of time (approximate)', (WidgetTester tester) async { testWidgets('Indeterminate RefreshProgressIndicator keeps spinning until end of time (approximate)', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/13782 // Regression test for https://github.com/flutter/flutter/issues/13782
......
...@@ -318,7 +318,7 @@ abstract class PaintPattern { ...@@ -318,7 +318,7 @@ abstract class PaintPattern {
/// painting has completed, not at the time of the call. If the same [Paint] /// painting has completed, not at the time of the call. If the same [Paint]
/// object is reused multiple times, then this may not match the actual /// object is reused multiple times, then this may not match the actual
/// arguments as they were seen by the method. /// arguments as they were seen by the method.
void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }); void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, StrokeCap? strokeCap });
/// Indicates that a paragraph is expected next. /// Indicates that a paragraph is expected next.
/// ///
...@@ -769,8 +769,8 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp ...@@ -769,8 +769,8 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
} }
@override @override
void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) { void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, StrokeCap? strokeCap }) {
_predicates.add(_ArcPaintPredicate(color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style)); _predicates.add(_ArcPaintPredicate(color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style, strokeCap: strokeCap));
} }
@override @override
...@@ -892,6 +892,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { ...@@ -892,6 +892,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
this.hasMaskFilter, this.hasMaskFilter,
this.style, this.style,
this.shader, this.shader,
this.strokeCap,
}); });
final Symbol symbol; final Symbol symbol;
...@@ -903,6 +904,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { ...@@ -903,6 +904,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
final bool? hasMaskFilter; final bool? hasMaskFilter;
final PaintingStyle? style; final PaintingStyle? style;
final Matcher? shader; final Matcher? shader;
final StrokeCap? strokeCap;
String get methodName => _symbolName(symbol); String get methodName => _symbolName(symbol);
...@@ -940,6 +942,9 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { ...@@ -940,6 +942,9 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
if (shader != null && !shader!.matches(paintArgument.shader, <dynamic, dynamic>{})) { if (shader != null && !shader!.matches(paintArgument.shader, <dynamic, dynamic>{})) {
throw 'It called $methodName with a paint whose shader, ${paintArgument.shader}, was not exactly the expected shader ($shader).'; throw 'It called $methodName with a paint whose shader, ${paintArgument.shader}, was not exactly the expected shader ($shader).';
} }
if (strokeCap != null && paintArgument.strokeCap != strokeCap) {
throw 'It called $methodName with a paint whose strokeCap, ${paintArgument.strokeCap}, was not exactly the expected strokeCap ($strokeCap).';
}
} }
@override @override
...@@ -1276,8 +1281,8 @@ class _LinePaintPredicate extends _DrawCommandPaintPredicate { ...@@ -1276,8 +1281,8 @@ class _LinePaintPredicate extends _DrawCommandPaintPredicate {
} }
class _ArcPaintPredicate extends _DrawCommandPaintPredicate { class _ArcPaintPredicate extends _DrawCommandPaintPredicate {
_ArcPaintPredicate({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) : super( _ArcPaintPredicate({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, StrokeCap? strokeCap }) : super(
#drawArc, 'an arc', 5, 4, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style, #drawArc, 'an arc', 5, 4, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style, strokeCap: strokeCap,
); );
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment