diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index c53cb77808..e5d780b192 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 Slider extends StatefulWidget { class _SliderState extends State 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 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { } } -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 _overlayRadiusTween = new Tween(begin: 0.0, end: _overlayRadius); - class _RenderSlider extends RenderBox { _RenderSlider({ @required double value, @@ -403,8 +410,12 @@ class _RenderSlider extends RenderBox { ..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 @@ class _RenderSlider extends RenderBox { ); } - 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 _overlayRadiusTween = new Tween(begin: 0.0, end: _overlayRadius); _SliderState _state; + Animation _overlayAnimation; + Animation _valueIndicatorAnimation; + Animation _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 @@ class _RenderSlider extends RenderBox { } _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 @@ class _RenderSlider extends RenderBox { _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 @@ class _RenderSlider extends RenderBox { markNeedsLayout(); } - double get _railLength => size.width - _overlayDiameter; - - Animation _reaction; - Animation _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 @@ class _RenderSlider extends RenderBox { _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 @@ class _RenderSlider extends RenderBox { 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 @@ class _RenderSlider extends RenderBox { } 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 @@ class _RenderSlider extends RenderBox { 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 @@ class _RenderSlider extends RenderBox { 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 1603e5b77a..9983db21ab 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -469,19 +469,19 @@ class SliderThemeData extends Diagnosticable { ); description.add(new DiagnosticsProperty('activeRailColor', activeRailColor, defaultValue: defaultData.activeRailColor)); description.add(new DiagnosticsProperty('inactiveRailColor', inactiveRailColor, defaultValue: defaultData.inactiveRailColor)); - description.add(new DiagnosticsProperty('disabledActiveRailColor', disabledActiveRailColor, defaultValue: defaultData.disabledActiveRailColor)); - description.add(new DiagnosticsProperty('disabledInactiveRailColor', disabledInactiveRailColor, defaultValue: defaultData.disabledInactiveRailColor)); - description.add(new DiagnosticsProperty('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor)); - description.add(new DiagnosticsProperty('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor)); - description.add(new DiagnosticsProperty('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor)); - description.add(new DiagnosticsProperty('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor)); + description.add(new DiagnosticsProperty('disabledActiveRailColor', disabledActiveRailColor, defaultValue: defaultData.disabledActiveRailColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('disabledInactiveRailColor', disabledInactiveRailColor, defaultValue: defaultData.disabledInactiveRailColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor, level: DiagnosticLevel.debug)); description.add(new DiagnosticsProperty('thumbColor', thumbColor, defaultValue: defaultData.thumbColor)); - description.add(new DiagnosticsProperty('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor)); - description.add(new DiagnosticsProperty('overlayColor', overlayColor, defaultValue: defaultData.overlayColor)); + description.add(new DiagnosticsProperty('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('overlayColor', overlayColor, defaultValue: defaultData.overlayColor, level: DiagnosticLevel.debug)); description.add(new DiagnosticsProperty('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor)); - description.add(new DiagnosticsProperty('thumbShape', thumbShape, defaultValue: defaultData.thumbShape)); - description.add(new DiagnosticsProperty('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape)); - description.add(new DiagnosticsProperty('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator)); + description.add(new DiagnosticsProperty('thumbShape', thumbShape, defaultValue: defaultData.thumbShape, level: DiagnosticLevel.debug)); + description.add(new DiagnosticsProperty('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape, level: DiagnosticLevel.debug)); + description.add(new EnumProperty('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator)); } } @@ -595,8 +595,8 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape { // 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 670f30134d..a005bb051a 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -177,6 +177,168 @@ void main() { 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 log = []; + 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 expectedLog = [ + 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { // 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 @@ void main() { 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 @@ void main() { ..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 @@ void main() { )); 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 @@ void main() { 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 @@ void main() { )); 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 @@ void main() { )); 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { ..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 @@ void main() { ..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 @@ void main() { 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 @@ void main() { 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(find.byType(Slider)); expect(