Unverified Commit d28e9aa5 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

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).
parent fc96326b
......@@ -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<Slider> with TickerProviderStateMixin {
static const Duration enableAnimationDuration = const Duration(milliseconds: 75);
static const Duration positionAnimationDuration = const Duration(milliseconds: 75);
// Animation controller that is run when interactions occur (taps, drags,
// etc.).
AnimationController reactionController;
static const Duration valueIndicatorAnimationDuration = const Duration(milliseconds: 100);
// 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<double> _overlayRadiusTween = new Tween<double>(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<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 @@ 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<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 @@ 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,
......
......@@ -469,19 +469,19 @@ class SliderThemeData extends Diagnosticable {
);
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 @@ 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;
......
......@@ -177,7 +177,58 @@ void main() {
expect(updates, equals(1));
});
testWidgets('discrete Slider repaints when dragged', (WidgetTester tester) async {
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>[];
......@@ -229,14 +280,125 @@ void main() {
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;
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(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<RenderBox>(find.byType(Slider));
expect(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment