diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index 067f011d41..29ae62f0a6 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -1402,8 +1402,32 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix sliderTheme: _sliderTheme, isDiscrete: isDiscrete, ); - _startThumbCenter = Offset(trackRect.left + startVisualPosition * trackRect.width, trackRect.center.dy); - _endThumbCenter = Offset(trackRect.left + endVisualPosition * trackRect.width, trackRect.center.dy); + final double padding = isDiscrete || _sliderTheme.rangeTrackShape!.isRounded ? trackRect.height : 0.0; + final double thumbYOffset = trackRect.center.dy; + final double startThumbPosition = isDiscrete + ? trackRect.left + startVisualPosition * (trackRect.width - padding) + padding / 2 + : trackRect.left + startVisualPosition * trackRect.width; + final double endThumbPosition = isDiscrete + ? trackRect.left + endVisualPosition * (trackRect.width - padding) + padding / 2 + : trackRect.left + endVisualPosition * trackRect.width; + final Size thumbPreferredSize = _sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete); + final double thumbPadding = (padding > thumbPreferredSize.width / 2 ? padding / 2 : 0); + _startThumbCenter = Offset( + clampDouble( + startThumbPosition, + trackRect.left + thumbPadding, + trackRect.right - thumbPadding, + ), + thumbYOffset, + ); + _endThumbCenter = Offset( + clampDouble( + endThumbPosition, + trackRect.left + thumbPadding, + trackRect.right - thumbPadding, + ), + thumbYOffset, + ); if (isEnabled) { final Size overlaySize = sliderTheme.overlayShape!.getPreferredSize(isEnabled, false); overlayStartRect = Rect.fromCircle(center: _startThumbCenter, radius: overlaySize.width / 2.0); @@ -1766,7 +1790,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } } - class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget { const _ValueIndicatorRenderObjectWidget({ required this.state, diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index 4cdc67a24b..f6e01833b2 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -1576,6 +1576,10 @@ abstract class RangeSliderTrackShape { bool isDiscrete = false, required TextDirection textDirection, }); + + /// Whether the track shape is rounded. This is used to determine the correct + /// position of the thumbs in relation to the track. Defaults to false. + bool get isRounded => false; } /// Base track shape that provides an implementation of [getPreferredRect] for @@ -2106,15 +2110,6 @@ class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape with BaseRa ), inactivePaint, ); - context.canvas.drawRect( - Rect.fromLTRB( - leftThumbOffset.dx, - trackRect.top - (additionalActiveTrackHeight / 2), - rightThumbOffset.dx, - trackRect.bottom + (additionalActiveTrackHeight / 2), - ), - activePaint, - ); context.canvas.drawRRect( RRect.fromLTRBAndCorners( rightThumbOffset.dx, @@ -2126,7 +2121,20 @@ class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape with BaseRa ), inactivePaint, ); + context.canvas.drawRRect( + RRect.fromLTRBR( + leftThumbOffset.dx - (sliderTheme.trackHeight! / 2), + trackRect.top - (additionalActiveTrackHeight / 2), + rightThumbOffset.dx + (sliderTheme.trackHeight! / 2), + trackRect.bottom + (additionalActiveTrackHeight / 2), + trackRadius, + ), + activePaint, + ); } + + @override + bool get isRounded => true; } /// The default shape of each [Slider] tick mark. diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart index cc516962c5..2950dc064e 100644 --- a/packages/flutter/test/material/range_slider_test.dart +++ b/packages/flutter/test/material/range_slider_test.dart @@ -1113,8 +1113,8 @@ void main() { sliderBox, paints ..rrect(color: sliderTheme.inactiveTrackColor) - ..rect(color: sliderTheme.activeTrackColor) - ..rrect(color: sliderTheme.inactiveTrackColor), + ..rrect(color: sliderTheme.inactiveTrackColor) + ..rrect(color: sliderTheme.activeTrackColor), ); expect( sliderBox, @@ -1142,8 +1142,8 @@ void main() { sliderBox, paints ..rrect(color: sliderTheme.inactiveTrackColor) - ..rect(color: activeColor) - ..rrect(color: sliderTheme.inactiveTrackColor), + ..rrect(color: sliderTheme.inactiveTrackColor) + ..rrect(color: activeColor), ); expect( sliderBox, @@ -1170,8 +1170,8 @@ void main() { sliderBox, paints ..rrect(color: inactiveColor) - ..rect(color: sliderTheme.activeTrackColor) - ..rrect(color: inactiveColor), + ..rrect(color: inactiveColor) + ..rrect(color: sliderTheme.activeTrackColor), ); expect( sliderBox, @@ -1202,8 +1202,8 @@ void main() { sliderBox, paints ..rrect(color: inactiveColor) - ..rect(color: activeColor) - ..rrect(color: inactiveColor), + ..rrect(color: inactiveColor) + ..rrect(color: activeColor), ); expect( sliderBox, @@ -1229,8 +1229,8 @@ void main() { sliderBox, paints ..rrect(color: sliderTheme.inactiveTrackColor) - ..rect(color: sliderTheme.activeTrackColor) - ..rrect(color: sliderTheme.inactiveTrackColor), + ..rrect(color: sliderTheme.inactiveTrackColor) + ..rrect(color: sliderTheme.activeTrackColor), ); expect( sliderBox, @@ -1267,8 +1267,8 @@ void main() { sliderBox, paints ..rrect(color: inactiveColor) - ..rect(color: activeColor) - ..rrect(color: inactiveColor), + ..rrect(color: inactiveColor) + ..rrect(color: activeColor), ); expect( sliderBox, @@ -1300,8 +1300,8 @@ void main() { sliderBox, paints ..rrect(color: sliderTheme.disabledInactiveTrackColor) - ..rect(color: sliderTheme.disabledActiveTrackColor) - ..rrect(color: sliderTheme.disabledInactiveTrackColor), + ..rrect(color: sliderTheme.disabledInactiveTrackColor) + ..rrect(color: sliderTheme.disabledActiveTrackColor), ); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); @@ -1327,8 +1327,8 @@ void main() { sliderBox, paints ..rrect(color: sliderTheme.disabledInactiveTrackColor) - ..rect(color: sliderTheme.disabledActiveTrackColor) - ..rrect(color: sliderTheme.disabledInactiveTrackColor), + ..rrect(color: sliderTheme.disabledInactiveTrackColor) + ..rrect(color: sliderTheme.disabledActiveTrackColor), ); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); @@ -2080,10 +2080,10 @@ void main() { paints // left inactive track RRect ..rrect(rrect: RRect.fromLTRBAndCorners(-24.0, 3.0, -12.0, 7.0, topLeft: const Radius.circular(2.0), bottomLeft: const Radius.circular(2.0))) - // active track RRect - ..rect(rect: const Rect.fromLTRB(-12.0, 2.0, 0.0, 8.0)) // right inactive track RRect ..rrect(rrect: RRect.fromLTRBAndCorners(0.0, 3.0, 24.0, 7.0, topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0))) + // active track RRect + ..rrect(rrect: RRect.fromLTRBR(-14.0, 2.0, 2.0, 8.0, const Radius.circular(2.0))) // thumbs ..circle(x: -12.0, y: 5.0, radius: 10.0) ..circle(x: 0.0, y: 5.0, radius: 10.0), @@ -2161,22 +2161,41 @@ void main() { await tester.pumpWidget(buildFrame(15)); await tester.pumpAndSettle(); // Finish the animation. - late Rect activeTrackRect; - expect(renderObject, paints..something((Symbol method, List arguments) { - if (method != #drawRect) { + late RRect activeTrackRRect; + expect(renderObject, paints + ..rrect() + ..rrect() + ..something((Symbol method, List arguments) { + if (method != #drawRRect) { return false; } - activeTrackRect = arguments[0] as Rect; + activeTrackRRect = arguments[0] as RRect; return true; })); + const double padding = 4.0; // The 1st thumb should at one-third(5 / 15) of the Slider. // The 2nd thumb should at (8 / 15) of the Slider. // The left of the active track shape is the position of the 1st thumb. // The right of the active track shape is the position of the 2nd thumb. - // 24.0 is the default margin, (800.0 - 24.0 - 24.0) is the slider's width. - expect(nearEqual(activeTrackRect.left, (800.0 - 24.0 - 24.0) * (5 / 15) + 24.0, 0.01), true); - expect(nearEqual(activeTrackRect.right, (800.0 - 24.0 - 24.0) * (8 / 15) + 24.0, 0.01), true); + // 24.0 is the default margin, (800.0 - 24.0 - 24.0 - padding) is the slider's width. + // Where the padding value equals to the track height. + expect( + nearEqual( + activeTrackRRect.left, + (800.0 - 24.0 - 24.0 - padding) * (5 / 15) + 24.0, + 0.01, + ), + true, + ); + expect( + nearEqual( + activeTrackRRect.right, + (800.0 - 24.0 - 24.0 - padding) * (8 / 15) + 24.0 + padding, + 0.01, + ), + true, + ); }); testWidgets('RangeSlider changes mouse cursor when hovered', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index 525826efe5..7e5b9022fb 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -1533,8 +1533,6 @@ void main() { topLeft: const Radius.circular(2.0), bottomLeft: const Radius.circular(2.0), )) - // active track RRect Start 10 pixels from left screen. - ..rect(rect:const Rect.fromLTRB(10.0, 297.0, 790.0, 303.0),) // inactive track RRect. Ends 10 pixels from right of screen. ..rrect(rrect: RRect.fromLTRBAndCorners( 790.0, @@ -1544,6 +1542,11 @@ void main() { topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0), )) + // active track RRect Start 10 pixels from left screen. + ..rrect(rrect: RRect.fromLTRBR( + 8.0, 297.0, 792.0, 303.0, + const Radius.circular(2.0)) + ) // The thumb Left. ..circle(x: 10.0, y: 300.0, radius: 10.0) // The thumb Right. @@ -1806,12 +1809,15 @@ void main() { topLeft: const Radius.circular(2.0), bottomLeft: const Radius.circular(2.0), )) - ..rect(rect: const Rect.fromLTRB(24.0, 297.0, 24.0, 303.0)) ..rrect(rrect: RRect.fromLTRBAndCorners( 24.0, 298.0, 776.0, 302.0, topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0), )) + ..rrect(rrect: RRect.fromLTRBR( + 22.0, 297.0, 26.0, 303.0, + const Radius.circular(2.0)), + ) ..circle(x: 24.0, y: 300.0) ..shadow(elevation: 1.0) ..circle(x: 24.0, y: 300.0) @@ -1855,12 +1861,15 @@ void main() { topLeft: const Radius.circular(2.0), bottomLeft: const Radius.circular(2.0), )) - ..rect(rect: const Rect.fromLTRB(24.0, 297.0, 24.0, 303.0)) ..rrect(rrect: RRect.fromLTRBAndCorners( 24.0, 298.0, 776.0, 302.0, topRight: const Radius.circular(2.0), bottomRight: const Radius.circular(2.0), )) + ..rrect(rrect: RRect.fromLTRBR( + 22.0, 297.0, 26.0, 303.0, + const Radius.circular(2)), + ) ..circle(x: 24.0, y: 300.0) ..path(strokeWidth: 1.0 * 2.0, color: Colors.black) ..circle(x: 24.0, y: 300.0) @@ -2554,9 +2563,11 @@ void main() { }); }); - testWidgets('SliderTrackShape isRounded defaults', (WidgetTester tester) async { + testWidgets('Track shape isRounded defaults', (WidgetTester tester) async { expect(const RectangularSliderTrackShape().isRounded, isFalse); expect(const RoundedRectSliderTrackShape().isRounded, isTrue); + expect(const RectangularRangeSliderTrackShape().isRounded, isFalse); + expect(const RoundedRectRangeSliderTrackShape().isRounded, isTrue); }); testWidgets('SliderThemeData.padding can override the default Slider padding', (WidgetTester tester) async {