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 {
required this.offsetValue,
required this.rotationValue,
required this.strokeWidth,
this.strokeCap,
}) : arcStart = value != null
? _startAngle
: _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi,
......@@ -413,6 +414,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
final double strokeWidth;
final double arcStart;
final double arcSweep;
final StrokeCap? strokeCap;
static const double _twoPi = math.pi * 2.0;
static const double _epsilon = .001;
......@@ -426,6 +428,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
..color = valueColor
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
if (backgroundColor != null) {
final Paint backgroundPaint = Paint()
..color = backgroundColor!
......@@ -434,8 +437,12 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint);
}
if (value == null) { // Indeterminate
if (value == null && strokeCap == null) {
// Indeterminate
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);
......@@ -450,7 +457,8 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
|| oldPainter.tailValue != tailValue
|| oldPainter.offsetValue != offsetValue
|| oldPainter.rotationValue != rotationValue
|| oldPainter.strokeWidth != strokeWidth;
|| oldPainter.strokeWidth != strokeWidth
|| oldPainter.strokeCap != strokeCap;
}
}
......@@ -507,6 +515,7 @@ class CircularProgressIndicator extends ProgressIndicator {
this.strokeWidth = 4.0,
super.semanticsLabel,
super.semanticsValue,
this.strokeCap,
}) : _indicatorType = _ActivityIndicatorType.material;
/// Creates an adaptive progress indicator that is a
......@@ -525,6 +534,7 @@ class CircularProgressIndicator extends ProgressIndicator {
this.strokeWidth = 4.0,
super.semanticsLabel,
super.semanticsValue,
this.strokeCap,
}) : _indicatorType = _ActivityIndicatorType.adaptive;
final _ActivityIndicatorType _indicatorType;
......@@ -542,6 +552,27 @@ class CircularProgressIndicator extends ProgressIndicator {
/// The width of the line used to draw the circle.
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
State<CircularProgressIndicator> createState() => _CircularProgressIndicatorState();
}
......@@ -621,6 +652,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
strokeCap: widget.strokeCap,
),
),
),
......@@ -679,6 +711,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
required super.rotationValue,
required super.strokeWidth,
required this.arrowheadScale,
required super.strokeCap,
});
final double arrowheadScale;
......@@ -703,6 +736,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
..lineTo(radius + ux * outerRadius, radius + uy * outerRadius)
..lineTo(arrowheadPointX, arrowheadPointY)
..close();
final Paint paint = Paint()
..color = valueColor
..strokeWidth = strokeWidth
......@@ -748,6 +782,7 @@ class RefreshProgressIndicator extends CircularProgressIndicator {
super.strokeWidth = defaultStrokeWidth, // Different default than CircularProgressIndicator.
super.semanticsLabel,
super.semanticsValue,
super.strokeCap,
});
/// Default stroke width.
......@@ -877,6 +912,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
arrowheadScale: arrowheadScale,
strokeCap: widget.strokeCap,
),
),
),
......
......@@ -433,7 +433,7 @@ void main() {
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()));
expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0));
......@@ -443,6 +443,28 @@ void main() {
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 {
const Color green = Color(0xFF00FF00);
const Color blue = Color(0xFF0000FF);
......@@ -569,6 +591,29 @@ void main() {
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 {
// Regression test for https://github.com/flutter/flutter/issues/13782
......
......@@ -318,7 +318,7 @@ abstract class PaintPattern {
/// 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
/// 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.
///
......@@ -769,8 +769,8 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
}
@override
void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) {
_predicates.add(_ArcPaintPredicate(color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style));
void arc({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, StrokeCap? strokeCap }) {
_predicates.add(_ArcPaintPredicate(color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style, strokeCap: strokeCap));
}
@override
......@@ -892,6 +892,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
this.hasMaskFilter,
this.style,
this.shader,
this.strokeCap,
});
final Symbol symbol;
......@@ -903,6 +904,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
final bool? hasMaskFilter;
final PaintingStyle? style;
final Matcher? shader;
final StrokeCap? strokeCap;
String get methodName => _symbolName(symbol);
......@@ -940,6 +942,9 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
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).';
}
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
......@@ -1276,8 +1281,8 @@ class _LinePaintPredicate extends _DrawCommandPaintPredicate {
}
class _ArcPaintPredicate extends _DrawCommandPaintPredicate {
_ArcPaintPredicate({ Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) : super(
#drawArc, 'an arc', 5, 4, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style,
_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, 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