Unverified Commit bd06749e authored by Jose Alba's avatar Jose Alba Committed by GitHub

Slider value indicator gets disposed if it is activated (#57535)

parent 8443f7cf
...@@ -472,6 +472,10 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin ...@@ -472,6 +472,10 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
enableController.dispose(); enableController.dispose();
startPositionController.dispose(); startPositionController.dispose();
endPositionController.dispose(); endPositionController.dispose();
if (overlayEntry != null) {
overlayEntry.remove();
overlayEntry = null;
}
super.dispose(); super.dispose();
} }
...@@ -1154,6 +1158,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ...@@ -1154,6 +1158,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
} }
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (!_state.mounted) {
return;
}
final double dragValue = _getValueFromGlobalPosition(details.globalPosition); final double dragValue = _getValueFromGlobalPosition(details.globalPosition);
// If no selection has been made yet, test for thumb selection again now // If no selection has been made yet, test for thumb selection again now
...@@ -1190,7 +1198,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ...@@ -1190,7 +1198,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
} }
void _endInteraction() { void _endInteraction() {
_state.overlayController.reverse(); if (!_state.mounted) {
return;
}
if (showValueIndicator && _state.interactionTimer == null) { if (showValueIndicator && _state.interactionTimer == null) {
_state.valueIndicatorController.reverse(); _state.valueIndicatorController.reverse();
} }
...@@ -1202,6 +1213,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ...@@ -1202,6 +1213,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
} }
_active = false; _active = false;
} }
_state.overlayController.reverse();
} }
void _handleDragStart(DragStartDetails details) { void _handleDragStart(DragStartDetails details) {
...@@ -1388,22 +1400,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ...@@ -1388,22 +1400,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
if (shouldPaintValueIndicators) { if (shouldPaintValueIndicators) {
_state.paintBottomValueIndicator = (PaintingContext context, Offset offset) { _state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint( if (attached) {
context, _sliderTheme.rangeValueIndicatorShape.paint(
bottomThumbCenter, context,
activationAnimation: _valueIndicatorAnimation, bottomThumbCenter,
enableAnimation: _enableAnimation, activationAnimation: _valueIndicatorAnimation,
isDiscrete: isDiscrete, enableAnimation: _enableAnimation,
isOnTop: false, isDiscrete: isDiscrete,
labelPainter: bottomLabelPainter, isOnTop: false,
parentBox: this, labelPainter: bottomLabelPainter,
sliderTheme: _sliderTheme, parentBox: this,
textDirection: _textDirection, sliderTheme: _sliderTheme,
thumb: bottomThumb, textDirection: _textDirection,
value: bottomValue, thumb: bottomThumb,
textScaleFactor: textScaleFactor, value: bottomValue,
sizeWithOverflow: resolvedscreenSize, textScaleFactor: textScaleFactor,
); sizeWithOverflow: resolvedscreenSize,
);
}
}; };
} }
...@@ -1462,22 +1476,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ...@@ -1462,22 +1476,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
} }
_state.paintTopValueIndicator = (PaintingContext context, Offset offset) { _state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint( if (attached) {
context, _sliderTheme.rangeValueIndicatorShape.paint(
topThumbCenter, context,
activationAnimation: _valueIndicatorAnimation, topThumbCenter,
enableAnimation: _enableAnimation, activationAnimation: _valueIndicatorAnimation,
isDiscrete: isDiscrete, enableAnimation: _enableAnimation,
isOnTop: thumbDelta < innerOverflow, isDiscrete: isDiscrete,
labelPainter: topLabelPainter, isOnTop: thumbDelta < innerOverflow,
parentBox: this, labelPainter: topLabelPainter,
sliderTheme: _sliderTheme, parentBox: this,
textDirection: _textDirection, sliderTheme: _sliderTheme,
thumb: topThumb, textDirection: _textDirection,
value: topValue, thumb: topThumb,
textScaleFactor: textScaleFactor, value: topValue,
sizeWithOverflow: resolvedscreenSize, textScaleFactor: textScaleFactor,
); sizeWithOverflow: resolvedscreenSize,
);
}
}; };
} }
......
...@@ -512,6 +512,10 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -512,6 +512,10 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
valueIndicatorController.dispose(); valueIndicatorController.dispose();
enableController.dispose(); enableController.dispose();
positionController.dispose(); positionController.dispose();
if (overlayEntry != null) {
overlayEntry.remove();
overlayEntry = null;
}
super.dispose(); super.dispose();
} }
...@@ -1238,6 +1242,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1238,6 +1242,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
} }
void _endInteraction() { void _endInteraction() {
if (!_state.mounted) {
return;
}
if (_active && _state.mounted) { if (_active && _state.mounted) {
if (onChangeEnd != null) { if (onChangeEnd != null) {
onChangeEnd(_discretize(_currentDragValue)); onChangeEnd(_discretize(_currentDragValue));
...@@ -1255,6 +1263,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1255,6 +1263,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition); void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition);
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (!_state.mounted) {
return;
}
if (isInteractive) { if (isInteractive) {
final double valueDelta = details.primaryDelta / _trackRect.width; final double valueDelta = details.primaryDelta / _trackRect.width;
switch (textDirection) { switch (textDirection) {
...@@ -1396,20 +1408,22 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1396,20 +1408,22 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) { if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
if (showValueIndicator) { if (showValueIndicator) {
_state.paintValueIndicator = (PaintingContext context, Offset offset) { _state.paintValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.valueIndicatorShape.paint( if (attached) {
context, _sliderTheme.valueIndicatorShape.paint(
offset + thumbCenter, context,
activationAnimation: _valueIndicatorAnimation, offset + thumbCenter,
enableAnimation: _enableAnimation, activationAnimation: _valueIndicatorAnimation,
isDiscrete: isDiscrete, enableAnimation: _enableAnimation,
labelPainter: _labelPainter, isDiscrete: isDiscrete,
parentBox: this, labelPainter: _labelPainter,
sliderTheme: _sliderTheme, parentBox: this,
textDirection: _textDirection, sliderTheme: _sliderTheme,
value: _value, textDirection: _textDirection,
textScaleFactor: textScaleFactor, value: _value,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize, textScaleFactor: textScaleFactor,
); sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
);
}
}; };
} }
} }
......
...@@ -2791,6 +2791,7 @@ class _RectangularSliderValueIndicatorPathPainter { ...@@ -2791,6 +2791,7 @@ class _RectangularSliderValueIndicatorPathPainter {
double scale, double scale,
}) { }) {
assert(!sizeWithOverflow.isEmpty); assert(!sizeWithOverflow.isEmpty);
const double edgePadding = 8.0; const double edgePadding = 8.0;
final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor); final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
/// Value indicator draws on the Overlay and by using the global Offset /// Value indicator draws on the Overlay and by using the global Offset
......
...@@ -1338,6 +1338,101 @@ void main() { ...@@ -1338,6 +1338,101 @@ void main() {
}); });
testWidgets('Range Slider removes value indicator from overlay if Slider gets disposed without value indicator animation completing.', (WidgetTester tester) async {
final ThemeData theme = _buildTheme();
final SliderThemeData sliderTheme = theme.sliderTheme;
RangeValues values = const RangeValues(0.5, 0.75);
Widget buildApp({
Color activeColor,
Color inactiveColor,
int divisions,
bool enabled = true,
}) {
final ValueChanged<RangeValues> onChanged = (RangeValues newValues) {
values = newValues;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Navigator(onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(builder: (BuildContext context) {
return Column(
children: <Widget>[
Theme(
data: theme,
child: RangeSlider(
values: values,
labels: RangeLabels(values.start.toStringAsFixed(2),
values.end.toStringAsFixed(2)),
divisions: divisions,
onChanged: onChanged,
),
),
RaisedButton(
child: const Text('Next'),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return RaisedButton(
child: const Text('Inner page'),
onPressed: () => Navigator.of(context).pop(),
);
},
),
);
},
),
],
);
});
}),
),
),
);
}
await tester.pumpWidget(buildApp(divisions: 3));
/// The value indicator is added to the overlay when it is clicked or dragged.
/// Because both of these gestures are occurring then it adds same value indicator
/// twice into the overlay.
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
final Offset topRight = tester.getTopRight(find.byType(RangeSlider)).translate(-24, 0);
final TestGesture gesture = await tester.startGesture(topRight);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(find.byType(RangeSlider), isNotNull);
expect(
valueIndicatorBox,
paints
..rrect(color: sliderTheme.inactiveTrackColor)
..rect(color: sliderTheme.activeTrackColor)
..rrect(color: sliderTheme.inactiveTrackColor),
);
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.byType(RangeSlider), findsNothing);
expect(
valueIndicatorBox,
isNot(
paints
..rrect(color: sliderTheme.inactiveTrackColor)
..rect(color: sliderTheme.activeTrackColor)
..rrect(color: sliderTheme.inactiveTrackColor)
),
);
// Don't stop holding the value indicator.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Range Slider top thumb gets stroked when overlapping', (WidgetTester tester) async { testWidgets('Range Slider top thumb gets stroked when overlapping', (WidgetTester tester) async {
RangeValues values = const RangeValues(0.3, 0.7); RangeValues values = const RangeValues(0.3, 0.7);
......
...@@ -1949,6 +1949,96 @@ void main() { ...@@ -1949,6 +1949,96 @@ void main() {
await gesture.up(); await gesture.up();
}); });
testWidgets('Slider removes value indicator from overlay if Slider gets disposed without value indicator animation completing.', (WidgetTester tester) async {
final Key sliderKey = UniqueKey();
double value = 0.0;
Widget buildApp({
Color activeColor,
Color inactiveColor,
int divisions,
bool enabled = true,
}) {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Navigator(onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(builder: (BuildContext context) {
return Column(
children: <Widget>[
Slider(
key: sliderKey,
min: 0.0,
max: 100.0,
divisions: divisions,
label: '${value.round()}',
value: value,
onChanged: (double newValue) {
value = newValue;
},
),
RaisedButton(
child: const Text('Next'),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return RaisedButton(
child: const Text('Inner page'),
onPressed: () => Navigator.of(context).pop(),
);
},
),
);
},
),
],
);
});
}),
),
),
);
}
await tester.pumpWidget(buildApp(divisions: 3));
/// The value indicator is added to the overlay when it is clicked or dragged.
/// Because both of these gestures are occurring then it adds same value indicator
/// twice into the overlay.
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
final Offset topRight = tester.getTopRight(find.byType(Slider)).translate(-24, 0);
final TestGesture gesture = await tester.startGesture(topRight);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(find.byType(Slider), isNotNull);
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xff2196f3)) // Active track.
..rrect(color: const Color(0x3d2196f3)), // Inactive track.
);
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.byType(Slider), findsNothing);
expect(
valueIndicatorBox,
isNot(
paints
..rrect(color: const Color(0xff2196f3)) // Active track.
..rrect(color: const Color(0x3d2196f3)) // Inactive track.
),
);
// Don't stop holding the value indicator.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Slider.adaptive', (WidgetTester tester) async { testWidgets('Slider.adaptive', (WidgetTester tester) async {
double value = 0.5; double value = 0.5;
......
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