[Range slider] Tap on active range, the thumb closest to the mouse cursor should move to the cursor position. (#173725)

Fix: https://github.com/flutter/flutter/issues/172923 


Internal issue: b/434778923



## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] 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.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[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
This commit is contained in:
Hannah Jin 2025-08-14 11:27:51 -07:00 committed by GitHub
parent 38eae2bf09
commit 450401dfec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 31 additions and 28 deletions

View File

@ -599,10 +599,13 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
return RangeValues(_unlerp(values.start), _unlerp(values.end));
}
// Finds closest thumb. If the thumbs are close to each other, no thumb is
// immediately selected while the drag displacement is zero. If the first
// non-zero displacement is negative, then the left thumb is selected, and if its
// positive, then the right thumb is selected.
// Finds the closest thumb. If both thumbs are close to each other and within
// the touch radius, neither is selected immediately while the drag
// displacement is zero. The first non-zero displacement determines which
// thumb is selected: a negative displacement selects the left thumb,
// a positive one selects the right thumb.
// If only one or zero thumbs are within the touch radius,
// the closest one is selected.
Thumb? _defaultRangeThumbSelector(
TextDirection textDirection,
RangeValues values,
@ -632,11 +635,10 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
return Thumb.end;
}
} else {
// Snap position on the track if its in the inactive range.
if (tapValue < values.start || inStartTouchTarget) {
// Choose the closest thumb and snap position.
if (tapValue * 2 < values.start + values.end) {
return Thumb.start;
}
if (tapValue > values.end || inEndTouchTarget) {
} else {
return Thumb.end;
}
}

View File

@ -125,7 +125,7 @@ void main() {
});
testWidgets('Range Slider can move when tapped (continuous LTR)', (WidgetTester tester) async {
RangeValues values = const RangeValues(0.3, 0.7);
RangeValues values = const RangeValues(0.3, 0.8);
await tester.pumpWidget(
MaterialApp(
@ -151,13 +151,13 @@ void main() {
),
);
// No thumbs get select when tapping between the thumbs outside the touch
// The closest thumb is selected when tapping between the thumbs outside the touch
// boundaries
expect(values, equals(const RangeValues(0.3, 0.7)));
expect(values, equals(const RangeValues(0.3, 0.8)));
// taps at 0.5
await tester.tap(find.byType(RangeSlider));
await tester.pump();
expect(values, equals(const RangeValues(0.3, 0.7)));
expect(values, equals(const RangeValues(0.5, 0.8)));
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
@ -168,7 +168,7 @@ void main() {
final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1;
await tester.tapAt(leftTarget);
expect(values.start, moreOrLessEquals(0.1, epsilon: 0.01));
expect(values.end, equals(0.7));
expect(values.end, equals(0.8));
// The end thumb is selected when tapping the right inactive track.
await tester.pump();
@ -179,7 +179,7 @@ void main() {
});
testWidgets('Range Slider can move when tapped (continuous RTL)', (WidgetTester tester) async {
RangeValues values = const RangeValues(0.3, 0.7);
RangeValues values = const RangeValues(0.3, 1.0);
await tester.pumpWidget(
MaterialApp(
@ -205,13 +205,13 @@ void main() {
),
);
// No thumbs get select when tapping between the thumbs outside the touch
// The closest thumb is selected when tapping between the thumbs outside the touch
// boundaries
expect(values, equals(const RangeValues(0.3, 0.7)));
expect(values, equals(const RangeValues(0.3, 1.0)));
// taps at 0.5
await tester.tap(find.byType(RangeSlider));
await tester.pump();
expect(values, equals(const RangeValues(0.3, 0.7)));
expect(values, equals(const RangeValues(0.5, 1.0)));
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
@ -221,7 +221,7 @@ void main() {
// The end thumb is selected when tapping the left inactive track.
final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1;
await tester.tapAt(leftTarget);
expect(values.start, 0.3);
expect(values.start, 0.5);
expect(values.end, moreOrLessEquals(0.9, epsilon: 0.01));
// The start thumb is selected when tapping the right inactive track.
@ -233,7 +233,7 @@ void main() {
});
testWidgets('Range Slider can move when tapped (discrete LTR)', (WidgetTester tester) async {
RangeValues values = const RangeValues(30, 70);
RangeValues values = const RangeValues(30, 80);
await tester.pumpWidget(
MaterialApp(
@ -261,13 +261,13 @@ void main() {
),
);
// No thumbs get select when tapping between the thumbs outside the touch
// The closest thumb is selected when tapping between the thumbs outside the touch
// boundaries
expect(values, equals(const RangeValues(30, 70)));
expect(values, equals(const RangeValues(30, 80)));
// taps at 0.5
await tester.tap(find.byType(RangeSlider));
await tester.pumpAndSettle();
expect(values, equals(const RangeValues(30, 70)));
expect(values, equals(const RangeValues(50, 80)));
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
@ -279,7 +279,7 @@ void main() {
await tester.tapAt(leftTarget);
await tester.pumpAndSettle();
expect(values.start.round(), equals(10));
expect(values.end.round(), equals(70));
expect(values.end.round(), equals(80));
// The end thumb is selected when tapping the right inactive track.
await tester.pump();
@ -291,7 +291,7 @@ void main() {
});
testWidgets('Range Slider can move when tapped (discrete RTL)', (WidgetTester tester) async {
RangeValues values = const RangeValues(30, 70);
RangeValues values = const RangeValues(30, 80);
await tester.pumpWidget(
MaterialApp(
@ -319,13 +319,13 @@ void main() {
),
);
// No thumbs get select when tapping between the thumbs outside the touch
// The closest thumb is selected when tapping between the thumbs outside the touch
// boundaries
expect(values, equals(const RangeValues(30, 70)));
expect(values, equals(const RangeValues(30, 80)));
// taps at 0.5
await tester.tap(find.byType(RangeSlider));
await tester.pumpAndSettle();
expect(values, equals(const RangeValues(30, 70)));
expect(values, equals(const RangeValues(50, 80)));
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
@ -336,7 +336,7 @@ void main() {
final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1;
await tester.tapAt(leftTarget);
await tester.pumpAndSettle();
expect(values.start.round(), equals(30));
expect(values.start.round(), equals(50));
expect(values.end.round(), equals(90));
// The end thumb is selected when tapping the right inactive track.
@ -744,6 +744,7 @@ void main() {
final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7;
await tester.dragFrom(rightTarget, middle - rightTarget);
expect(values.start, moreOrLessEquals(50, epsilon: 0.01));
expect(values.end, moreOrLessEquals(50, epsilon: 0.01));
// Drag the start thumb apart.
await tester.pumpAndSettle();