parent
daa0d2dfb3
commit
767ce82646
@ -11,18 +11,17 @@ class SliderDemo extends StatefulWidget {
|
|||||||
|
|
||||||
class _SliderDemoState extends State<SliderDemo> {
|
class _SliderDemoState extends State<SliderDemo> {
|
||||||
double _value = 25.0;
|
double _value = 25.0;
|
||||||
|
double _discreteValue = 20.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
appBar: new AppBar(title: new Text("Sliders")),
|
appBar: new AppBar(title: new Text('Sliders')),
|
||||||
body: new Block(children: <Widget>[
|
body: new Column(
|
||||||
new Container(
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
height: 100.0,
|
|
||||||
child: new Center(
|
|
||||||
child: new Row(
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new Slider(
|
new Center(
|
||||||
|
child: new Slider(
|
||||||
value: _value,
|
value: _value,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 100.0,
|
max: 100.0,
|
||||||
@ -31,33 +30,25 @@ class _SliderDemoState extends State<SliderDemo> {
|
|||||||
_value = value;
|
_value = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
),
|
|
||||||
new Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: new Text(_value.round().toString().padLeft(3, '0'))
|
|
||||||
),
|
|
||||||
],
|
|
||||||
mainAxisAlignment: MainAxisAlignment.collapse
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
new Container(
|
new Center(child: new Slider(value: _value / 100.0)),
|
||||||
height: 100.0,
|
new Center(
|
||||||
child: new Center(
|
child: new Slider(
|
||||||
child: new Row(
|
value: _discreteValue,
|
||||||
children: <Widget>[
|
min: 0.0,
|
||||||
// Disabled, but tracking the slider above.
|
max: 100.0,
|
||||||
new Slider(value: _value / 100.0),
|
divisions: 5,
|
||||||
new Container(
|
label: '${_discreteValue.round()}',
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
onChanged: (double value) {
|
||||||
child: new Text((_value / 100.0).toStringAsFixed(2))
|
setState(() {
|
||||||
|
_discreteValue = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
]
|
||||||
mainAxisAlignment: MainAxisAlignment.collapse
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@ -10,10 +12,18 @@ import 'colors.dart';
|
|||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
import 'typography.dart';
|
||||||
|
|
||||||
/// A material design slider.
|
/// 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 itself does not maintain any state. Instead, when the state of
|
||||||
/// the slider changes, the widget calls the [onChanged] callback. Most widgets
|
/// the slider changes, the widget calls the [onChanged] callback. Most widgets
|
||||||
@ -34,6 +44,8 @@ class Slider extends StatelessWidget {
|
|||||||
this.value,
|
this.value,
|
||||||
this.min: 0.0,
|
this.min: 0.0,
|
||||||
this.max: 1.0,
|
this.max: 1.0,
|
||||||
|
this.divisions,
|
||||||
|
this.label,
|
||||||
this.activeColor,
|
this.activeColor,
|
||||||
this.onChanged
|
this.onChanged
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
@ -41,6 +53,7 @@ class Slider extends StatelessWidget {
|
|||||||
assert(min != null);
|
assert(min != null);
|
||||||
assert(max != null);
|
assert(max != null);
|
||||||
assert(value >= min && value <= max);
|
assert(value >= min && value <= max);
|
||||||
|
assert(divisions == null || divisions > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The currently selected value for this slider.
|
/// The currently selected value for this slider.
|
||||||
@ -58,6 +71,18 @@ class Slider extends StatelessWidget {
|
|||||||
/// Defaults to 1.0.
|
/// Defaults to 1.0.
|
||||||
final double max;
|
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.
|
/// The color to use for the portion of the slider that has been selected.
|
||||||
///
|
///
|
||||||
/// Defaults to accent color of the current [Theme].
|
/// Defaults to accent color of the current [Theme].
|
||||||
@ -82,6 +107,8 @@ class Slider extends StatelessWidget {
|
|||||||
assert(debugCheckHasMaterial(context));
|
assert(debugCheckHasMaterial(context));
|
||||||
return new _SliderRenderObjectWidget(
|
return new _SliderRenderObjectWidget(
|
||||||
value: (value - min) / (max - min),
|
value: (value - min) / (max - min),
|
||||||
|
divisions: divisions,
|
||||||
|
label: label,
|
||||||
activeColor: activeColor ?? Theme.of(context).accentColor,
|
activeColor: activeColor ?? Theme.of(context).accentColor,
|
||||||
onChanged: onChanged != null ? _handleChanged : null
|
onChanged: onChanged != null ? _handleChanged : null
|
||||||
);
|
);
|
||||||
@ -89,16 +116,26 @@ class Slider extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
|
class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
|
||||||
_SliderRenderObjectWidget({ Key key, this.value, this.activeColor, this.onChanged })
|
_SliderRenderObjectWidget({
|
||||||
: super(key: key);
|
Key key,
|
||||||
|
this.value,
|
||||||
|
this.divisions,
|
||||||
|
this.label,
|
||||||
|
this.activeColor,
|
||||||
|
this.onChanged
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final double value;
|
final double value;
|
||||||
|
final int divisions;
|
||||||
|
final String label;
|
||||||
final Color activeColor;
|
final Color activeColor;
|
||||||
final ValueChanged<double> onChanged;
|
final ValueChanged<double> onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderSlider createRenderObject(BuildContext context) => new _RenderSlider(
|
_RenderSlider createRenderObject(BuildContext context) => new _RenderSlider(
|
||||||
value: value,
|
value: value,
|
||||||
|
divisions: divisions,
|
||||||
|
label: label,
|
||||||
activeColor: activeColor,
|
activeColor: activeColor,
|
||||||
onChanged: onChanged
|
onChanged: onChanged
|
||||||
);
|
);
|
||||||
@ -107,6 +144,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
|
|||||||
void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
|
void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
|
||||||
renderObject
|
renderObject
|
||||||
..value = value
|
..value = value
|
||||||
|
..divisions = divisions
|
||||||
|
..label = label
|
||||||
..activeColor = activeColor
|
..activeColor = activeColor
|
||||||
..onChanged = onChanged;
|
..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> _kReactionRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kReactionRadius);
|
||||||
final Tween<double> _kThumbRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kActiveThumbRadius);
|
final Tween<double> _kThumbRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kActiveThumbRadius);
|
||||||
final ColorTween _kTrackColorTween = new ColorTween(begin: _kInactiveTrackColor, end: _kActiveTrackColor);
|
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 {
|
class _RenderSlider extends RenderConstrainedBox {
|
||||||
_RenderSlider({
|
_RenderSlider({
|
||||||
double value,
|
double value,
|
||||||
|
int divisions,
|
||||||
|
String label,
|
||||||
Color activeColor,
|
Color activeColor,
|
||||||
this.onChanged
|
this.onChanged
|
||||||
}) : _value = value,
|
}) : _value = value,
|
||||||
|
_divisions = divisions,
|
||||||
_activeColor = activeColor,
|
_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);
|
assert(value != null && value >= 0.0 && value <= 1.0);
|
||||||
|
this.label = label;
|
||||||
_drag = new HorizontalDragGestureRecognizer()
|
_drag = new HorizontalDragGestureRecognizer()
|
||||||
..onStart = _handleDragStart
|
..onStart = _handleDragStart
|
||||||
..onUpdate = _handleDragUpdate
|
..onUpdate = _handleDragUpdate
|
||||||
@ -141,6 +203,10 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||||||
parent: _reactionController,
|
parent: _reactionController,
|
||||||
curve: Curves.ease
|
curve: Curves.ease
|
||||||
)..addListener(markNeedsPaint);
|
)..addListener(markNeedsPaint);
|
||||||
|
_position = new AnimationController(
|
||||||
|
value: value,
|
||||||
|
duration: _kDiscreteTransitionDuration
|
||||||
|
)..addListener(markNeedsPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get value => _value;
|
double get value => _value;
|
||||||
@ -150,6 +216,38 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||||||
if (newValue == _value)
|
if (newValue == _value)
|
||||||
return;
|
return;
|
||||||
_value = newValue;
|
_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();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,15 +267,25 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||||||
Animation<double> _reaction;
|
Animation<double> _reaction;
|
||||||
AnimationController _reactionController;
|
AnimationController _reactionController;
|
||||||
|
|
||||||
|
AnimationController _position;
|
||||||
|
final TextPainter _labelPainter = new TextPainter();
|
||||||
|
|
||||||
HorizontalDragGestureRecognizer _drag;
|
HorizontalDragGestureRecognizer _drag;
|
||||||
bool _active = false;
|
bool _active = false;
|
||||||
double _currentDragValue = 0.0;
|
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) {
|
void _handleDragStart(Point globalPosition) {
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
_active = true;
|
_active = true;
|
||||||
_currentDragValue = (globalToLocal(globalPosition).x - _kReactionRadius) / _trackLength;
|
_currentDragValue = (globalToLocal(globalPosition).x - _kReactionRadius) / _trackLength;
|
||||||
onChanged(_currentDragValue.clamp(0.0, 1.0));
|
onChanged(_discretizedCurrentDragValue);
|
||||||
_reactionController.forward();
|
_reactionController.forward();
|
||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
@ -186,7 +294,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||||||
void _handleDragUpdate(double delta) {
|
void _handleDragUpdate(double delta) {
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
_currentDragValue += delta / _trackLength;
|
_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 double trackLength = _trackLength;
|
||||||
final bool enabled = onChanged != null;
|
final bool enabled = onChanged != null;
|
||||||
|
final double value = _position.value;
|
||||||
|
|
||||||
double trackCenter = offset.dy + size.height / 2.0;
|
final double additionalHeightForLabel = _getAdditionalHeightForLabel(label);
|
||||||
double trackLeft = offset.dx + _kReactionRadius;
|
final double trackCenter = offset.dy + (size.height - additionalHeightForLabel) / 2.0 + additionalHeightForLabel;
|
||||||
double trackTop = trackCenter - 1.0;
|
final double trackLeft = offset.dx + _kReactionRadius;
|
||||||
double trackBottom = trackCenter + 1.0;
|
final double trackTop = trackCenter - 1.0;
|
||||||
double trackRight = trackLeft + trackLength;
|
final double trackBottom = trackCenter + 1.0;
|
||||||
double trackActive = trackLeft + trackLength * value;
|
final double trackRight = trackLeft + trackLength;
|
||||||
|
final double trackActive = trackLeft + trackLength * value;
|
||||||
|
|
||||||
Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _kInactiveTrackColor;
|
final Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _kInactiveTrackColor;
|
||||||
Paint trackPaint = new Paint()..color = _kTrackColorTween.evaluate(_reaction);
|
final Paint trackPaint = new Paint()..color = _kTrackColorTween.evaluate(_reaction);
|
||||||
|
|
||||||
double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
|
final Point thumbCenter = new Point(trackActive, trackCenter);
|
||||||
Point activeLocation = new Point(trackActive, trackCenter);
|
final double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
|
||||||
|
|
||||||
if (enabled) {
|
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);
|
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 {
|
} else {
|
||||||
canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, activeLocation.x - _kDisabledThumbRadius - 2, trackBottom), trackPaint);
|
if (value > 0.0)
|
||||||
canvas.drawRect(new Rect.fromLTRB(activeLocation.x + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint);
|
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) {
|
if (_reaction.status != AnimationStatus.dismissed) {
|
||||||
Paint reactionPaint = new Paint()..color = _activeColor.withAlpha(kRadialReactionAlpha);
|
final int divisions = this.divisions;
|
||||||
canvas.drawCircle(activeLocation, _kReactionRadiusTween.evaluate(_reaction), reactionPaint);
|
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);
|
||||||
}
|
}
|
||||||
canvas.drawCircle(activeLocation, thumbRadius, primaryPaint);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(thumbCenter, thumbRadius, primaryPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,16 +259,9 @@ List<TextPainter> _initPainters(List<String> labels) {
|
|||||||
List<TextPainter> painters = new List<TextPainter>(labels.length);
|
List<TextPainter> painters = new List<TextPainter>(labels.length);
|
||||||
for (int i = 0; i < painters.length; ++i) {
|
for (int i = 0; i < painters.length; ++i) {
|
||||||
String label = labels[i];
|
String label = labels[i];
|
||||||
TextPainter painter = new TextPainter(
|
painters[i] = new TextPainter(
|
||||||
new TextSpan(style: style, text: label)
|
new TextSpan(style: style, text: label)
|
||||||
);
|
)..layoutToMaxIntrinsicWidth();
|
||||||
painter
|
|
||||||
..maxWidth = double.INFINITY
|
|
||||||
..maxHeight = double.INFINITY
|
|
||||||
..layout()
|
|
||||||
..maxWidth = painter.maxIntrinsicWidth
|
|
||||||
..layout();
|
|
||||||
painters[i] = painter;
|
|
||||||
}
|
}
|
||||||
return painters;
|
return painters;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ class TextPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui.Paragraph _paragraph;
|
ui.Paragraph _paragraph;
|
||||||
bool _needsLayout = true;
|
bool _needsLayout = false;
|
||||||
|
|
||||||
TextSpan _text;
|
TextSpan _text;
|
||||||
/// The (potentially styled) text to paint.
|
/// The (potentially styled) text to paint.
|
||||||
@ -253,10 +253,15 @@ class TextPainter {
|
|||||||
if (_text == value)
|
if (_text == value)
|
||||||
return;
|
return;
|
||||||
_text = value;
|
_text = value;
|
||||||
|
if (_text != null) {
|
||||||
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
|
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
|
||||||
_text.build(builder);
|
_text.build(builder);
|
||||||
_paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle());
|
_paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle());
|
||||||
_needsLayout = true;
|
_needsLayout = true;
|
||||||
|
} else {
|
||||||
|
_paragraph = null;
|
||||||
|
_needsLayout = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum width at which to layout the text.
|
/// 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.
|
/// Computes the visual position of the glyphs for painting the text.
|
||||||
void layout() {
|
void layout() {
|
||||||
if (!_needsLayout)
|
if (!_needsLayout)
|
||||||
return;
|
return;
|
||||||
_paragraph.layout();
|
_paragraph.layout();
|
||||||
_needsLayout = false;
|
_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.
|
/// 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