From b6e964bd0cdc6fe45540265915b7196ec16cf2bc Mon Sep 17 00:00:00 2001
From: Taha Tesser <48603081+TahaTesser@users.noreply.github.com>
Date: Wed, 30 Apr 2025 02:44:25 +0300
Subject: [PATCH] Fix discrete `Slider` and `RangeSlider` to enforce thumb
height padding when the track shape is non-rounded (#164703)
Fixes [Discrete `Slider` and `RangeSlider` applies thumb padding when
using custom Slider
shapes](https://github.com/flutter/flutter/issues/161805)
### Code Sample
expand to view the code sample
```dart
import 'package:flutter/material.dart';
void main() => runApp(const RangeSliderExampleApp());
class RangeSliderExampleApp extends StatelessWidget {
const RangeSliderExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
sliderTheme: const SliderThemeData(
trackHeight: 32,
trackShape: RectangularSliderTrackShape(),
rangeTrackShape: RectangularRangeSliderTrackShape(),
thumbColor: Colors.amber,
),
),
home: Scaffold(
body: Column(
spacing: 20.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Slider(
value: 100,
max: 100,
divisions: 100,
onChanged: (double value) {},
),
RangeSlider(
values: const RangeValues(0, 100),
max: 100,
divisions: 100,
onChanged: (RangeValues values) {},
),
],
),
),
);
}
}
```
### Before
### After
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
---
.../lib/src/material/range_slider.dart | 9 ++--
packages/flutter/lib/src/material/slider.dart | 8 ++--
.../test/material/range_slider_test.dart | 43 +++++++++++++++++++
.../flutter/test/material/slider_test.dart | 42 ++++++++++++++++++
4 files changed, 93 insertions(+), 9 deletions(-)
diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart
index 5fd0a065f5f..d21cfefb532 100644
--- a/packages/flutter/lib/src/material/range_slider.dart
+++ b/packages/flutter/lib/src/material/range_slider.dart
@@ -1492,8 +1492,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
sliderTheme: _sliderTheme,
isDiscrete: isDiscrete,
);
- final double padding =
- isDiscrete || _sliderTheme.rangeTrackShape!.isRounded ? trackRect.height : 0.0;
+ final double padding = _sliderTheme.rangeTrackShape!.isRounded ? trackRect.height : 0.0;
final double thumbYOffset = trackRect.center.dy;
final double startThumbPosition =
isDiscrete
@@ -1586,8 +1585,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_sliderTheme.rangeTickMarkShape!
.getPreferredSize(isEnabled: isEnabled, sliderTheme: _sliderTheme)
.width;
- final double padding = trackRect.height;
- final double adjustedTrackWidth = trackRect.width - padding;
+ final double discreteTrackPadding = trackRect.height;
+ final double adjustedTrackWidth = trackRect.width - discreteTrackPadding;
// If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy;
@@ -1595,7 +1594,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
final double value = i / divisions!;
// The ticks are mapped to be within the track, so the tick mark width
// must be subtracted from the track width.
- final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
+ final double dx = trackRect.left + value * adjustedTrackWidth + discreteTrackPadding / 2;
final Offset tickMarkOffset = Offset(dx, dy);
_sliderTheme.rangeTickMarkShape!.paint(
context,
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index 45e06e5c5da..e42b39bafb6 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -1740,8 +1740,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
sliderTheme: _sliderTheme,
isDiscrete: isDiscrete,
);
- final double padding =
- isDiscrete || _sliderTheme.trackShape!.isRounded ? trackRect.height : 0.0;
+ final double padding = _sliderTheme.trackShape!.isRounded ? trackRect.height : 0.0;
final double thumbPosition =
isDiscrete
? trackRect.left + visualPosition * (trackRect.width - padding) + padding / 2
@@ -1822,7 +1821,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_sliderTheme.tickMarkShape!
.getPreferredSize(isEnabled: isInteractive, sliderTheme: _sliderTheme)
.width;
- final double adjustedTrackWidth = trackRect.width - padding;
+ final double discreteTrackPadding = trackRect.height;
+ final double adjustedTrackWidth = trackRect.width - discreteTrackPadding;
// If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy;
@@ -1830,7 +1830,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
final double value = i / divisions!;
// The ticks are mapped to be within the track, so the tick mark width
// must be subtracted from the track width.
- final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
+ final double dx = trackRect.left + value * adjustedTrackWidth + discreteTrackPadding / 2;
final Offset tickMarkOffset = Offset(dx, dy);
_sliderTheme.tickMarkShape!.paint(
context,
diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart
index cc76c0d5e8a..9c9f5a5a64f 100644
--- a/packages/flutter/test/material/range_slider_test.dart
+++ b/packages/flutter/test/material/range_slider_test.dart
@@ -3093,4 +3093,47 @@ void main() {
..rrect(rrect: RRect.fromLTRBR(8.0, 7.0, 792.0, 13.0, const Radius.circular(2.0))),
);
});
+
+ // Regression test for hhttps://github.com/flutter/flutter/issues/161805
+ testWidgets('Discrete RangeSlider does not apply thumb padding in a non-rounded track shape', (
+ WidgetTester tester,
+ ) async {
+ // The default track left and right padding.
+ const double sliderPadding = 24.0;
+ final ThemeData theme = ThemeData(
+ sliderTheme: const SliderThemeData(
+ // Thumb padding is applied based on the track height.
+ trackHeight: 100,
+ rangeTrackShape: RectangularRangeSliderTrackShape(),
+ ),
+ );
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: theme,
+ home: Material(
+ child: SizedBox(
+ width: 300,
+ child: RangeSlider(
+ values: const RangeValues(0, 100),
+ max: 100,
+ divisions: 100,
+ onChanged: (RangeValues value) {},
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final MaterialInkController material = Material.of(tester.element(find.byType(RangeSlider)));
+
+ expect(
+ material,
+ paints
+ // Start thumb.
+ ..circle(x: sliderPadding, y: 300.0, color: theme.colorScheme.primary)
+ // End thumb.
+ ..circle(x: 800.0 - sliderPadding, y: 300.0, color: theme.colorScheme.primary),
+ );
+ });
}
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 5da7f13a218..7b4d4240b20 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -5328,4 +5328,46 @@ void main() {
await tester.pumpAndSettle();
expect(log.last, const Offset(400.0, 300.0));
});
+
+ // Regression test for hhttps://github.com/flutter/flutter/issues/161805
+ testWidgets('Discrete Slider does not apply thumb padding in a non-rounded track shape', (
+ WidgetTester tester,
+ ) async {
+ // The default track left and right padding.
+ const double sliderPadding = 24.0;
+ final ThemeData theme = ThemeData(
+ sliderTheme: const SliderThemeData(
+ // Thumb padding is applied based on the track height.
+ trackHeight: 100,
+ trackShape: RectangularSliderTrackShape(),
+ ),
+ );
+
+ Widget buildSlider({required double value}) {
+ return MaterialApp(
+ theme: theme,
+ home: Material(
+ child: SizedBox(
+ width: 300,
+ child: Slider(value: value, max: 100, divisions: 100, onChanged: (double value) {}),
+ ),
+ ),
+ );
+ }
+
+ await tester.pumpWidget(buildSlider(value: 0));
+
+ MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
+
+ expect(material, paints..circle(x: sliderPadding, y: 300.0, color: theme.colorScheme.primary));
+
+ await tester.pumpWidget(buildSlider(value: 100));
+ await tester.pumpAndSettle();
+
+ material = Material.of(tester.element(find.byType(Slider)));
+ expect(
+ material,
+ paints..circle(x: 800.0 - sliderPadding, y: 300.0, color: theme.colorScheme.primary),
+ );
+ });
}