Add slider minimum interaction time (#15358)
This change makes the discrete slider show the value indicator for a minimum amount of time for any interaction (tap, drag).
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index c53cb77..e5d780b 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter/widgets.dart';
import 'constants.dart';
@@ -212,41 +214,55 @@
class _SliderState extends State<Slider> with TickerProviderStateMixin {
static const Duration enableAnimationDuration = const Duration(milliseconds: 75);
- static const Duration positionAnimationDuration = const Duration(milliseconds: 75);
+ static const Duration valueIndicatorAnimationDuration = const Duration(milliseconds: 100);
- // Animation controller that is run when interactions occur (taps, drags,
- // etc.).
- AnimationController reactionController;
+ // Animation controller that is run when the overlay (a.k.a radial reaction)
+ // is shown in response to user interaction.
+ AnimationController overlayController;
+ // Animation controller that is run when the value indicator is being shown
+ // or hidden.
+ AnimationController valueIndicatorController;
// Animation controller that is run when enabling/disabling the slider.
AnimationController enableController;
// Animation controller that is run when transitioning between one value
// and the next on a discrete slider.
AnimationController positionController;
+ Timer interactionTimer;
@override
void initState() {
super.initState();
- reactionController = new AnimationController(
+ overlayController = new AnimationController(
duration: kRadialReactionDuration,
vsync: this,
);
+ valueIndicatorController = new AnimationController(
+ duration: valueIndicatorAnimationDuration,
+ vsync: this,
+ );
enableController = new AnimationController(
duration: enableAnimationDuration,
vsync: this,
);
positionController = new AnimationController(
- duration: positionAnimationDuration,
+ duration: Duration.zero,
vsync: this,
);
+ // Create timer in a cancelled state, so that we don't have to
+ // check for null below.
+ interactionTimer = new Timer(Duration.zero, () {});
+ interactionTimer.cancel();
enableController.value = widget.onChanged != null ? 1.0 : 0.0;
- positionController.value = widget.value;
+ positionController.value = _unlerp(widget.value);
}
@override
void dispose() {
- reactionController.dispose();
+ overlayController.dispose();
+ valueIndicatorController.dispose();
enableController.dispose();
positionController.dispose();
+ interactionTimer?.cancel();
super.dispose();
}
@@ -358,15 +374,6 @@
}
}
-const double _overlayRadius = 16.0;
-const double _overlayDiameter = _overlayRadius * 2.0;
-const double _railHeight = 2.0;
-const double _preferredRailWidth = 144.0;
-const double _preferredTotalWidth = _preferredRailWidth + _overlayDiameter;
-
-const double _adjustmentUnit = 0.1; // Matches iOS implementation of material slider.
-final Tween<double> _overlayRadiusTween = new Tween<double>(begin: 0.0, end: _overlayRadius);
-
class _RenderSlider extends RenderBox {
_RenderSlider({
@required double value,
@@ -403,8 +410,12 @@
..onTapDown = _handleTapDown
..onTapUp = _handleTapUp
..onTapCancel = _endInteraction;
- _reaction = new CurvedAnimation(
- parent: _state.reactionController,
+ _overlayAnimation = new CurvedAnimation(
+ parent: _state.overlayController,
+ curve: Curves.fastOutSlowIn,
+ );
+ _valueIndicatorAnimation = new CurvedAnimation(
+ parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn,
);
_enableAnimation = new CurvedAnimation(
@@ -413,11 +424,34 @@
);
}
- double get value => _value;
- double _value;
+ static const Duration _positionAnimationDuration = const Duration(milliseconds: 75);
+ static const double _overlayRadius = 16.0;
+ static const double _overlayDiameter = _overlayRadius * 2.0;
+ static const double _railHeight = 2.0;
+ static const double _preferredRailWidth = 144.0;
+ static const double _preferredTotalWidth = _preferredRailWidth + _overlayDiameter;
+ static const Duration _minimumInteractionTime = const Duration(milliseconds: 500);
+ static const double _adjustmentUnit = 0.1; // Matches iOS implementation of material slider.
+ static final Tween<double> _overlayRadiusTween = new Tween<double>(begin: 0.0, end: _overlayRadius);
_SliderState _state;
+ Animation<double> _overlayAnimation;
+ Animation<double> _valueIndicatorAnimation;
+ Animation<double> _enableAnimation;
+ final TextPainter _labelPainter = new TextPainter();
+ HorizontalDragGestureRecognizer _drag;
+ TapGestureRecognizer _tap;
+ bool _active = false;
+ double _currentDragValue = 0.0;
+ double get _railLength => size.width - _overlayDiameter;
+
+ bool get isInteractive => onChanged != null;
+
+ bool get isDiscrete => divisions != null && divisions > 0;
+
+ double get value => _value;
+ double _value;
set value(double newValue) {
assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
final double convertedValue = isDiscrete ? _discretize(newValue) : newValue;
@@ -426,6 +460,14 @@
}
_value = convertedValue;
if (isDiscrete) {
+ // Reset the duration to match the distance that we're traveling, so that
+ // whatever the distance, we still do it in _positionAnimationDuration,
+ // and if we get re-targeted in the middle, it still takes that long to
+ // get to the new location.
+ final double distance = (_value - _state.positionController.value).abs();
+ _state.positionController.duration = distance != 0.0
+ ? _positionAnimationDuration * (1.0 / distance)
+ : 0.0;
_state.positionController.animateTo(convertedValue, curve: Curves.easeInOut);
} else {
_state.positionController.value = convertedValue;
@@ -514,6 +556,25 @@
_updateLabelPainter();
}
+ bool get showValueIndicator {
+ bool showValueIndicator;
+ switch (_sliderTheme.showValueIndicator) {
+ case ShowValueIndicator.onlyForDiscrete:
+ showValueIndicator = isDiscrete;
+ break;
+ case ShowValueIndicator.onlyForContinuous:
+ showValueIndicator = !isDiscrete;
+ break;
+ case ShowValueIndicator.always:
+ showValueIndicator = true;
+ break;
+ case ShowValueIndicator.never:
+ showValueIndicator = false;
+ break;
+ }
+ return showValueIndicator;
+ }
+
void _updateLabelPainter() {
if (label != null) {
_labelPainter
@@ -530,31 +591,19 @@
markNeedsLayout();
}
- double get _railLength => size.width - _overlayDiameter;
-
- Animation<double> _reaction;
- Animation<double> _enableAnimation;
- final TextPainter _labelPainter = new TextPainter();
- HorizontalDragGestureRecognizer _drag;
- TapGestureRecognizer _tap;
- bool _active = false;
- double _currentDragValue = 0.0;
-
- bool get isInteractive => onChanged != null;
-
- bool get isDiscrete => divisions != null && divisions > 0;
-
@override
void attach(PipelineOwner owner) {
super.attach(owner);
- _reaction.addListener(markNeedsPaint);
+ _overlayAnimation.addListener(markNeedsPaint);
+ _valueIndicatorAnimation.addListener(markNeedsPaint);
_enableAnimation.addListener(markNeedsPaint);
_state.positionController.addListener(markNeedsPaint);
}
@override
void detach() {
- _reaction.removeListener(markNeedsPaint);
+ _overlayAnimation.removeListener(markNeedsPaint);
+ _valueIndicatorAnimation.removeListener(markNeedsPaint);
_enableAnimation.removeListener(markNeedsPaint);
_state.positionController.removeListener(markNeedsPaint);
super.detach();
@@ -588,7 +637,19 @@
_active = true;
_currentDragValue = _getValueFromGlobalPosition(globalPosition);
onChanged(_discretize(_currentDragValue));
- _state.reactionController.forward();
+ _state.overlayController.forward();
+ if (showValueIndicator) {
+ _state.valueIndicatorController.forward();
+ if (_state.interactionTimer.isActive) {
+ _state.interactionTimer.cancel();
+ }
+ _state.interactionTimer = new Timer(_minimumInteractionTime * timeDilation, () {
+ if (!_active && _state.valueIndicatorController.status == AnimationStatus.completed) {
+ _state.valueIndicatorController.reverse();
+ }
+ _state.interactionTimer.cancel();
+ });
+ }
}
}
@@ -596,7 +657,10 @@
if (_active) {
_active = false;
_currentDragValue = 0.0;
- _state.reactionController.reverse();
+ _state.overlayController.reverse();
+ if (showValueIndicator && !_state.interactionTimer.isActive) {
+ _state.valueIndicatorController.reverse();
+ }
}
}
@@ -696,15 +760,15 @@
}
void _paintOverlay(Canvas canvas, Offset center) {
- if (!_reaction.isDismissed) {
+ if (!_overlayAnimation.isDismissed) {
// TODO(gspencer) : We don't really follow the spec here for overlays.
// The spec says to use 16% opacity for drawing over light material,
// and 32% for colored material, but we don't really have a way to
// know what the underlying color is, so there's no easy way to
// implement this. Choosing the "light" version for now.
- final Paint reactionPaint = new Paint()..color = _sliderTheme.overlayColor;
- final double radius = _overlayRadiusTween.evaluate(_reaction);
- canvas.drawCircle(center, radius, reactionPaint);
+ final Paint overlayPaint = new Paint()..color = _sliderTheme.overlayColor;
+ final double radius = _overlayRadiusTween.evaluate(_overlayAnimation);
+ canvas.drawCircle(center, radius, overlayPaint);
}
}
@@ -781,29 +845,15 @@
rightTickMarkPaint,
);
- if (isInteractive && _reaction.status != AnimationStatus.dismissed && label != null) {
- bool showValueIndicator;
- switch (_sliderTheme.showValueIndicator) {
- case ShowValueIndicator.onlyForDiscrete:
- showValueIndicator = isDiscrete;
- break;
- case ShowValueIndicator.onlyForContinuous:
- showValueIndicator = !isDiscrete;
- break;
- case ShowValueIndicator.always:
- showValueIndicator = true;
- break;
- case ShowValueIndicator.never:
- showValueIndicator = false;
- break;
- }
+ if (isInteractive && label != null &&
+ _valueIndicatorAnimation.status != AnimationStatus.dismissed) {
if (showValueIndicator) {
_sliderTheme.valueIndicatorShape.paint(
this,
context,
isDiscrete,
thumbCenter,
- _reaction,
+ _valueIndicatorAnimation,
_enableAnimation,
_labelPainter,
_sliderTheme,
@@ -818,7 +868,7 @@
context,
isDiscrete,
thumbCenter,
- _reaction,
+ _overlayAnimation,
_enableAnimation,
label != null ? _labelPainter : null,
_sliderTheme,
diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart
index 1603e5b..9983db2 100644
--- a/packages/flutter/lib/src/material/slider_theme.dart
+++ b/packages/flutter/lib/src/material/slider_theme.dart
@@ -469,19 +469,19 @@
);
description.add(new DiagnosticsProperty<Color>('activeRailColor', activeRailColor, defaultValue: defaultData.activeRailColor));
description.add(new DiagnosticsProperty<Color>('inactiveRailColor', inactiveRailColor, defaultValue: defaultData.inactiveRailColor));
- description.add(new DiagnosticsProperty<Color>('disabledActiveRailColor', disabledActiveRailColor, defaultValue: defaultData.disabledActiveRailColor));
- description.add(new DiagnosticsProperty<Color>('disabledInactiveRailColor', disabledInactiveRailColor, defaultValue: defaultData.disabledInactiveRailColor));
- description.add(new DiagnosticsProperty<Color>('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor));
- description.add(new DiagnosticsProperty<Color>('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor));
- description.add(new DiagnosticsProperty<Color>('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor));
- description.add(new DiagnosticsProperty<Color>('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor));
+ description.add(new DiagnosticsProperty<Color>('disabledActiveRailColor', disabledActiveRailColor, defaultValue: defaultData.disabledActiveRailColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('disabledInactiveRailColor', disabledInactiveRailColor, defaultValue: defaultData.disabledInactiveRailColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor, level: DiagnosticLevel.debug));
description.add(new DiagnosticsProperty<Color>('thumbColor', thumbColor, defaultValue: defaultData.thumbColor));
- description.add(new DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor));
- description.add(new DiagnosticsProperty<Color>('overlayColor', overlayColor, defaultValue: defaultData.overlayColor));
+ description.add(new DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<Color>('overlayColor', overlayColor, defaultValue: defaultData.overlayColor, level: DiagnosticLevel.debug));
description.add(new DiagnosticsProperty<Color>('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor));
- description.add(new DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape));
- description.add(new DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape));
- description.add(new DiagnosticsProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator));
+ description.add(new DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape, level: DiagnosticLevel.debug));
+ description.add(new DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape, level: DiagnosticLevel.debug));
+ description.add(new EnumProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator));
}
}
@@ -595,8 +595,8 @@
// Radius of the top lobe of the value indicator.
static const double _topLobeRadius = 16.0;
- // Baseline size of the label text. This is the size that the value indicator
- // was designed to contain. We scale if from here to fit other sizes.
+ // Designed size of the label text. This is the size that the value indicator
+ // was designed to contain. We scale it from here to fit other sizes.
static const double _labelTextDesignSize = 14.0;
// Radius of the bottom lobe of the value indicator.
static const double _bottomLobeRadius = 6.0;
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 670f301..a005bb0 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -177,6 +177,168 @@
expect(updates, equals(1));
});
+ testWidgets('Value indicator shows for a bit after being tapped', (WidgetTester tester) async {
+ final Key sliderKey = new UniqueKey();
+ double value = 0.0;
+
+ await tester.pumpWidget(
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return new MediaQuery(
+ data: new MediaQueryData.fromWindow(window),
+ child: new Material(
+ child: new Center(
+ child: new Slider(
+ key: sliderKey,
+ value: value,
+ divisions: 4,
+ onChanged: (double newValue) {
+ setState(() {
+ value = newValue;
+ });
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+
+ expect(value, equals(0.0));
+ await tester.tap(find.byKey(sliderKey));
+ expect(value, equals(0.5));
+ await tester.pump(const Duration(milliseconds: 100));
+ // Starts with the position animation and value indicator
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
+ await tester.pump(const Duration(milliseconds: 100));
+ // Value indicator is longer than position.
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
+ await tester.pump(const Duration(milliseconds: 100));
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
+ await tester.pump(const Duration(milliseconds: 100));
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
+ await tester.pump(const Duration(milliseconds: 100));
+ // Shown for long enough, value indicator is animated closed.
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
+ await tester.pump(const Duration(milliseconds: 101));
+ expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
+ });
+
+ testWidgets('Discrete Slider repaints and animates when dragged', (WidgetTester tester) async {
+ final Key sliderKey = new UniqueKey();
+ double value = 0.0;
+ final List<Offset> log = <Offset>[];
+ final LoggingThumbShape loggingThumb = new LoggingThumbShape(log);
+ await tester.pumpWidget(
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ final SliderThemeData sliderTheme = SliderTheme.of(context).copyWith(thumbShape: loggingThumb);
+ return new MediaQuery(
+ data: new MediaQueryData.fromWindow(window),
+ child: new Material(
+ child: new Center(
+ child: new SliderTheme(
+ data: sliderTheme,
+ child: new Slider(
+ key: sliderKey,
+ value: value,
+ divisions: 4,
+ onChanged: (double newValue) {
+ setState(() {
+ value = newValue;
+ });
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+
+ final List<Offset> expectedLog = <Offset>[
+ const Offset(16.0, 300.0),
+ const Offset(16.0, 300.0),
+ const Offset(400.0, 300.0),
+ ];
+ final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 100));
+ expect(value, equals(0.5));
+ expect(log.length, 3);
+ expect(log, orderedEquals(expectedLog));
+ await gesture.moveBy(const Offset(-500.0, 0.0));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 10));
+ expect(value, equals(0.0));
+ expect(log.length, 5);
+ expect(log.last.dx, closeTo(386.3, 0.1));
+ // With no more gesture or value changes, the thumb position should still
+ // be redrawn in the animated position.
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 10));
+ expect(value, equals(0.0));
+ expect(log.length, 7);
+ expect(log.last.dx, closeTo(343.3, 0.1));
+ // Final position.
+ await tester.pump(const Duration(milliseconds: 80));
+ expectedLog.add(const Offset(16.0, 300.0));
+ expect(value, equals(0.0));
+ expect(log.length, 8);
+ expect(log.last.dx, closeTo(16.0, 0.1));
+ await gesture.up();
+ });
+
+ testWidgets("Slider doesn't send duplicate change events if tapped on the same value", (WidgetTester tester) async {
+ final Key sliderKey = new UniqueKey();
+ double value = 0.0;
+ int updates = 0;
+
+ await tester.pumpWidget(
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return new MediaQuery(
+ data: new MediaQueryData.fromWindow(window),
+ child: new Material(
+ child: new Center(
+ child: new Slider(
+ key: sliderKey,
+ value: value,
+ onChanged: (double newValue) {
+ setState(() {
+ updates++;
+ value = newValue;
+ });
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+
+ expect(value, equals(0.0));
+ await tester.tap(find.byKey(sliderKey));
+ expect(value, equals(0.5));
+ await tester.pump();
+ await tester.tap(find.byKey(sliderKey));
+ expect(value, equals(0.5));
+ await tester.pump();
+ expect(updates, equals(1));
+ });
+
testWidgets('discrete Slider repaints when dragged', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey();
double value = 0.0;
@@ -229,14 +391,14 @@
await tester.pump(const Duration(milliseconds: 10));
expect(value, equals(0.0));
expect(log.length, 5);
- expect(log.last.dx, closeTo(343.3, 0.1));
+ expect(log.last.dx, closeTo(386.3, 0.1));
// With no more gesture or value changes, the thumb position should still
// be redrawn in the animated position.
await tester.pump();
await tester.pump(const Duration(milliseconds: 10));
expect(value, equals(0.0));
expect(log.length, 7);
- expect(log.last.dx, closeTo(185.5, 0.1));
+ expect(log.last.dx, closeTo(343.3, 0.1));
// Final position.
await tester.pump(const Duration(milliseconds: 80));
expectedLog.add(const Offset(16.0, 300.0));
@@ -443,7 +605,11 @@
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
- await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, divisions: 3));
+ await tester.pumpWidget(buildApp(
+ activeColor: customColor1,
+ inactiveColor: customColor2,
+ divisions: 3,
+ ));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
expect(
sliderBox,
@@ -462,7 +628,7 @@
// Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false));
- await tester.pump(const Duration(seconds: 1)); // wait for disable animation to finish.
+ await tester.pumpAndSettle();
expect(
sliderBox,
paints
@@ -489,9 +655,8 @@
await tester.pumpWidget(buildApp(divisions: 3));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
- await tester.pump();
// Wait for value indicator animation to finish.
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
sliderBox,
@@ -507,9 +672,8 @@
..circle(color: sliderTheme.thumbColor),
);
await gesture.up();
- await tester.pump();
// Wait for value indicator animation to finish.
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
// Testing the custom colors are used for the indicator.
await tester.pumpWidget(buildApp(
@@ -519,9 +683,8 @@
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
- await tester.pump();
// Wait for value indicator animation to finish.
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
sliderBox,
@@ -734,24 +897,22 @@
await tester.pumpWidget(buildSlider(textScaleFactor: 1.0));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 1.0, y: 1.0));
await gesture.up();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
await tester.pumpWidget(buildSlider(textScaleFactor: 2.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 2.0, y: 2.0));
await gesture.up();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
// Check continuous
await tester.pumpWidget(buildSlider(
@@ -761,13 +922,12 @@
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 1.0, y: 1.0));
await gesture.up();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
await tester.pumpWidget(buildSlider(
textScaleFactor: 2.0,
@@ -776,13 +936,12 @@
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 2.0, y: 2.0));
await gesture.up();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
});
testWidgets('Slider has correct animations when reparented', (WidgetTester tester) async {
@@ -830,7 +989,7 @@
TestGesture gesture = await tester.startGesture(Offset.zero);
await tester.pump();
await gesture.up();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
@@ -850,13 +1009,13 @@
expect(
sliderBox,
paints
- ..circle(x: 310.9375, y: 16.0, radius: 3.791776657104492)
+ ..circle(x: 105.0625, y: 16.0, radius: 3.791776657104492)
..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 1.0)
..circle(x: 591.5, y: 16.0, radius: 1.0)
..circle(x: 783.0, y: 16.0, radius: 1.0)
- ..circle(x: 310.9375, y: 16.0, radius: 6.0),
+ ..circle(x: 105.0625, y: 16.0, radius: 6.0),
);
// Reparenting in the middle of an animation should do nothing.
@@ -870,15 +1029,16 @@
expect(
sliderBox,
paints
- ..circle(x: 396.6802978515625, y: 16.0, radius: 8.0)
+ ..circle(x: 185.5457763671875, y: 16.0, radius: 8.0)
..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, y: 16.0, radius: 1.0)
+ ..circle(x: 400.0, y: 16.0, radius: 1.0)
..circle(x: 591.5, y: 16.0, radius: 1.0)
..circle(x: 783.0, y: 16.0, radius: 1.0)
- ..circle(x: 396.6802978515625, y: 16.0, radius: 6.0),
+ ..circle(x: 185.5457763671875, y: 16.0, radius: 6.0),
);
// Wait for animations to finish.
- await tester.pump(const Duration(milliseconds: 300));
+ await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
@@ -891,8 +1051,7 @@
..circle(x: 400.0, y: 16.0, radius: 6.0),
);
await gesture.up();
- await tester.pump();
- await tester.pump(const Duration(seconds: 1));
+ await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
@@ -903,21 +1062,6 @@
..circle(x: 783.0, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 6.0),
);
- // Move to 0.0 again.
- gesture = await tester.startGesture(Offset.zero);
- await tester.pump();
- await gesture.up();
- await tester.pump(const Duration(seconds: 1));
- expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
- expect(
- sliderBox,
- paints
- ..circle(x: 208.5, y: 16.0, radius: 1.0)
- ..circle(x: 400.0, y: 16.0, radius: 1.0)
- ..circle(x: 591.5, y: 16.0, radius: 1.0)
- ..circle(x: 783.0, y: 16.0, radius: 1.0)
- ..circle(x: 16.0, y: 16.0, radius: 6.0),
- );
}
await tester.pumpWidget(buildSlider(1));
@@ -925,7 +1069,6 @@
await testReparenting(false);
// Now do it again with reparenting in the middle of an animation.
await testReparenting(true);
-
});
testWidgets('Slider Semantics', (WidgetTester tester) async {
@@ -1025,9 +1168,8 @@
await tester.pumpWidget(buildApp(sliderTheme: theme, divisions: divisions, enabled: enabled));
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
- await tester.pump();
// Wait for value indicator animation to finish.
- await tester.pump(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(