mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Slider value indicator gets disposed if it is activated (#57535)
This commit is contained in:
parent
8443f7cfd3
commit
bd06749edc
@ -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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
}
|
}
|
||||||
_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
|
|||||||
|
|
||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
_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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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() {
|
|||||||
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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user