diff --git a/AUTHORS b/AUTHORS index 3b0e2a5414..f7a1150b0f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -80,3 +80,4 @@ Ludwik Trammer Marian Triebe Alexis Rouillard Mirko Mucaria +Karol Czeryna diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 4c6c23f055..fa0e189937 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -912,26 +912,69 @@ class _SwitchPainter extends ToggleablePainter { ? (currentValue < 0.5 ? onInactiveThumbImageError : onActiveThumbImageError) : onInactiveThumbImageError; - // Paint the track final Paint paint = Paint() ..color = trackColor; - const double trackHorizontalPadding = kRadialReactionRadius - _kTrackRadius; + + final Offset trackPaintOffset = _computeTrackPaintOffset(size, _kTrackWidth, _kTrackHeight); + final Offset thumbPaintOffset = _computeThumbPaintOffset(trackPaintOffset, visualPosition); + final Offset radialReactionOrigin = Offset(thumbPaintOffset.dx + _kThumbRadius, size.height / 2); + + _paintTrackWith(canvas, paint, trackPaintOffset); + paintRadialReaction(canvas: canvas, origin: radialReactionOrigin); + _paintThumbWith( + thumbPaintOffset, + canvas, + currentValue, + thumbColor, + thumbImage, + thumbErrorListener, + ); + } + + /// Computes canvas offset for track's upper left corner + Offset _computeTrackPaintOffset(Size canvasSize, double trackWidth, double trackHeight) { + final double horizontalOffset = (canvasSize.width - _kTrackWidth) / 2.0; + final double verticalOffset = (canvasSize.height - _kTrackHeight) / 2.0; + + return Offset(horizontalOffset, verticalOffset); + } + + /// Computes canvas offset for thumb's upper left corner as if it were a + /// square + Offset _computeThumbPaintOffset(Offset trackPaintOffset, double visualPosition) { + // How much thumb radius extends beyond the track + const double additionalThumbRadius = _kThumbRadius - _kTrackRadius; + + final double horizontalProgress = visualPosition * trackInnerLength; + final double thumbHorizontalOffset = trackPaintOffset.dx - additionalThumbRadius + horizontalProgress; + final double thumbVerticalOffset = trackPaintOffset.dy - additionalThumbRadius; + + return Offset(thumbHorizontalOffset, thumbVerticalOffset); + } + + void _paintTrackWith(Canvas canvas, Paint paint, Offset trackPaintOffset) { final Rect trackRect = Rect.fromLTWH( - trackHorizontalPadding, - (size.height - _kTrackHeight) / 2.0, - size.width - 2.0 * trackHorizontalPadding, + trackPaintOffset.dx, + trackPaintOffset.dy, + _kTrackWidth, _kTrackHeight, ); - final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius)); - canvas.drawRRect(trackRRect, paint); - - final Offset thumbPosition = Offset( - kRadialReactionRadius + visualPosition * trackInnerLength, - size.height / 2.0, + final RRect trackRRect = RRect.fromRectAndRadius( + trackRect, + const Radius.circular(_kTrackRadius), ); - paintRadialReaction(canvas: canvas, origin: thumbPosition); + canvas.drawRRect(trackRRect, paint); + } + void _paintThumbWith( + Offset thumbPaintOffset, + Canvas canvas, + double currentValue, + Color thumbColor, + ImageProvider? thumbImage, + ImageErrorListener? thumbErrorListener, + ) { try { _isPainting = true; if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage || thumbErrorListener != _cachedThumbErrorListener) { @@ -946,9 +989,10 @@ class _SwitchPainter extends ToggleablePainter { // The thumb contracts slightly during the animation final double inset = 1.0 - (currentValue - 0.5).abs() * 2.0; final double radius = _kThumbRadius - inset; + thumbPainter.paint( canvas, - thumbPosition - Offset(radius, radius), + thumbPaintOffset + Offset(0, inset), configuration.copyWith(size: Size.fromRadius(radius)), ); } finally { diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index 34199a76f7..e0e968f5e0 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -92,6 +92,57 @@ void main() { expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); }); + testWidgets('Switch does not get distorted upon changing constraints with parent', (WidgetTester tester) async { + const double maxWidth = 300; + const double maxHeight = 100; + + const ValueKey boundaryKey = ValueKey('switch container'); + + Widget buildSwitch({required double width, required double height}) { + return MaterialApp( + home: Scaffold( + body: Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: maxWidth, + height: maxHeight, + child: RepaintBoundary( + key: boundaryKey, + child: SizedBox( + width: width, + height: height, + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: true, + onChanged: (_) {}, + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch( + width: maxWidth, + height: maxHeight, + )); + await expectLater( + find.byKey(boundaryKey), + matchesGoldenFile('switch_test.big.on.png'), + ); + + await tester.pumpWidget(buildSwitch( + width: 20, + height: 10, + )); + await expectLater( + find.byKey(boundaryKey), + matchesGoldenFile('switch_test.small.on.png'), + ); + }); + testWidgets('Switch can drag (LTR)', (WidgetTester tester) async { bool value = false;