From d01d8aeed07e788942ab5e1fcee94dab97a1b1ec Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 11 May 2023 05:57:55 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20`strokeAlign`=20to=20`Circula?= =?UTF-8?q?rProgressIndicator`=20and=20`RefreshProgressIndicator`=20(#1259?= =?UTF-8?q?45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/src/material/progress_indicator.dart | 62 ++++++++++++++++++- .../material/progress_indicator_test.dart | 41 ++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index 6669850f2b..43541f6bf1 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -421,6 +421,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { required this.offsetValue, required this.rotationValue, required this.strokeWidth, + required this.strokeAlign, this.strokeCap, }) : arcStart = value != null ? _startAngle @@ -437,6 +438,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { final double offsetValue; final double rotationValue; final double strokeWidth; + final double strokeAlign; final double arcStart; final double arcSweep; final StrokeCap? strokeCap; @@ -454,12 +456,27 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; + // Use the negative operator as intended to keep the exposed constant value + // as users are already familiar with. + final double strokeOffset = strokeWidth / 2 * -strokeAlign; + final Offset arcBaseOffset = Offset(strokeOffset, strokeOffset); + final Size arcActualSize = Size( + size.width - strokeOffset * 2, + size.height - strokeOffset * 2, + ); + if (backgroundColor != null) { final Paint backgroundPaint = Paint() ..color = backgroundColor! ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; - canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint); + canvas.drawArc( + arcBaseOffset & arcActualSize, + 0, + _sweep, + false, + backgroundPaint, + ); } if (value == null && strokeCap == null) { @@ -470,7 +487,13 @@ class _CircularProgressIndicatorPainter extends CustomPainter { paint.strokeCap = strokeCap ?? StrokeCap.butt; } - canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint); + canvas.drawArc( + arcBaseOffset & arcActualSize, + arcStart, + arcSweep, + false, + paint, + ); } @override @@ -483,6 +506,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { || oldPainter.offsetValue != offsetValue || oldPainter.rotationValue != rotationValue || oldPainter.strokeWidth != strokeWidth + || oldPainter.strokeAlign != strokeAlign || oldPainter.strokeCap != strokeCap; } } @@ -538,6 +562,7 @@ class CircularProgressIndicator extends ProgressIndicator { super.color, super.valueColor, this.strokeWidth = 4.0, + this.strokeAlign = strokeAlignCenter, super.semanticsLabel, super.semanticsValue, this.strokeCap, @@ -560,6 +585,7 @@ class CircularProgressIndicator extends ProgressIndicator { super.semanticsLabel, super.semanticsValue, this.strokeCap, + this.strokeAlign = strokeAlignCenter, }) : _indicatorType = _ActivityIndicatorType.adaptive; final _ActivityIndicatorType _indicatorType; @@ -577,6 +603,15 @@ class CircularProgressIndicator extends ProgressIndicator { /// The width of the line used to draw the circle. 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 + /// on the edge of the widget. + final double strokeAlign; + /// The progress indicator's line ending. /// /// This determines the shape of the stroke ends of the progress indicator. @@ -598,6 +633,25 @@ class CircularProgressIndicator extends ProgressIndicator { /// degrees and end at 275 degrees. final StrokeCap? strokeCap; + /// The indicator stroke is drawn fully inside of the indicator path. + /// + /// This is a constant for use with [strokeAlign]. + static const double strokeAlignInside = -1.0; + + /// The indicator stroke is drawn on the center of the indicator path, + /// with half of the [strokeWidth] on the inside, and the other half + /// on the outside of the path. + /// + /// This is a constant for use with [strokeAlign]. + /// + /// This is the default value for [strokeAlign]. + static const double strokeAlignCenter = 0.0; + + /// The indicator stroke is drawn on the outside of the indicator path. + /// + /// This is a constant for use with [strokeAlign]. + static const double strokeAlignOutside = 1.0; + @override State createState() => _CircularProgressIndicatorState(); } @@ -677,6 +731,7 @@ class _CircularProgressIndicatorState extends State w offsetValue: offsetValue, rotationValue: rotationValue, strokeWidth: widget.strokeWidth, + strokeAlign: widget.strokeAlign, strokeCap: widget.strokeCap, ), ), @@ -735,6 +790,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter required super.offsetValue, required super.rotationValue, required super.strokeWidth, + required super.strokeAlign, required this.arrowheadScale, required super.strokeCap, }); @@ -805,6 +861,7 @@ class RefreshProgressIndicator extends CircularProgressIndicator { super.color, super.valueColor, super.strokeWidth = defaultStrokeWidth, // Different default than CircularProgressIndicator. + super.strokeAlign, super.semanticsLabel, super.semanticsValue, super.strokeCap, @@ -936,6 +993,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState { offsetValue: offsetValue, rotationValue: rotationValue, strokeWidth: widget.strokeWidth, + strokeAlign: widget.strokeAlign, arrowheadScale: arrowheadScale, strokeCap: widget.strokeCap, ), diff --git a/packages/flutter/test/material/progress_indicator_test.dart b/packages/flutter/test/material/progress_indicator_test.dart index c609c04956..fdf268f4aa 100644 --- a/packages/flutter/test/material/progress_indicator_test.dart +++ b/packages/flutter/test/material/progress_indicator_test.dart @@ -443,6 +443,47 @@ void main() { expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0)); }); + testWidgets('CircularProgressIndicator strokeAlign', (WidgetTester tester) async { + await tester.pumpWidget( + Theme( + data: theme, + child: const CircularProgressIndicator(), + ), + ); + expect(find.byType(CircularProgressIndicator), paints..arc(rect: Offset.zero & const Size(800.0, 600.0))); + + await tester.pumpWidget( + Theme( + data: theme, + child: const CircularProgressIndicator( + strokeAlign: CircularProgressIndicator.strokeAlignInside, + ), + ), + ); + expect(find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(2.0, 2.0) & const Size(796.0, 596.0))); + + await tester.pumpWidget( + Theme( + data: theme, + child: const CircularProgressIndicator( + strokeAlign: CircularProgressIndicator.strokeAlignOutside, + ), + ), + ); + expect(find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(-2.0, -2.0) & const Size(804.0, 604.0))); + + // Unbounded alignment. + await tester.pumpWidget( + Theme( + data: theme, + child: const CircularProgressIndicator( + strokeAlign: 2.0, + ), + ), + ); + expect(find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(-4.0, -4.0) & const Size(808.0, 608.0))); + }); + testWidgets('CircularProgressIndicator with strokeCap', (WidgetTester tester) async { await tester.pumpWidget(const CircularProgressIndicator()); expect(find.byType(CircularProgressIndicator),