Unverified Commit a276ab3d authored by Anthony's avatar Anthony Committed by GitHub

[Material] Properly call onChangeStart and onChangeEnd in Range Slider (#34869)

Fixes a bug in the Range Sider where the onChangeStart and onChangeEnd were not being called if the initial thumb selection came from a drag (rather than a tap).
parent afe790a7
......@@ -454,8 +454,8 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
}
// Finds closest thumb. If the thumbs are close to each other, no thumb is
// immediately selected while the drag velocity is zero. If the first
// non-zero velocity is negative, then the left thumb is selected, and if its
// 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.
static final RangeThumbSelector _defaultRangeThumbSelector = (
TextDirection textDirection,
......@@ -463,16 +463,17 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
double tapValue,
Size thumbSize,
Size trackSize,
double dx, // drag velocity
double dx, // The horizontal delta or displacement of the drag update.
) {
final double touchRadius = math.max(thumbSize.width, RangeSlider._minTouchTargetWidth) / 2;
final bool inStartTouchTarget = (tapValue - values.start).abs() * trackSize.width < touchRadius;
final bool inEndTouchTarget = (tapValue - values.end).abs() * trackSize.width < touchRadius;
// Use the velocity if the thumb touch targets overlap. If dx is 0 and the
// the drag position is in both touch targets, no thumb is selected because
// it is ambiguous to which thumb should be selected. Once the drag
// velocity is non-zero, the thumb selection can be determined.
// Use dx if the thumb touch targets overlap. If dx is 0 and the drag
// position is in both touch targets, no thumb is selected because it is
// ambiguous to which thumb should be selected. If the dx is non-zero, the
// thumb selection is determined by the direction of the dx. The left thumb
// is chosen for negative dx, and the right thumb is chosen for positive dx.
if (inStartTouchTarget && inEndTouchTarget) {
bool towardsStart;
bool towardsEnd;
......@@ -729,6 +730,7 @@ class _RenderRangeSlider extends RenderBox {
HorizontalDragGestureRecognizer _drag;
TapGestureRecognizer _tap;
bool _active = false;
RangeValues _newValues;
bool get isEnabled => onChanged != null;
......@@ -985,11 +987,10 @@ class _RenderRangeSlider extends RenderBox {
// a tap, it consists of a call to onChangeStart with the previous value and
// a call to onChangeEnd with the new value.
final RangeValues currentValues = _discretizeRangeValues(values);
RangeValues newValues;
if (_lastThumbSelection == Thumb.start) {
newValues = RangeValues(tapValue, currentValues.end);
_newValues = RangeValues(tapValue, currentValues.end);
} else if (_lastThumbSelection == Thumb.end) {
newValues = RangeValues(currentValues.start, tapValue);
_newValues = RangeValues(currentValues.start, tapValue);
}
_updateLabelPainter(_lastThumbSelection);
......@@ -997,7 +998,7 @@ class _RenderRangeSlider extends RenderBox {
onChangeStart(currentValues);
}
onChanged(_discretizeRangeValues(newValues));
onChanged(_discretizeRangeValues(_newValues));
_state.overlayController.forward();
if (showValueIndicator) {
......@@ -1017,11 +1018,15 @@ class _RenderRangeSlider extends RenderBox {
void _handleDragUpdate(DragUpdateDetails details) {
final double dragValue = _getValueFromGlobalPosition(details.globalPosition);
// If no selection has been made yet, use the velocity of the drag to
// determine which thumb should be selected.
// If no selection has been made yet, test for thumb selection again now
// that the value of dx can be non-zero. If this is the first selection of
// the interaction, then onChangeStart must be called.
bool shouldCallOnChangeStart = false;
if (_lastThumbSelection == null) {
_lastThumbSelection = sliderTheme.thumbSelector(textDirection, values, dragValue, _thumbSize, size, details.delta.dx);
if (_lastThumbSelection != null) {
shouldCallOnChangeStart = true;
_active = true;
_state.overlayController.forward();
if (showValueIndicator) {
_state.valueIndicatorController.forward();
......@@ -1031,16 +1036,18 @@ class _RenderRangeSlider extends RenderBox {
if (isEnabled && _lastThumbSelection != null) {
final RangeValues currentValues = _discretizeRangeValues(values);
if (onChangeStart != null && shouldCallOnChangeStart) {
onChangeStart(currentValues);
}
final double currentDragValue = _discretize(dragValue);
RangeValues newValues;
final double minThumbSeparationValue = isDiscrete ? 0 : sliderTheme.minThumbSeparation / _trackRect.width;
if (_lastThumbSelection == Thumb.start) {
newValues = RangeValues(math.min(currentDragValue, currentValues.end - minThumbSeparationValue), currentValues.end);
_newValues = RangeValues(math.min(currentDragValue, currentValues.end - minThumbSeparationValue), currentValues.end);
} else if (_lastThumbSelection == Thumb.end) {
newValues = RangeValues(currentValues.start, math.max(currentDragValue, currentValues.start + minThumbSeparationValue));
_newValues = RangeValues(currentValues.start, math.max(currentDragValue, currentValues.start + minThumbSeparationValue));
}
onChanged(newValues);
onChanged(_newValues);
}
}
......@@ -1050,10 +1057,8 @@ class _RenderRangeSlider extends RenderBox {
_state.valueIndicatorController.reverse();
}
if (_active && _state.mounted) {
if (_lastThumbSelection == null)
return;
final RangeValues discreteValues = _discretizeRangeValues(values);
if (_active && _state.mounted && _lastThumbSelection != null) {
final RangeValues discreteValues = _discretizeRangeValues(_newValues);
if (onChangeEnd != null) {
onChangeEnd(discreteValues);
}
......
......@@ -49,7 +49,8 @@ void main() {
await tester.pump();
expect(values, equals(const RangeValues(0.3, 0.7)));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -103,7 +104,8 @@ void main() {
await tester.pump();
expect(values, equals(const RangeValues(0.3, 0.7)));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -160,7 +162,8 @@ void main() {
await tester.pumpAndSettle();
expect(values, equals(const RangeValues(30, 70)));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -219,7 +222,8 @@ void main() {
await tester.pumpAndSettle();
expect(values, equals(const RangeValues(30, 70)));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -267,7 +271,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -311,7 +316,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -358,7 +364,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -405,7 +412,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
......@@ -449,7 +457,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -499,7 +508,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -552,7 +562,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -605,7 +616,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -655,7 +667,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -705,7 +718,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -758,7 +772,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -811,7 +826,8 @@ void main() {
),
);
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -833,6 +849,125 @@ void main() {
expect(values.end, closeTo(80, 0.01));
});
testWidgets('Range Slider onChangeEnd and onChangeStart are called on an interaction initiated by tap', (WidgetTester tester) async {
RangeValues values = const RangeValues(30, 70);
RangeValues startValues;
RangeValues endValues;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return MediaQuery(
data: MediaQueryData.fromWindow(window),
child: Material(
child: Center(
child: RangeSlider(
values: values,
min: 0,
max: 100,
onChanged: (RangeValues newValues) {
setState(() {
values = newValues;
});
},
onChangeStart: (RangeValues newValues) {
startValues = newValues;
},
onChangeEnd: (RangeValues newValues) {
endValues = newValues;
},
),
),
),
);
},
),
),
);
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
// Drag the start thumb towards the center.
final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3;
expect(startValues, null);
expect(endValues, null);
await tester.dragFrom(leftTarget, (bottomRight - topLeft) * 0.2);
expect(startValues.start, closeTo(30, 1));
expect(startValues.end, closeTo(70, 1));
expect(values.start, closeTo(50, 1));
expect(values.end, closeTo(70, 1));
expect(endValues.start, closeTo(50, 1));
expect(endValues.end, closeTo(70, 1));
});
testWidgets('Range Slider onChangeEnd and onChangeStart are called on an interaction initiated by drag', (WidgetTester tester) async {
RangeValues values = const RangeValues(30, 70);
RangeValues startValues;
RangeValues endValues;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return MediaQuery(
data: MediaQueryData.fromWindow(window),
child: Material(
child: Center(
child: RangeSlider(
values: values,
min: 0,
max: 100,
onChanged: (RangeValues newValues) {
setState(() {
values = newValues;
});
},
onChangeStart: (RangeValues newValues) {
startValues = newValues;
},
onChangeEnd: (RangeValues newValues) {
endValues = newValues;
},
),
),
),
);
},
),
),
);
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
// Drag the thumbs together.
final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3;
await tester.dragFrom(leftTarget, (bottomRight - topLeft) * 0.2);
await tester.pumpAndSettle();
final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7;
await tester.dragFrom(rightTarget, (bottomRight - topLeft) * -0.2);
await tester.pumpAndSettle();
expect(values.start, closeTo(50, 1));
expect(values.end, closeTo(51, 1));
// Drag the end thumb to the right.
final Offset middleTarget = topLeft + (bottomRight - topLeft) * 0.5;
await tester.dragFrom(middleTarget, (bottomRight - topLeft) * 0.4);
await tester.pumpAndSettle();
expect(startValues.start, closeTo(50, 1));
expect(startValues.end, closeTo(51, 1));
expect(endValues.start, closeTo(50, 1));
expect(endValues.end, closeTo(90, 1));
});
ThemeData _buildTheme() {
return ThemeData(
platform: TargetPlatform.android,
......@@ -1226,7 +1361,8 @@ void main() {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......@@ -1295,7 +1431,8 @@ void main() {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider));
// Get the bounds of the track by shifting by the the overlay radius.
// Get the bounds of the track by finding the slider edges and translating
// inwards by the overlay radius.
final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0);
final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0);
final Offset middle = topLeft + bottomRight / 2;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment