Skip to content

Commit bd06749

Browse files
authored
Slider value indicator gets disposed if it is activated (flutter#57535)
1 parent 8443f7c commit bd06749

File tree

5 files changed

+263
-47
lines changed

5 files changed

+263
-47
lines changed

packages/flutter/lib/src/material/range_slider.dart

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,10 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
472472
enableController.dispose();
473473
startPositionController.dispose();
474474
endPositionController.dispose();
475+
if (overlayEntry != null) {
476+
overlayEntry.remove();
477+
overlayEntry = null;
478+
}
475479
super.dispose();
476480
}
477481

@@ -1154,6 +1158,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
11541158
}
11551159

11561160
void _handleDragUpdate(DragUpdateDetails details) {
1161+
if (!_state.mounted) {
1162+
return;
1163+
}
1164+
11571165
final double dragValue = _getValueFromGlobalPosition(details.globalPosition);
11581166

11591167
// If no selection has been made yet, test for thumb selection again now
@@ -1190,7 +1198,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
11901198
}
11911199

11921200
void _endInteraction() {
1193-
_state.overlayController.reverse();
1201+
if (!_state.mounted) {
1202+
return;
1203+
}
1204+
11941205
if (showValueIndicator && _state.interactionTimer == null) {
11951206
_state.valueIndicatorController.reverse();
11961207
}
@@ -1202,6 +1213,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
12021213
}
12031214
_active = false;
12041215
}
1216+
_state.overlayController.reverse();
12051217
}
12061218

12071219
void _handleDragStart(DragStartDetails details) {
@@ -1388,22 +1400,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
13881400

13891401
if (shouldPaintValueIndicators) {
13901402
_state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
1391-
_sliderTheme.rangeValueIndicatorShape.paint(
1392-
context,
1393-
bottomThumbCenter,
1394-
activationAnimation: _valueIndicatorAnimation,
1395-
enableAnimation: _enableAnimation,
1396-
isDiscrete: isDiscrete,
1397-
isOnTop: false,
1398-
labelPainter: bottomLabelPainter,
1399-
parentBox: this,
1400-
sliderTheme: _sliderTheme,
1401-
textDirection: _textDirection,
1402-
thumb: bottomThumb,
1403-
value: bottomValue,
1404-
textScaleFactor: textScaleFactor,
1405-
sizeWithOverflow: resolvedscreenSize,
1406-
);
1403+
if (attached) {
1404+
_sliderTheme.rangeValueIndicatorShape.paint(
1405+
context,
1406+
bottomThumbCenter,
1407+
activationAnimation: _valueIndicatorAnimation,
1408+
enableAnimation: _enableAnimation,
1409+
isDiscrete: isDiscrete,
1410+
isOnTop: false,
1411+
labelPainter: bottomLabelPainter,
1412+
parentBox: this,
1413+
sliderTheme: _sliderTheme,
1414+
textDirection: _textDirection,
1415+
thumb: bottomThumb,
1416+
value: bottomValue,
1417+
textScaleFactor: textScaleFactor,
1418+
sizeWithOverflow: resolvedscreenSize,
1419+
);
1420+
}
14071421
};
14081422
}
14091423

@@ -1462,22 +1476,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
14621476
}
14631477

14641478
_state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
1465-
_sliderTheme.rangeValueIndicatorShape.paint(
1466-
context,
1467-
topThumbCenter,
1468-
activationAnimation: _valueIndicatorAnimation,
1469-
enableAnimation: _enableAnimation,
1470-
isDiscrete: isDiscrete,
1471-
isOnTop: thumbDelta < innerOverflow,
1472-
labelPainter: topLabelPainter,
1473-
parentBox: this,
1474-
sliderTheme: _sliderTheme,
1475-
textDirection: _textDirection,
1476-
thumb: topThumb,
1477-
value: topValue,
1478-
textScaleFactor: textScaleFactor,
1479-
sizeWithOverflow: resolvedscreenSize,
1480-
);
1479+
if (attached) {
1480+
_sliderTheme.rangeValueIndicatorShape.paint(
1481+
context,
1482+
topThumbCenter,
1483+
activationAnimation: _valueIndicatorAnimation,
1484+
enableAnimation: _enableAnimation,
1485+
isDiscrete: isDiscrete,
1486+
isOnTop: thumbDelta < innerOverflow,
1487+
labelPainter: topLabelPainter,
1488+
parentBox: this,
1489+
sliderTheme: _sliderTheme,
1490+
textDirection: _textDirection,
1491+
thumb: topThumb,
1492+
value: topValue,
1493+
textScaleFactor: textScaleFactor,
1494+
sizeWithOverflow: resolvedscreenSize,
1495+
);
1496+
}
14811497
};
14821498
}
14831499

packages/flutter/lib/src/material/slider.dart

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,10 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
512512
valueIndicatorController.dispose();
513513
enableController.dispose();
514514
positionController.dispose();
515+
if (overlayEntry != null) {
516+
overlayEntry.remove();
517+
overlayEntry = null;
518+
}
515519
super.dispose();
516520
}
517521

@@ -1238,6 +1242,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
12381242
}
12391243

12401244
void _endInteraction() {
1245+
if (!_state.mounted) {
1246+
return;
1247+
}
1248+
12411249
if (_active && _state.mounted) {
12421250
if (onChangeEnd != null) {
12431251
onChangeEnd(_discretize(_currentDragValue));
@@ -1255,6 +1263,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
12551263
void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition);
12561264

12571265
void _handleDragUpdate(DragUpdateDetails details) {
1266+
if (!_state.mounted) {
1267+
return;
1268+
}
1269+
12581270
if (isInteractive) {
12591271
final double valueDelta = details.primaryDelta / _trackRect.width;
12601272
switch (textDirection) {
@@ -1396,20 +1408,22 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
13961408
if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
13971409
if (showValueIndicator) {
13981410
_state.paintValueIndicator = (PaintingContext context, Offset offset) {
1399-
_sliderTheme.valueIndicatorShape.paint(
1400-
context,
1401-
offset + thumbCenter,
1402-
activationAnimation: _valueIndicatorAnimation,
1403-
enableAnimation: _enableAnimation,
1404-
isDiscrete: isDiscrete,
1405-
labelPainter: _labelPainter,
1406-
parentBox: this,
1407-
sliderTheme: _sliderTheme,
1408-
textDirection: _textDirection,
1409-
value: _value,
1410-
textScaleFactor: textScaleFactor,
1411-
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
1412-
);
1411+
if (attached) {
1412+
_sliderTheme.valueIndicatorShape.paint(
1413+
context,
1414+
offset + thumbCenter,
1415+
activationAnimation: _valueIndicatorAnimation,
1416+
enableAnimation: _enableAnimation,
1417+
isDiscrete: isDiscrete,
1418+
labelPainter: _labelPainter,
1419+
parentBox: this,
1420+
sliderTheme: _sliderTheme,
1421+
textDirection: _textDirection,
1422+
value: _value,
1423+
textScaleFactor: textScaleFactor,
1424+
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
1425+
);
1426+
}
14131427
};
14141428
}
14151429
}

packages/flutter/lib/src/material/slider_theme.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,7 @@ class _RectangularSliderValueIndicatorPathPainter {
27912791
double scale,
27922792
}) {
27932793
assert(!sizeWithOverflow.isEmpty);
2794+
27942795
const double edgePadding = 8.0;
27952796
final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
27962797
/// Value indicator draws on the Overlay and by using the global Offset

packages/flutter/test/material/range_slider_test.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,101 @@ void main() {
13381338

13391339
});
13401340

1341+
testWidgets('Range Slider removes value indicator from overlay if Slider gets disposed without value indicator animation completing.', (WidgetTester tester) async {
1342+
final ThemeData theme = _buildTheme();
1343+
final SliderThemeData sliderTheme = theme.sliderTheme;
1344+
RangeValues values = const RangeValues(0.5, 0.75);
1345+
1346+
Widget buildApp({
1347+
Color activeColor,
1348+
Color inactiveColor,
1349+
int divisions,
1350+
bool enabled = true,
1351+
}) {
1352+
final ValueChanged<RangeValues> onChanged = (RangeValues newValues) {
1353+
values = newValues;
1354+
};
1355+
return MaterialApp(
1356+
home: Directionality(
1357+
textDirection: TextDirection.ltr,
1358+
child: Material(
1359+
child: Navigator(onGenerateRoute: (RouteSettings settings) {
1360+
return MaterialPageRoute<void>(builder: (BuildContext context) {
1361+
return Column(
1362+
children: <Widget>[
1363+
Theme(
1364+
data: theme,
1365+
child: RangeSlider(
1366+
values: values,
1367+
labels: RangeLabels(values.start.toStringAsFixed(2),
1368+
values.end.toStringAsFixed(2)),
1369+
divisions: divisions,
1370+
onChanged: onChanged,
1371+
),
1372+
),
1373+
RaisedButton(
1374+
child: const Text('Next'),
1375+
onPressed: () {
1376+
Navigator.of(context).pushReplacement(
1377+
MaterialPageRoute<void>(
1378+
builder: (BuildContext context) {
1379+
return RaisedButton(
1380+
child: const Text('Inner page'),
1381+
onPressed: () => Navigator.of(context).pop(),
1382+
);
1383+
},
1384+
),
1385+
);
1386+
},
1387+
),
1388+
],
1389+
);
1390+
});
1391+
}),
1392+
),
1393+
),
1394+
);
1395+
}
1396+
1397+
await tester.pumpWidget(buildApp(divisions: 3));
1398+
1399+
/// The value indicator is added to the overlay when it is clicked or dragged.
1400+
/// Because both of these gestures are occurring then it adds same value indicator
1401+
/// twice into the overlay.
1402+
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
1403+
final Offset topRight = tester.getTopRight(find.byType(RangeSlider)).translate(-24, 0);
1404+
final TestGesture gesture = await tester.startGesture(topRight);
1405+
// Wait for value indicator animation to finish.
1406+
await tester.pumpAndSettle();
1407+
1408+
expect(find.byType(RangeSlider), isNotNull);
1409+
expect(
1410+
valueIndicatorBox,
1411+
paints
1412+
..rrect(color: sliderTheme.inactiveTrackColor)
1413+
..rect(color: sliderTheme.activeTrackColor)
1414+
..rrect(color: sliderTheme.inactiveTrackColor),
1415+
);
1416+
1417+
await tester.tap(find.text('Next'));
1418+
await tester.pumpAndSettle();
1419+
1420+
expect(find.byType(RangeSlider), findsNothing);
1421+
expect(
1422+
valueIndicatorBox,
1423+
isNot(
1424+
paints
1425+
..rrect(color: sliderTheme.inactiveTrackColor)
1426+
..rect(color: sliderTheme.activeTrackColor)
1427+
..rrect(color: sliderTheme.inactiveTrackColor)
1428+
),
1429+
);
1430+
1431+
// Don't stop holding the value indicator.
1432+
await gesture.up();
1433+
await tester.pumpAndSettle();
1434+
});
1435+
13411436
testWidgets('Range Slider top thumb gets stroked when overlapping', (WidgetTester tester) async {
13421437
RangeValues values = const RangeValues(0.3, 0.7);
13431438

0 commit comments

Comments
 (0)