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

This commit is contained in:
Jose Alba 2020-05-27 18:27:01 -04:00 committed by GitHub
parent 8443f7cfd3
commit bd06749edc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 263 additions and 47 deletions

View file

@ -472,6 +472,10 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
enableController.dispose();
startPositionController.dispose();
endPositionController.dispose();
if (overlayEntry != null) {
overlayEntry.remove();
overlayEntry = null;
}
super.dispose();
}
@ -1154,6 +1158,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
void _handleDragUpdate(DragUpdateDetails details) {
if (!_state.mounted) {
return;
}
final double dragValue = _getValueFromGlobalPosition(details.globalPosition);
// If no selection has been made yet, test for thumb selection again now
@ -1190,7 +1198,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
void _endInteraction() {
_state.overlayController.reverse();
if (!_state.mounted) {
return;
}
if (showValueIndicator && _state.interactionTimer == null) {
_state.valueIndicatorController.reverse();
}
@ -1202,6 +1213,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
_active = false;
}
_state.overlayController.reverse();
}
void _handleDragStart(DragStartDetails details) {
@ -1388,22 +1400,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
if (shouldPaintValueIndicators) {
_state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
bottomThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: false,
labelPainter: bottomLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: bottomThumb,
value: bottomValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
if (attached) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
bottomThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: false,
labelPainter: bottomLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: bottomThumb,
value: bottomValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
}
};
}
@ -1462,22 +1476,24 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
_state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
topThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: thumbDelta < innerOverflow,
labelPainter: topLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: topThumb,
value: topValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
if (attached) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
topThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: thumbDelta < innerOverflow,
labelPainter: topLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: topThumb,
value: topValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
}
};
}

View file

@ -512,6 +512,10 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
valueIndicatorController.dispose();
enableController.dispose();
positionController.dispose();
if (overlayEntry != null) {
overlayEntry.remove();
overlayEntry = null;
}
super.dispose();
}
@ -1238,6 +1242,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
void _endInteraction() {
if (!_state.mounted) {
return;
}
if (_active && _state.mounted) {
if (onChangeEnd != null) {
onChangeEnd(_discretize(_currentDragValue));
@ -1255,6 +1263,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition);
void _handleDragUpdate(DragUpdateDetails details) {
if (!_state.mounted) {
return;
}
if (isInteractive) {
final double valueDelta = details.primaryDelta / _trackRect.width;
switch (textDirection) {
@ -1396,20 +1408,22 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
if (showValueIndicator) {
_state.paintValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.valueIndicatorShape.paint(
context,
offset + thumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
value: _value,
textScaleFactor: textScaleFactor,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
);
if (attached) {
_sliderTheme.valueIndicatorShape.paint(
context,
offset + thumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
value: _value,
textScaleFactor: textScaleFactor,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
);
}
};
}
}

View file

@ -2791,6 +2791,7 @@ class _RectangularSliderValueIndicatorPathPainter {
double scale,
}) {
assert(!sizeWithOverflow.isEmpty);
const double edgePadding = 8.0;
final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
/// Value indicator draws on the Overlay and by using the global Offset

View file

@ -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 {
RangeValues values = const RangeValues(0.3, 0.7);

View file

@ -1949,6 +1949,96 @@ void main() {
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 {
double value = 0.5;