parent
daa0d2dfb3
commit
767ce82646
@ -11,53 +11,44 @@ class SliderDemo extends StatefulWidget {
|
||||
|
||||
class _SliderDemoState extends State<SliderDemo> {
|
||||
double _value = 25.0;
|
||||
double _discreteValue = 20.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
appBar: new AppBar(title: new Text("Sliders")),
|
||||
body: new Block(children: <Widget>[
|
||||
new Container(
|
||||
height: 100.0,
|
||||
child: new Center(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Slider(
|
||||
value: _value,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
_value = value;
|
||||
});
|
||||
}
|
||||
),
|
||||
new Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new Text(_value.round().toString().padLeft(3, '0'))
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.collapse
|
||||
appBar: new AppBar(title: new Text('Sliders')),
|
||||
body: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
new Center(
|
||||
child: new Slider(
|
||||
value: _value,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
_value = value;
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
new Container(
|
||||
height: 100.0,
|
||||
child: new Center(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
// Disabled, but tracking the slider above.
|
||||
new Slider(value: _value / 100.0),
|
||||
new Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new Text((_value / 100.0).toStringAsFixed(2))
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.collapse
|
||||
),
|
||||
new Center(child: new Slider(value: _value / 100.0)),
|
||||
new Center(
|
||||
child: new Slider(
|
||||
value: _discreteValue,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
divisions: 5,
|
||||
label: '${_discreteValue.round()}',
|
||||
onChanged: (double value) {
|
||||
setState(() {
|
||||
_discreteValue = value;
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -10,10 +12,18 @@ import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'theme.dart';
|
||||
import 'typography.dart';
|
||||
|
||||
/// A material design slider.
|
||||
///
|
||||
/// Used to select from a continuous range of values.
|
||||
/// Used to select from a range of values.
|
||||
///
|
||||
/// A slider can be used to select from either a continuous or a discrete set of
|
||||
/// values. The default is use a continuous range of values from [min] to [max].
|
||||
/// To use discrete values, use a non-null value for [divisions], which
|
||||
/// indicates the number of discrete intervals. For example, if [min] is 0.0 and
|
||||
/// [max] is 50.0 and [divisions] is 5, then the slider can take on the values
|
||||
/// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
|
||||
///
|
||||
/// The slider itself does not maintain any state. Instead, when the state of
|
||||
/// the slider changes, the widget calls the [onChanged] callback. Most widgets
|
||||
@ -34,6 +44,8 @@ class Slider extends StatelessWidget {
|
||||
this.value,
|
||||
this.min: 0.0,
|
||||
this.max: 1.0,
|
||||
this.divisions,
|
||||
this.label,
|
||||
this.activeColor,
|
||||
this.onChanged
|
||||
}) : super(key: key) {
|
||||
@ -41,6 +53,7 @@ class Slider extends StatelessWidget {
|
||||
assert(min != null);
|
||||
assert(max != null);
|
||||
assert(value >= min && value <= max);
|
||||
assert(divisions == null || divisions > 0);
|
||||
}
|
||||
|
||||
/// The currently selected value for this slider.
|
||||
@ -58,6 +71,18 @@ class Slider extends StatelessWidget {
|
||||
/// Defaults to 1.0.
|
||||
final double max;
|
||||
|
||||
/// The number of discrete divisions.
|
||||
///
|
||||
/// Typically used with [label] to show the current discrete value.
|
||||
///
|
||||
/// If null, the slider is continuous.
|
||||
final int divisions;
|
||||
|
||||
/// A label to show above the slider when the slider is active.
|
||||
///
|
||||
/// Typically used to display the value of a discrete slider.
|
||||
final String label;
|
||||
|
||||
/// The color to use for the portion of the slider that has been selected.
|
||||
///
|
||||
/// Defaults to accent color of the current [Theme].
|
||||
@ -82,6 +107,8 @@ class Slider extends StatelessWidget {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
return new _SliderRenderObjectWidget(
|
||||
value: (value - min) / (max - min),
|
||||
divisions: divisions,
|
||||
label: label,
|
||||
activeColor: activeColor ?? Theme.of(context).accentColor,
|
||||
onChanged: onChanged != null ? _handleChanged : null
|
||||
);
|
||||
@ -89,16 +116,26 @@ class Slider extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
_SliderRenderObjectWidget({ Key key, this.value, this.activeColor, this.onChanged })
|
||||
: super(key: key);
|
||||
_SliderRenderObjectWidget({
|
||||
Key key,
|
||||
this.value,
|
||||
this.divisions,
|
||||
this.label,
|
||||
this.activeColor,
|
||||
this.onChanged
|
||||
}) : super(key: key);
|
||||
|
||||
final double value;
|
||||
final int divisions;
|
||||
final String label;
|
||||
final Color activeColor;
|
||||
final ValueChanged<double> onChanged;
|
||||
|
||||
@override
|
||||
_RenderSlider createRenderObject(BuildContext context) => new _RenderSlider(
|
||||
value: value,
|
||||
divisions: divisions,
|
||||
label: label,
|
||||
activeColor: activeColor,
|
||||
onChanged: onChanged
|
||||
);
|
||||
@ -107,6 +144,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
|
||||
renderObject
|
||||
..value = value
|
||||
..divisions = divisions
|
||||
..label = label
|
||||
..activeColor = activeColor
|
||||
..onChanged = onChanged;
|
||||
}
|
||||
@ -122,16 +161,39 @@ final Color _kActiveTrackColor = Colors.grey[500];
|
||||
final Tween<double> _kReactionRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kReactionRadius);
|
||||
final Tween<double> _kThumbRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kActiveThumbRadius);
|
||||
final ColorTween _kTrackColorTween = new ColorTween(begin: _kInactiveTrackColor, end: _kActiveTrackColor);
|
||||
final ColorTween _kTickColorTween = new ColorTween(begin: _kInactiveTrackColor, end: Colors.black54);
|
||||
final Duration _kDiscreteTransitionDuration = const Duration(milliseconds: 500);
|
||||
|
||||
const double _kLabelBalloonRadius = 14.0;
|
||||
final Tween<double> _kLabelBalloonCenterTween = new Tween<double>(begin: 0.0, end: -_kLabelBalloonRadius * 2.0);
|
||||
final Tween<double> _kLabelBalloonRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kLabelBalloonRadius);
|
||||
final Tween<double> _kLabelBalloonTipTween = new Tween<double>(begin: 0.0, end: -8.0);
|
||||
final double _kLabelBalloonTipAttachmentRatio = math.sin(math.PI / 4.0);
|
||||
|
||||
double _getAdditionalHeightForLabel(String label) {
|
||||
return label == null ? 0.0 : _kLabelBalloonRadius * 2.0;
|
||||
}
|
||||
|
||||
BoxConstraints _getAdditionalConstraints(String label) {
|
||||
return new BoxConstraints.tightFor(
|
||||
width: _kTrackWidth + 2 * _kReactionRadius,
|
||||
height: 2 * _kReactionRadius + _getAdditionalHeightForLabel(label)
|
||||
);
|
||||
}
|
||||
|
||||
class _RenderSlider extends RenderConstrainedBox {
|
||||
_RenderSlider({
|
||||
double value,
|
||||
int divisions,
|
||||
String label,
|
||||
Color activeColor,
|
||||
this.onChanged
|
||||
}) : _value = value,
|
||||
_divisions = divisions,
|
||||
_activeColor = activeColor,
|
||||
super(additionalConstraints: const BoxConstraints.tightFor(width: _kTrackWidth + 2 * _kReactionRadius, height: 2 * _kReactionRadius)) {
|
||||
super(additionalConstraints: _getAdditionalConstraints(label)) {
|
||||
assert(value != null && value >= 0.0 && value <= 1.0);
|
||||
this.label = label;
|
||||
_drag = new HorizontalDragGestureRecognizer()
|
||||
..onStart = _handleDragStart
|
||||
..onUpdate = _handleDragUpdate
|
||||
@ -141,6 +203,10 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
parent: _reactionController,
|
||||
curve: Curves.ease
|
||||
)..addListener(markNeedsPaint);
|
||||
_position = new AnimationController(
|
||||
value: value,
|
||||
duration: _kDiscreteTransitionDuration
|
||||
)..addListener(markNeedsPaint);
|
||||
}
|
||||
|
||||
double get value => _value;
|
||||
@ -150,6 +216,38 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
if (newValue == _value)
|
||||
return;
|
||||
_value = newValue;
|
||||
if (divisions != null)
|
||||
_position.animateTo(newValue, curve: Curves.ease);
|
||||
else
|
||||
_position.value = newValue;
|
||||
}
|
||||
|
||||
int get divisions => _divisions;
|
||||
int _divisions;
|
||||
void set divisions(int newDivisions) {
|
||||
if (newDivisions == _divisions)
|
||||
return;
|
||||
_divisions = newDivisions;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
String get label => _label;
|
||||
String _label;
|
||||
void set label(String newLabel) {
|
||||
if (newLabel == _label)
|
||||
return;
|
||||
_label = newLabel;
|
||||
additionalConstraints = _getAdditionalConstraints(_label);
|
||||
if (newLabel != null) {
|
||||
_labelPainter
|
||||
..text = new TextSpan(
|
||||
style: Typography.white.body1.copyWith(fontSize: 10.0),
|
||||
text: newLabel
|
||||
)
|
||||
..layoutToMaxIntrinsicWidth();
|
||||
} else {
|
||||
_labelPainter.text = null;
|
||||
}
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@ -169,15 +267,25 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
Animation<double> _reaction;
|
||||
AnimationController _reactionController;
|
||||
|
||||
AnimationController _position;
|
||||
final TextPainter _labelPainter = new TextPainter();
|
||||
|
||||
HorizontalDragGestureRecognizer _drag;
|
||||
bool _active = false;
|
||||
double _currentDragValue = 0.0;
|
||||
|
||||
double get _discretizedCurrentDragValue {
|
||||
double dragValue = _currentDragValue.clamp(0.0, 1.0);
|
||||
if (divisions != null)
|
||||
dragValue = (dragValue * divisions).round() / divisions;
|
||||
return dragValue;
|
||||
}
|
||||
|
||||
void _handleDragStart(Point globalPosition) {
|
||||
if (onChanged != null) {
|
||||
_active = true;
|
||||
_currentDragValue = (globalToLocal(globalPosition).x - _kReactionRadius) / _trackLength;
|
||||
onChanged(_currentDragValue.clamp(0.0, 1.0));
|
||||
onChanged(_discretizedCurrentDragValue);
|
||||
_reactionController.forward();
|
||||
markNeedsPaint();
|
||||
}
|
||||
@ -186,7 +294,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
void _handleDragUpdate(double delta) {
|
||||
if (onChanged != null) {
|
||||
_currentDragValue += delta / _trackLength;
|
||||
onChanged(_currentDragValue.clamp(0.0, 1.0));
|
||||
onChanged(_discretizedCurrentDragValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,33 +322,74 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
|
||||
final double trackLength = _trackLength;
|
||||
final bool enabled = onChanged != null;
|
||||
final double value = _position.value;
|
||||
|
||||
double trackCenter = offset.dy + size.height / 2.0;
|
||||
double trackLeft = offset.dx + _kReactionRadius;
|
||||
double trackTop = trackCenter - 1.0;
|
||||
double trackBottom = trackCenter + 1.0;
|
||||
double trackRight = trackLeft + trackLength;
|
||||
double trackActive = trackLeft + trackLength * value;
|
||||
final double additionalHeightForLabel = _getAdditionalHeightForLabel(label);
|
||||
final double trackCenter = offset.dy + (size.height - additionalHeightForLabel) / 2.0 + additionalHeightForLabel;
|
||||
final double trackLeft = offset.dx + _kReactionRadius;
|
||||
final double trackTop = trackCenter - 1.0;
|
||||
final double trackBottom = trackCenter + 1.0;
|
||||
final double trackRight = trackLeft + trackLength;
|
||||
final double trackActive = trackLeft + trackLength * value;
|
||||
|
||||
Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _kInactiveTrackColor;
|
||||
Paint trackPaint = new Paint()..color = _kTrackColorTween.evaluate(_reaction);
|
||||
final Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _kInactiveTrackColor;
|
||||
final Paint trackPaint = new Paint()..color = _kTrackColorTween.evaluate(_reaction);
|
||||
|
||||
double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
|
||||
Point activeLocation = new Point(trackActive, trackCenter);
|
||||
final Point thumbCenter = new Point(trackActive, trackCenter);
|
||||
final double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
|
||||
|
||||
if (enabled) {
|
||||
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), trackPaint);
|
||||
if (_value > 0.0)
|
||||
if (value > 0.0)
|
||||
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), primaryPaint);
|
||||
if (value < 1.0)
|
||||
canvas.drawRect(new Rect.fromLTRB(trackActive, trackTop, trackRight, trackBottom), trackPaint);
|
||||
} else {
|
||||
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, activeLocation.x - _kDisabledThumbRadius - 2, trackBottom), trackPaint);
|
||||
canvas.drawRect(new Rect.fromLTRB(activeLocation.x + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint);
|
||||
if (value > 0.0)
|
||||
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive - _kDisabledThumbRadius - 2, trackBottom), trackPaint);
|
||||
if (value < 1.0)
|
||||
canvas.drawRect(new Rect.fromLTRB(trackActive + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint);
|
||||
}
|
||||
|
||||
if (_reaction.status != AnimationStatus.dismissed) {
|
||||
Paint reactionPaint = new Paint()..color = _activeColor.withAlpha(kRadialReactionAlpha);
|
||||
canvas.drawCircle(activeLocation, _kReactionRadiusTween.evaluate(_reaction), reactionPaint);
|
||||
final int divisions = this.divisions;
|
||||
if (divisions != null) {
|
||||
const double tickWidth = 2.0;
|
||||
final double dx = (trackLength - tickWidth) / divisions;
|
||||
// If the ticks would be too dense, don't bother painting them.
|
||||
if (dx >= 3 * tickWidth) {
|
||||
final Paint tickPaint = new Paint()..color = _kTickColorTween.evaluate(_reaction);
|
||||
for (int i = 0; i <= divisions; i += 1) {
|
||||
double left = trackLeft + i * dx;
|
||||
canvas.drawRect(new Rect.fromLTRB(left, trackTop, left + tickWidth, trackBottom), tickPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (label != null) {
|
||||
final Point center = new Point(trackActive, _kLabelBalloonCenterTween.evaluate(_reaction) + trackCenter);
|
||||
final double radius = _kLabelBalloonRadiusTween.evaluate(_reaction);
|
||||
final Point tip = new Point(trackActive, _kLabelBalloonTipTween.evaluate(_reaction) + trackCenter);
|
||||
final double tipAttachment = _kLabelBalloonTipAttachmentRatio * radius;
|
||||
|
||||
canvas.drawCircle(center, radius, primaryPaint);
|
||||
Path path = new Path()
|
||||
..moveTo(tip.x, tip.y)
|
||||
..lineTo(center.x - tipAttachment, center.y + tipAttachment)
|
||||
..lineTo(center.x + tipAttachment, center.y + tipAttachment)
|
||||
..close();
|
||||
canvas.drawPath(path, primaryPaint);
|
||||
_labelPainter.layout();
|
||||
Offset labelOffset = new Offset(
|
||||
center.x - _labelPainter.width / 2.0,
|
||||
center.y - _labelPainter.height / 2.0
|
||||
);
|
||||
_labelPainter.paint(canvas, labelOffset);
|
||||
return;
|
||||
} else {
|
||||
Paint reactionPaint = new Paint()..color = _activeColor.withAlpha(kRadialReactionAlpha);
|
||||
canvas.drawCircle(thumbCenter, _kReactionRadiusTween.evaluate(_reaction), reactionPaint);
|
||||
}
|
||||
}
|
||||
canvas.drawCircle(activeLocation, thumbRadius, primaryPaint);
|
||||
canvas.drawCircle(thumbCenter, thumbRadius, primaryPaint);
|
||||
}
|
||||
}
|
||||
|
@ -259,16 +259,9 @@ List<TextPainter> _initPainters(List<String> labels) {
|
||||
List<TextPainter> painters = new List<TextPainter>(labels.length);
|
||||
for (int i = 0; i < painters.length; ++i) {
|
||||
String label = labels[i];
|
||||
TextPainter painter = new TextPainter(
|
||||
painters[i] = new TextPainter(
|
||||
new TextSpan(style: style, text: label)
|
||||
);
|
||||
painter
|
||||
..maxWidth = double.INFINITY
|
||||
..maxHeight = double.INFINITY
|
||||
..layout()
|
||||
..maxWidth = painter.maxIntrinsicWidth
|
||||
..layout();
|
||||
painters[i] = painter;
|
||||
)..layoutToMaxIntrinsicWidth();
|
||||
}
|
||||
return painters;
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ class TextPainter {
|
||||
}
|
||||
|
||||
ui.Paragraph _paragraph;
|
||||
bool _needsLayout = true;
|
||||
bool _needsLayout = false;
|
||||
|
||||
TextSpan _text;
|
||||
/// The (potentially styled) text to paint.
|
||||
@ -253,10 +253,15 @@ class TextPainter {
|
||||
if (_text == value)
|
||||
return;
|
||||
_text = value;
|
||||
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
|
||||
_text.build(builder);
|
||||
_paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle());
|
||||
_needsLayout = true;
|
||||
if (_text != null) {
|
||||
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
|
||||
_text.build(builder);
|
||||
_paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle());
|
||||
_needsLayout = true;
|
||||
} else {
|
||||
_paragraph = null;
|
||||
_needsLayout = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum width at which to layout the text.
|
||||
@ -343,12 +348,33 @@ class TextPainter {
|
||||
}
|
||||
}
|
||||
|
||||
bool _lastLayoutWasToMaxIntrinsicWidth = false;
|
||||
|
||||
/// Computes the visual position of the glyphs for painting the text.
|
||||
void layout() {
|
||||
if (!_needsLayout)
|
||||
return;
|
||||
_paragraph.layout();
|
||||
_needsLayout = false;
|
||||
_lastLayoutWasToMaxIntrinsicWidth = false;
|
||||
}
|
||||
|
||||
/// Computes the visual position of the glyphs using the unconstrainted max intrinsic width.
|
||||
void layoutToMaxIntrinsicWidth() {
|
||||
if (!_needsLayout && _lastLayoutWasToMaxIntrinsicWidth && width == maxIntrinsicWidth)
|
||||
return;
|
||||
_needsLayout = false;
|
||||
_lastLayoutWasToMaxIntrinsicWidth = true;
|
||||
_paragraph
|
||||
..minWidth = 0.0
|
||||
..maxWidth = double.INFINITY
|
||||
..layout();
|
||||
final double newMaxIntrinsicWidth = maxIntrinsicWidth;
|
||||
_paragraph
|
||||
..minWidth = newMaxIntrinsicWidth
|
||||
..maxWidth = newMaxIntrinsicWidth
|
||||
..layout();
|
||||
assert(width == maxIntrinsicWidth);
|
||||
}
|
||||
|
||||
/// Paints the text onto the given canvas at the given offset.
|
||||
|
90
packages/flutter/test/material/slider_test.dart
Normal file
90
packages/flutter/test/material/slider_test.dart
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Slider can move when tapped', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
Key sliderKey = new UniqueKey();
|
||||
double value = 0.0;
|
||||
|
||||
tester.pumpWidget(
|
||||
new StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return new Material(
|
||||
child: new Center(
|
||||
child: new Slider(
|
||||
key: sliderKey,
|
||||
value: value,
|
||||
onChanged: (double newValue) {
|
||||
setState(() {
|
||||
value = newValue;
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(value, equals(0.0));
|
||||
tester.tap(tester.findElementByKey(sliderKey));
|
||||
expect(value, equals(0.5));
|
||||
tester.pump(); // No animation should start.
|
||||
expect(Scheduler.instance.transientCallbackCount, equals(0));
|
||||
});
|
||||
});
|
||||
|
||||
test('Slider take on discrete values', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
Key sliderKey = new UniqueKey();
|
||||
double value = 0.0;
|
||||
|
||||
tester.pumpWidget(
|
||||
new StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return new Material(
|
||||
child: new Center(
|
||||
child: new Slider(
|
||||
key: sliderKey,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
divisions: 10,
|
||||
value: value,
|
||||
onChanged: (double newValue) {
|
||||
setState(() {
|
||||
value = newValue;
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(value, equals(0.0));
|
||||
tester.tap(tester.findElementByKey(sliderKey));
|
||||
expect(value, equals(50.0));
|
||||
tester.scroll(tester.findElementByKey(sliderKey), const Offset(5.0, 0.0));
|
||||
expect(value, equals(50.0));
|
||||
tester.scroll(tester.findElementByKey(sliderKey), const Offset(40.0, 0.0));
|
||||
expect(value, equals(80.0));
|
||||
|
||||
tester.pump(); // Starts animation.
|
||||
expect(Scheduler.instance.transientCallbackCount, greaterThan(0));
|
||||
tester.pump(const Duration(milliseconds: 200));
|
||||
tester.pump(const Duration(milliseconds: 200));
|
||||
tester.pump(const Duration(milliseconds: 200));
|
||||
tester.pump(const Duration(milliseconds: 200));
|
||||
// Animation complete.
|
||||
expect(Scheduler.instance.transientCallbackCount, equals(0));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user