Adding edge avoidance, painting tests, general cleanup (#15078)

Fixed the real repaint problem the Slider had (missing addListener), and added tests for it.

Added GlobalKey reparenting test.

Added the ability for the value indicator to slide left and right to avoid falling off the edge of the screen.
It only shifts as much as it can without deforming, but even at large text scales, that is enough to keep the text on the screen.

Updated the formatting on theme_data.dart and others to use longer line length.

Also, removed a color tween that faded the value indicator in as it scaled, since that wasn't to spec.
This commit is contained in:
Greg Spencer 2018-03-09 14:04:58 -08:00 committed by GitHub
parent 3a40d0ee4e
commit 7fab7f6d02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1017 additions and 525 deletions

View File

@ -53,6 +53,10 @@ import 'theme.dart';
/// ///
/// Requires one of its ancestors to be a [Material] widget. /// Requires one of its ancestors to be a [Material] widget.
/// ///
/// Requires one of its ancestors to be a [MediaQuery] widget. Typically, these
/// are introduced by the [MaterialApp] or [WidgetsApp] widget at the top of
/// your application widget tree.
///
/// To determine how it should be displayed (e.g. colors, thumb shape, etc.), /// To determine how it should be displayed (e.g. colors, thumb shape, etc.),
/// a slider uses the [SliderThemeData] available from either a [SliderTheme] /// a slider uses the [SliderThemeData] available from either a [SliderTheme]
/// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the /// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the
@ -67,13 +71,15 @@ import 'theme.dart';
/// * [Radio], for selecting among a set of explicit values. /// * [Radio], for selecting among a set of explicit values.
/// * [Checkbox] and [Switch], for toggling a particular value on or off. /// * [Checkbox] and [Switch], for toggling a particular value on or off.
/// * <https://material.google.com/components/sliders.html> /// * <https://material.google.com/components/sliders.html>
/// * [MediaQuery], from which the text scale factor is obtained.
class Slider extends StatefulWidget { class Slider extends StatefulWidget {
/// Creates a material design slider. /// Creates a material design slider.
/// ///
/// 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
/// that use a slider will listen for the [onChanged] callback and rebuild the /// widgets that use a slider will listen for the [onChanged] callback and
/// slider with a new [value] to update the visual appearance of the slider. /// rebuild the slider with a new [value] to update the visual appearance of
/// the slider.
/// ///
/// * [value] determines currently selected value for this slider. /// * [value] determines currently selected value for this slider.
/// * [onChanged] is called when the user selects a new value for the slider. /// * [onChanged] is called when the user selects a new value for the slider.
@ -208,8 +214,13 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
static const Duration enableAnimationDuration = const Duration(milliseconds: 75); static const Duration enableAnimationDuration = const Duration(milliseconds: 75);
static const Duration positionAnimationDuration = 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; AnimationController reactionController;
// Animation controller that is run when enabling/disabling the slider.
AnimationController enableController; AnimationController enableController;
// Animation controller that is run when transitioning between one value
// and the next on a discrete slider.
AnimationController positionController; AnimationController positionController;
@override @override
@ -227,6 +238,8 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
duration: positionAnimationDuration, duration: positionAnimationDuration,
vsync: this, vsync: this,
); );
enableController.value = widget.onChanged != null ? 1.0 : 0.0;
positionController.value = widget.value;
} }
@override @override
@ -263,6 +276,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
assert(debugCheckHasMediaQuery(context));
SliderThemeData sliderTheme = SliderTheme.of(context); SliderThemeData sliderTheme = SliderTheme.of(context);
@ -286,7 +300,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
divisions: widget.divisions, divisions: widget.divisions,
label: widget.label, label: widget.label,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, mediaQueryData: MediaQuery.of(context),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
state: this, state: this,
); );
@ -300,7 +314,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.divisions, this.divisions,
this.label, this.label,
this.sliderTheme, this.sliderTheme,
this.textScaleFactor, this.mediaQueryData,
this.onChanged, this.onChanged,
this.state, this.state,
}) : super(key: key); }) : super(key: key);
@ -309,7 +323,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
final int divisions; final int divisions;
final String label; final String label;
final SliderThemeData sliderTheme; final SliderThemeData sliderTheme;
final double textScaleFactor; final MediaQueryData mediaQueryData;
final ValueChanged<double> onChanged; final ValueChanged<double> onChanged;
final _SliderState state; final _SliderState state;
@ -321,7 +335,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
label: label, label: label,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
theme: Theme.of(context), theme: Theme.of(context),
textScaleFactor: textScaleFactor, mediaQueryData: mediaQueryData,
onChanged: onChanged, onChanged: onChanged,
state: state, state: state,
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
@ -336,7 +350,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..label = label ..label = label
..sliderTheme = sliderTheme ..sliderTheme = sliderTheme
..theme = Theme.of(context) ..theme = Theme.of(context)
..textScaleFactor = textScaleFactor ..mediaQueryData = mediaQueryData
..onChanged = onChanged ..onChanged = onChanged
..textDirection = Directionality.of(context); ..textDirection = Directionality.of(context);
// Ticker provider cannot change since there's a 1:1 relationship between // Ticker provider cannot change since there's a 1:1 relationship between
@ -360,7 +374,7 @@ class _RenderSlider extends RenderBox {
String label, String label,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
ThemeData theme, ThemeData theme,
double textScaleFactor, MediaQueryData mediaQueryData,
ValueChanged<double> onChanged, ValueChanged<double> onChanged,
@required _SliderState state, @required _SliderState state,
@required TextDirection textDirection, @required TextDirection textDirection,
@ -372,7 +386,7 @@ class _RenderSlider extends RenderBox {
_divisions = divisions, _divisions = divisions,
_sliderTheme = sliderTheme, _sliderTheme = sliderTheme,
_theme = theme, _theme = theme,
_textScaleFactor = textScaleFactor, _mediaQueryData = mediaQueryData,
_onChanged = onChanged, _onChanged = onChanged,
_state = state, _state = state,
_textDirection = textDirection { _textDirection = textDirection {
@ -389,12 +403,14 @@ class _RenderSlider extends RenderBox {
..onTapDown = _handleTapDown ..onTapDown = _handleTapDown
..onTapUp = _handleTapUp ..onTapUp = _handleTapUp
..onTapCancel = _endInteraction; ..onTapCancel = _endInteraction;
_reaction = new CurvedAnimation(parent: state.reactionController, curve: Curves.fastOutSlowIn) _reaction = new CurvedAnimation(
..addListener(markNeedsPaint); parent: _state.reactionController,
state.enableController.value = isInteractive ? 1.0 : 0.0; curve: Curves.fastOutSlowIn,
_enableAnimation = new CurvedAnimation(parent: state.enableController, curve: Curves.easeInOut) );
..addListener(markNeedsPaint); _enableAnimation = new CurvedAnimation(
state.positionController.value = _value; parent: _state.enableController,
curve: Curves.easeInOut,
);
} }
double get value => _value; double get value => _value;
@ -418,7 +434,6 @@ class _RenderSlider extends RenderBox {
int get divisions => _divisions; int get divisions => _divisions;
int _divisions; int _divisions;
set divisions(int value) { set divisions(int value) {
if (value == _divisions) { if (value == _divisions) {
return; return;
@ -429,7 +444,6 @@ class _RenderSlider extends RenderBox {
String get label => _label; String get label => _label;
String _label; String _label;
set label(String value) { set label(String value) {
if (value == _label) { if (value == _label) {
return; return;
@ -440,7 +454,6 @@ class _RenderSlider extends RenderBox {
SliderThemeData get sliderTheme => _sliderTheme; SliderThemeData get sliderTheme => _sliderTheme;
SliderThemeData _sliderTheme; SliderThemeData _sliderTheme;
set sliderTheme(SliderThemeData value) { set sliderTheme(SliderThemeData value) {
if (value == _sliderTheme) { if (value == _sliderTheme) {
return; return;
@ -451,7 +464,6 @@ class _RenderSlider extends RenderBox {
ThemeData get theme => _theme; ThemeData get theme => _theme;
ThemeData _theme; ThemeData _theme;
set theme(ThemeData value) { set theme(ThemeData value) {
if (value == _theme) { if (value == _theme) {
return; return;
@ -460,20 +472,20 @@ class _RenderSlider extends RenderBox {
markNeedsPaint(); markNeedsPaint();
} }
double get textScaleFactor => _textScaleFactor; MediaQueryData get mediaQueryData => _mediaQueryData;
double _textScaleFactor; MediaQueryData _mediaQueryData;
set mediaQueryData(MediaQueryData value) {
set textScaleFactor(double value) { if (value == _mediaQueryData) {
if (value == _textScaleFactor) {
return; return;
} }
_textScaleFactor = value; _mediaQueryData = value;
// Media query data includes the textScaleFactor, so we need to update the
// label painter.
_updateLabelPainter(); _updateLabelPainter();
} }
ValueChanged<double> get onChanged => _onChanged; ValueChanged<double> get onChanged => _onChanged;
ValueChanged<double> _onChanged; ValueChanged<double> _onChanged;
set onChanged(ValueChanged<double> value) { set onChanged(ValueChanged<double> value) {
if (value == _onChanged) { if (value == _onChanged) {
return; return;
@ -493,7 +505,6 @@ class _RenderSlider extends RenderBox {
TextDirection get textDirection => _textDirection; TextDirection get textDirection => _textDirection;
TextDirection _textDirection; TextDirection _textDirection;
set textDirection(TextDirection value) { set textDirection(TextDirection value) {
assert(value != null); assert(value != null);
if (value == _textDirection) { if (value == _textDirection) {
@ -505,12 +516,10 @@ class _RenderSlider extends RenderBox {
void _updateLabelPainter() { void _updateLabelPainter() {
if (label != null) { if (label != null) {
// We have to account for the text scale factor in the supplied theme.
final TextStyle style = _theme.accentTextTheme.body2
.copyWith(fontSize: _theme.accentTextTheme.body2.fontSize * _textScaleFactor);
_labelPainter _labelPainter
..text = new TextSpan(style: style, text: label) ..text = new TextSpan(style: _theme.accentTextTheme.body2, text: label)
..textDirection = textDirection ..textDirection = textDirection
..textScaleFactor = _mediaQueryData.textScaleFactor
..layout(); ..layout();
} else { } else {
_labelPainter.text = null; _labelPainter.text = null;
@ -535,6 +544,22 @@ class _RenderSlider extends RenderBox {
bool get isDiscrete => divisions != null && divisions > 0; bool get isDiscrete => divisions != null && divisions > 0;
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_reaction.addListener(markNeedsPaint);
_enableAnimation.addListener(markNeedsPaint);
_state.positionController.addListener(markNeedsPaint);
}
@override
void detach() {
_reaction.removeListener(markNeedsPaint);
_enableAnimation.removeListener(markNeedsPaint);
_state.positionController.removeListener(markNeedsPaint);
super.detach();
}
double _getValueFromVisualPosition(double visualPosition) { double _getValueFromVisualPosition(double visualPosition) {
switch (textDirection) { switch (textDirection) {
case TextDirection.rtl: case TextDirection.rtl:
@ -589,7 +614,6 @@ class _RenderSlider extends RenderBox {
break; break;
} }
onChanged(_discretize(_currentDragValue)); onChanged(_discretize(_currentDragValue));
markNeedsPaint();
} }
} }
@ -614,8 +638,10 @@ class _RenderSlider extends RenderBox {
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) {
return math.max(_overlayDiameter, return math.max(
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width); _overlayDiameter,
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width,
);
} }
@override @override
@ -643,7 +669,12 @@ class _RenderSlider extends RenderBox {
} }
void _paintTickMarks( void _paintTickMarks(
Canvas canvas, Rect railLeft, Rect railRight, Paint leftPaint, Paint rightPaint) { Canvas canvas,
Rect railLeft,
Rect railRight,
Paint leftPaint,
Paint rightPaint,
) {
if (isDiscrete) { if (isDiscrete) {
// The ticks are tiny circles that are the same height as the rail. // The ticks are tiny circles that are the same height as the rail.
const double tickRadius = _railHeight / 2.0; const double tickRadius = _railHeight / 2.0;
@ -683,23 +714,15 @@ class _RenderSlider extends RenderBox {
final double railLength = size.width - 2 * _overlayRadius; final double railLength = size.width - 2 * _overlayRadius;
final double value = _state.positionController.value; final double value = _state.positionController.value;
final ColorTween activeRailEnableColor = new ColorTween( final ColorTween activeRailEnableColor = new ColorTween(begin: _sliderTheme.disabledActiveRailColor, end: _sliderTheme.activeRailColor);
begin: _sliderTheme.disabledActiveRailColor, end: _sliderTheme.activeRailColor); final ColorTween inactiveRailEnableColor = new ColorTween(begin: _sliderTheme.disabledInactiveRailColor, end: _sliderTheme.inactiveRailColor);
final ColorTween inactiveRailEnableColor = new ColorTween( final ColorTween activeTickMarkEnableColor = new ColorTween(begin: _sliderTheme.disabledActiveTickMarkColor, end: _sliderTheme.activeTickMarkColor);
begin: _sliderTheme.disabledInactiveRailColor, end: _sliderTheme.inactiveRailColor); final ColorTween inactiveTickMarkEnableColor = new ColorTween(begin: _sliderTheme.disabledInactiveTickMarkColor, end: _sliderTheme.inactiveTickMarkColor);
final ColorTween activeTickMarkEnableColor = new ColorTween(
begin: _sliderTheme.disabledActiveTickMarkColor, end: _sliderTheme.activeTickMarkColor);
final ColorTween inactiveTickMarkEnableColor = new ColorTween(
begin: _sliderTheme.disabledInactiveTickMarkColor, end: _sliderTheme.inactiveTickMarkColor);
final Paint activeRailPaint = new Paint() final Paint activeRailPaint = new Paint()..color = activeRailEnableColor.evaluate(_enableAnimation);
..color = activeRailEnableColor.evaluate(_enableAnimation); final Paint inactiveRailPaint = new Paint()..color = inactiveRailEnableColor.evaluate(_enableAnimation);
final Paint inactiveRailPaint = new Paint() final Paint activeTickMarkPaint = new Paint()..color = activeTickMarkEnableColor.evaluate(_enableAnimation);
..color = inactiveRailEnableColor.evaluate(_enableAnimation); final Paint inactiveTickMarkPaint = new Paint()..color = inactiveTickMarkEnableColor.evaluate(_enableAnimation);
final Paint activeTickMarkPaint = new Paint()
..color = activeTickMarkEnableColor.evaluate(_enableAnimation);
final Paint inactiveTickMarkPaint = new Paint()
..color = inactiveTickMarkEnableColor.evaluate(_enableAnimation);
double visualPosition; double visualPosition;
Paint leftRailPaint; Paint leftRailPaint;
@ -732,12 +755,9 @@ class _RenderSlider extends RenderBox {
final double railBottom = railVerticalCenter + railRadius; final double railBottom = railVerticalCenter + railRadius;
final double railRight = railLeft + railLength; final double railRight = railLeft + railLength;
final double railActive = railLeft + railLength * visualPosition; final double railActive = railLeft + railLength * visualPosition;
final double thumbRadius = final double thumbRadius = _sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width / 2.0;
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width / 2.0; final double railActiveLeft = math.max(0.0, railActive - thumbRadius - thumbGap * (1.0 - _enableAnimation.value));
final double railActiveLeft = final double railActiveRight = math.min(railActive + thumbRadius + thumbGap * (1.0 - _enableAnimation.value), railRight);
math.max(0.0, railActive - thumbRadius - thumbGap * (1.0 - _enableAnimation.value));
final double railActiveRight =
math.min(railActive + thumbRadius + thumbGap * (1.0 - _enableAnimation.value), railRight);
final Rect railLeftRect = new Rect.fromLTRB(railLeft, railTop, railActiveLeft, railBottom); final Rect railLeftRect = new Rect.fromLTRB(railLeft, railTop, railActiveLeft, railBottom);
final Rect railRightRect = new Rect.fromLTRB(railActiveRight, railTop, railRight, railBottom); final Rect railRightRect = new Rect.fromLTRB(railActiveRight, railTop, railRight, railBottom);
@ -779,6 +799,7 @@ class _RenderSlider extends RenderBox {
} }
if (showValueIndicator) { if (showValueIndicator) {
_sliderTheme.valueIndicatorShape.paint( _sliderTheme.valueIndicatorShape.paint(
this,
context, context,
isDiscrete, isDiscrete,
thumbCenter, thumbCenter,
@ -787,13 +808,13 @@ class _RenderSlider extends RenderBox {
_labelPainter, _labelPainter,
_sliderTheme, _sliderTheme,
_textDirection, _textDirection,
_textScaleFactor,
value, value,
); );
} }
} }
_sliderTheme.thumbShape.paint( _sliderTheme.thumbShape.paint(
this,
context, context,
isDiscrete, isDiscrete,
thumbCenter, thumbCenter,
@ -802,7 +823,6 @@ class _RenderSlider extends RenderBox {
label != null ? _labelPainter : null, label != null ? _labelPainter : null,
_sliderTheme, _sliderTheme,
_textDirection, _textDirection,
_textScaleFactor,
value, value,
); );
} }

View File

@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
@ -49,20 +48,27 @@ class SliderTheme extends InheritedWidget {
/// Defaults to the ambient [ThemeData.sliderTheme] if there is no /// Defaults to the ambient [ThemeData.sliderTheme] if there is no
/// [SliderTheme] in the given build context. /// [SliderTheme] in the given build context.
/// ///
/// Typical usage is as follows: /// ## Sample code
/// ///
/// ```dart /// ```dart
/// double _rocketThrust; /// class Launch extends StatefulWidget {
/// @override
/// State createState() => new LaunchState();
/// }
/// ///
/// @override /// class LaunchState extends State<Launch> {
/// Widget build(BuildContext context) { /// double _rocketThrust;
/// return new SliderTheme( ///
/// data: SliderTheme.of(context).copyWith(activeRail: Colors.orange), /// @override
/// child: new Slider( /// Widget build(BuildContext context) {
/// onChanged: (double value) => setState(() => _rocketThrust = value), /// return new SliderTheme(
/// value: _rocketThrust; /// data: SliderTheme.of(context).copyWith(activeRailColor: const Color(0xff804040)),
/// ), /// child: new Slider(
/// ); /// onChanged: (double value) { setState(() { _rocketThrust = value; }); },
/// value: _rocketThrust,
/// ),
/// );
/// }
/// } /// }
/// ``` /// ```
/// ///
@ -147,7 +153,31 @@ class SliderThemeData extends Diagnosticable {
/// ///
/// The simplest way to create a SliderThemeData is to use /// The simplest way to create a SliderThemeData is to use
/// [copyWith] on the one you get from [SliderTheme.of], or create an /// [copyWith] on the one you get from [SliderTheme.of], or create an
/// entirely new one with [SliderThemeData.materialDefaults]. /// entirely new one with [SliderThemeData.fromPrimaryColors].
///
/// ## Sample code
///
/// ```dart
/// class Blissful extends StatefulWidget {
/// @override
/// State createState() => new BlissfulState();
/// }
///
/// class BlissfulState extends State<Blissful> {
/// double _bliss;
///
/// @override
/// Widget build(BuildContext context) {
/// return new SliderTheme(
/// data: SliderTheme.of(context).copyWith(activeRailColor: const Color(0xff404080)),
/// child: new Slider(
/// onChanged: (double value) { setState(() { _bliss = value; }); },
/// value: _bliss,
/// ),
/// );
/// }
/// }
/// ```
const SliderThemeData({ const SliderThemeData({
@required this.activeRailColor, @required this.activeRailColor,
@required this.inactiveRailColor, @required this.inactiveRailColor,
@ -189,7 +219,7 @@ class SliderThemeData extends Diagnosticable {
/// defaults when assigning them to the slider theme component colors. /// defaults when assigning them to the slider theme component colors.
/// ///
/// This is used to generate the default slider theme for a [ThemeData]. /// This is used to generate the default slider theme for a [ThemeData].
factory SliderThemeData.materialDefaults({ factory SliderThemeData.fromPrimaryColors({
@required Color primaryColor, @required Color primaryColor,
@required Color primaryColorDark, @required Color primaryColorDark,
@required Color primaryColorLight, @required Color primaryColorLight,
@ -238,22 +268,69 @@ class SliderThemeData extends Diagnosticable {
); );
} }
/// The color of the [Slider] rail between the [Slider.min] position and the
/// current thumb position.
final Color activeRailColor; final Color activeRailColor;
/// The color of the [Slider] rail between the current thumb position and the
/// [Slider.max] position.
final Color inactiveRailColor; final Color inactiveRailColor;
/// The color of the [Slider] rail between the [Slider.min] position and the
/// current thumb position when the [Slider] is disabled.
final Color disabledActiveRailColor; final Color disabledActiveRailColor;
/// The color of the [Slider] rail between the current thumb position and the
/// [Slider.max] position when the [Slider] is disabled.
final Color disabledInactiveRailColor; final Color disabledInactiveRailColor;
/// The color of the rail's tick marks that are drawn between the [Slider.min]
/// position and the current thumb position.
final Color activeTickMarkColor; final Color activeTickMarkColor;
/// The color of the rail's tick marks that are drawn between the current
/// thumb position and the [Slider.max] position.
final Color inactiveTickMarkColor; final Color inactiveTickMarkColor;
/// The color of the rail's tick marks that are drawn between the [Slider.min]
/// position and the current thumb position when the [Slider] is disabled.
final Color disabledActiveTickMarkColor; final Color disabledActiveTickMarkColor;
/// The color of the rail's tick marks that are drawn between the current
/// thumb position and the [Slider.max] position when the [Slider] is
/// disabled.
final Color disabledInactiveTickMarkColor; final Color disabledInactiveTickMarkColor;
/// The color given to the [thumbShape] to draw itself with.
final Color thumbColor; final Color thumbColor;
/// The color given to the [thumbShape] to draw itself with when the
/// [Slider] is disabled.
final Color disabledThumbColor; final Color disabledThumbColor;
/// The color of the overlay drawn around the slider thumb when it is pressed.
///
/// This is typically a semi-transparent color.
final Color overlayColor; final Color overlayColor;
/// The color given to the [valueIndicatorShape] to draw itself with.
final Color valueIndicatorColor; final Color valueIndicatorColor;
/// The shape and behavior that will be used to draw the [Slider]'s thumb.
///
/// This can be customized by implementing a subclass of
/// [SliderComponentShape].
final SliderComponentShape thumbShape; final SliderComponentShape thumbShape;
/// The shape and behavior that will be used to draw the [Slider]'s value
/// indicator.
///
/// This can be customized by implementing a subclass of
/// [SliderComponentShape].
final SliderComponentShape valueIndicatorShape; final SliderComponentShape valueIndicatorShape;
/// Whether the value indicator should be shown for different types of sliders. /// Whether the value indicator should be shown for different types of
/// sliders.
/// ///
/// By default, [showValueIndicator] is set to /// By default, [showValueIndicator] is set to
/// [ShowValueIndicator.onlyForDiscrete]. The value indicator is only shown /// [ShowValueIndicator.onlyForDiscrete]. The value indicator is only shown
@ -285,8 +362,7 @@ class SliderThemeData extends Diagnosticable {
activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor, activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor,
inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor, inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor,
disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor, disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor,
disabledInactiveTickMarkColor: disabledInactiveTickMarkColor: disabledInactiveTickMarkColor ?? this.disabledInactiveTickMarkColor,
disabledInactiveTickMarkColor ?? this.disabledInactiveTickMarkColor,
thumbColor: thumbColor ?? this.thumbColor, thumbColor: thumbColor ?? this.thumbColor,
disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor, disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
overlayColor: overlayColor ?? this.overlayColor, overlayColor: overlayColor ?? this.overlayColor,
@ -320,14 +396,11 @@ class SliderThemeData extends Diagnosticable {
activeRailColor: Color.lerp(a.activeRailColor, b.activeRailColor, t), activeRailColor: Color.lerp(a.activeRailColor, b.activeRailColor, t),
inactiveRailColor: Color.lerp(a.inactiveRailColor, b.inactiveRailColor, t), inactiveRailColor: Color.lerp(a.inactiveRailColor, b.inactiveRailColor, t),
disabledActiveRailColor: Color.lerp(a.disabledActiveRailColor, b.disabledActiveRailColor, t), disabledActiveRailColor: Color.lerp(a.disabledActiveRailColor, b.disabledActiveRailColor, t),
disabledInactiveRailColor: disabledInactiveRailColor: Color.lerp(a.disabledInactiveRailColor, b.disabledInactiveRailColor, t),
Color.lerp(a.disabledInactiveRailColor, b.disabledInactiveRailColor, t),
activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t), activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t),
inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t), inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t),
disabledActiveTickMarkColor: disabledActiveTickMarkColor: Color.lerp(a.disabledActiveTickMarkColor, b.disabledActiveTickMarkColor, t),
Color.lerp(a.disabledActiveTickMarkColor, b.disabledActiveTickMarkColor, t), disabledInactiveTickMarkColor: Color.lerp(a.disabledInactiveTickMarkColor, b.disabledInactiveTickMarkColor, t),
disabledInactiveTickMarkColor:
Color.lerp(a.disabledInactiveTickMarkColor, b.disabledInactiveTickMarkColor, t),
thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t),
disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t), disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t),
@ -361,6 +434,9 @@ class SliderThemeData extends Diagnosticable {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) { if (other.runtimeType != runtimeType) {
return false; return false;
} }
@ -386,47 +462,26 @@ class SliderThemeData extends Diagnosticable {
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
final ThemeData defaultTheme = new ThemeData.fallback(); final ThemeData defaultTheme = new ThemeData.fallback();
final SliderThemeData defaultData = new SliderThemeData.materialDefaults( final SliderThemeData defaultData = new SliderThemeData.fromPrimaryColors(
primaryColor: defaultTheme.primaryColor, primaryColor: defaultTheme.primaryColor,
primaryColorDark: defaultTheme.primaryColorDark, primaryColorDark: defaultTheme.primaryColorDark,
primaryColorLight: defaultTheme.primaryColorLight, primaryColorLight: defaultTheme.primaryColorLight,
); );
description.add(new DiagnosticsProperty<Color>('activeRailColor', activeRailColor, description.add(new DiagnosticsProperty<Color>('activeRailColor', activeRailColor, defaultValue: defaultData.activeRailColor));
defaultValue: defaultData.activeRailColor)); description.add(new DiagnosticsProperty<Color>('inactiveRailColor', inactiveRailColor, defaultValue: defaultData.inactiveRailColor));
description.add(new DiagnosticsProperty<Color>('inactiveRailColor', inactiveRailColor, description.add(new DiagnosticsProperty<Color>('disabledActiveRailColor', disabledActiveRailColor, defaultValue: defaultData.disabledActiveRailColor));
defaultValue: defaultData.inactiveRailColor)); description.add(new DiagnosticsProperty<Color>('disabledInactiveRailColor', disabledInactiveRailColor, defaultValue: defaultData.disabledInactiveRailColor));
description.add(new DiagnosticsProperty<Color>( description.add(new DiagnosticsProperty<Color>('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor));
'disabledActiveRailColor', disabledActiveRailColor, description.add(new DiagnosticsProperty<Color>('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor));
defaultValue: defaultData.disabledActiveRailColor)); description.add(new DiagnosticsProperty<Color>('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor));
description.add(new DiagnosticsProperty<Color>( description.add(new DiagnosticsProperty<Color>('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor));
'disabledInactiveRailColor', disabledInactiveRailColor, description.add(new DiagnosticsProperty<Color>('thumbColor', thumbColor, defaultValue: defaultData.thumbColor));
defaultValue: defaultData.disabledInactiveRailColor)); description.add(new DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor));
description.add(new DiagnosticsProperty<Color>('activeTickMarkColor', activeTickMarkColor, description.add(new DiagnosticsProperty<Color>('overlayColor', overlayColor, defaultValue: defaultData.overlayColor));
defaultValue: defaultData.activeTickMarkColor)); description.add(new DiagnosticsProperty<Color>('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor));
description.add(new DiagnosticsProperty<Color>('inactiveTickMarkColor', inactiveTickMarkColor, description.add(new DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape));
defaultValue: defaultData.inactiveTickMarkColor)); description.add(new DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape));
description.add(new DiagnosticsProperty<Color>( description.add(new DiagnosticsProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator));
'disabledActiveTickMarkColor', disabledActiveTickMarkColor,
defaultValue: defaultData.disabledActiveTickMarkColor));
description.add(new DiagnosticsProperty<Color>(
'disabledInactiveTickMarkColor', disabledInactiveTickMarkColor,
defaultValue: defaultData.disabledInactiveTickMarkColor));
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>('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));
} }
} }
@ -460,6 +515,7 @@ abstract class SliderComponentShape {
/// passed is null, then no label was supplied to the [Slider]. /// passed is null, then no label was supplied to the [Slider].
/// [value] is the current parametric value (from 0.0 to 1.0) of the slider. /// [value] is the current parametric value (from 0.0 to 1.0) of the slider.
void paint( void paint(
RenderBox parentBox,
PaintingContext context, PaintingContext context,
bool isDiscrete, bool isDiscrete,
Offset thumbCenter, Offset thumbCenter,
@ -468,7 +524,6 @@ abstract class SliderComponentShape {
TextPainter labelPainter, TextPainter labelPainter,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
double textScaleFactor,
double value, double value,
); );
} }
@ -493,6 +548,7 @@ class RoundSliderThumbShape extends SliderComponentShape {
@override @override
void paint( void paint(
RenderBox parentBox,
PaintingContext context, PaintingContext context,
bool isDiscrete, bool isDiscrete,
Offset thumbCenter, Offset thumbCenter,
@ -501,14 +557,17 @@ class RoundSliderThumbShape extends SliderComponentShape {
TextPainter labelPainter, TextPainter labelPainter,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
double textScaleFactor,
double value, double value,
) { ) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final Tween<double> radiusTween = final Tween<double> radiusTween = new Tween<double>(
new Tween<double>(begin: _disabledThumbRadius, end: _thumbRadius); begin: _disabledThumbRadius,
final ColorTween colorTween = end: _thumbRadius,
new ColorTween(begin: sliderTheme.disabledThumbColor, end: sliderTheme.thumbColor); );
final ColorTween colorTween = new ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
canvas.drawCircle( canvas.drawCircle(
thumbCenter, thumbCenter,
radiusTween.evaluate(enableAnimation), radiusTween.evaluate(enableAnimation),
@ -536,6 +595,9 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
// Radius of the top lobe of the value indicator. // Radius of the top lobe of the value indicator.
static const double _topLobeRadius = 16.0; 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.
static const double _labelTextDesignSize = 14.0;
// Radius of the bottom lobe of the value indicator. // Radius of the bottom lobe of the value indicator.
static const double _bottomLobeRadius = 6.0; static const double _bottomLobeRadius = 6.0;
// The starting angle for the bottom lobe. Picked to get the desired // The starting angle for the bottom lobe. Picked to get the desired
@ -557,15 +619,14 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
static const double _twoSeventyDegrees = 3.0 * math.pi / 2.0; static const double _twoSeventyDegrees = 3.0 * math.pi / 2.0;
static const double _ninetyDegrees = math.pi / 2.0; static const double _ninetyDegrees = math.pi / 2.0;
static const double _thirtyDegrees = math.pi / 6.0; static const double _thirtyDegrees = math.pi / 6.0;
static const Size preferredSize = static const Size _preferredSize =
const Size.fromHeight(_distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius); const Size.fromHeight(_distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius);
static final Tween<double> _slideUpTween = new Tween<double>(begin: 0.0, end: 1.0);
static Path _bottomLobePath; // Initialized by _generateBottomLobe static Path _bottomLobePath; // Initialized by _generateBottomLobe
static Offset _bottomLobeEnd; // Initialized by _generateBottomLobe static Offset _bottomLobeEnd; // Initialized by _generateBottomLobe
@override @override
Size getPreferredSize(bool isEnabled, bool isDiscrete) => preferredSize; Size getPreferredSize(bool isEnabled, bool isDiscrete) => _preferredSize;
// Adds an arc to the path that has the attributes passed in. This is // Adds an arc to the path that has the attributes passed in. This is
// a convenience to make adding arcs have less boilerplate. // a convenience to make adding arcs have less boilerplate.
@ -638,20 +699,71 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
return _bottomLobeEnd; return _bottomLobeEnd;
} }
void _drawValueIndicator(Canvas canvas, Offset center, Paint paint, double scale, // Determines the "best" offset to keep the bubble on the screen. The calling
TextPainter labelPainter, double textScaleFactor) { // code will bound that with the available movement in the paddle shape.
double _getIdealOffset(
RenderBox parentBox,
double halfWidthNeeded,
double scale,
Offset center,
) {
const double edgeMargin = 4.0;
final Rect topLobeRect = new Rect.fromLTWH(
-_topLobeRadius - halfWidthNeeded,
-_topLobeRadius - _distanceBetweenTopBottomCenters,
2.0 * (_topLobeRadius + halfWidthNeeded),
2.0 * _topLobeRadius,
);
// We can just multiply by scale instead of a transform, since we're scaling
// around (0, 0).
final Offset topLeft = (topLobeRect.topLeft * scale) + center;
final Offset bottomRight = (topLobeRect.bottomRight * scale) + center;
double shift = 0.0;
if (topLeft.dx < edgeMargin) {
shift = edgeMargin - topLeft.dx;
}
if (bottomRight.dx > parentBox.size.width - edgeMargin) {
shift = parentBox.size.width - bottomRight.dx - edgeMargin;
}
shift = (scale == 0.0 ? 0.0 : shift / scale);
return shift;
}
void _drawValueIndicator(
RenderBox parentBox,
Canvas canvas,
Offset center,
Paint paint,
double scale,
TextPainter labelPainter,
) {
canvas.save(); canvas.save();
canvas.translate(center.dx, center.dy); canvas.translate(center.dx, center.dy);
// The entire value indicator should scale with the text scale factor, // The entire value indicator should scale with the size of the label,
// to keep it large enough to encompass the label text. // to keep it large enough to encompass the label text.
canvas.scale(scale * textScaleFactor, scale * textScaleFactor); final double textScaleFactor = labelPainter.height / _labelTextDesignSize;
final double inverseTextScale = 1.0 / textScaleFactor; final double overallScale = scale * textScaleFactor;
canvas.scale(overallScale, overallScale);
final double inverseTextScale = textScaleFactor != 0 ? 1.0 / textScaleFactor : 0.0;
final double labelHalfWidth = labelPainter.width / 2.0; final double labelHalfWidth = labelPainter.width / 2.0;
// This is the needed extra width for the label. It is only positive when // This is the needed extra width for the label. It is only positive when
// the label exceeds the minimum size contained by the round top lobe. // the label exceeds the minimum size contained by the round top lobe.
final double halfWidthNeeded = final double halfWidthNeeded = math.max(
math.max(0.0, inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding)); 0.0,
inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding),
);
double shift = _getIdealOffset(parentBox, halfWidthNeeded, overallScale, center);
double leftWidthNeeded;
double rightWidthNeeded;
if (shift < 0.0) { // shifting to the left
shift = math.max(shift, -halfWidthNeeded);
} else { // shifting to the right
shift = math.min(shift, halfWidthNeeded);
}
rightWidthNeeded = halfWidthNeeded + shift;
leftWidthNeeded = halfWidthNeeded - shift;
final Path path = new Path(); final Path path = new Path();
final Offset bottomLobeEnd = _addBottomLobe(path); final Offset bottomLobeEnd = _addBottomLobe(path);
@ -660,39 +772,57 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
final double neckTriangleBase = _topNeckRadius - bottomLobeEnd.dx; final double neckTriangleBase = _topNeckRadius - bottomLobeEnd.dx;
// The parameter that describes how far along the transition from round to // The parameter that describes how far along the transition from round to
// stretched we are. // stretched we are.
final double t = math.max(0.0, math.min(1.0, halfWidthNeeded / neckTriangleBase)); final double leftAmount = math.max(0.0, math.min(1.0, leftWidthNeeded / neckTriangleBase));
final double rightAmount = math.max(0.0, math.min(1.0, rightWidthNeeded / neckTriangleBase));
// The angle between the top neck arc's center and the top lobe's center // The angle between the top neck arc's center and the top lobe's center
// and vertical. // and vertical.
final double theta = (1.0 - t) * _thirtyDegrees; final double leftTheta = (1.0 - leftAmount) * _thirtyDegrees;
final double rightTheta = (1.0 - rightAmount) * _thirtyDegrees;
// The center of the top left neck arc. // The center of the top left neck arc.
final Offset neckLeftCenter = new Offset( final Offset neckLeftCenter = new Offset(
-neckTriangleBase, _topLobeCenter.dy + math.cos(theta) * _neckTriangleHypotenuse); -neckTriangleBase,
final Offset topLobeShift = new Offset(halfWidthNeeded, 0.0); _topLobeCenter.dy + math.cos(leftTheta) * _neckTriangleHypotenuse,
final double neckArcAngle = _ninetyDegrees - theta; );
final Offset neckRightCenter = new Offset(
neckTriangleBase,
_topLobeCenter.dy + math.cos(rightTheta) * _neckTriangleHypotenuse,
);
final double leftNeckArcAngle = _ninetyDegrees - leftTheta;
final double rightNeckArcAngle = math.pi + _ninetyDegrees - rightTheta;
_addArc( _addArc(
path, path,
neckLeftCenter, neckLeftCenter,
_topNeckRadius, _topNeckRadius,
0.0, 0.0,
-neckArcAngle, -leftNeckArcAngle,
);
_addArc(
path,
_topLobeCenter - new Offset(leftWidthNeeded, 0.0),
_topLobeRadius,
_ninetyDegrees + leftTheta,
_twoSeventyDegrees,
);
_addArc(
path,
_topLobeCenter + new Offset(rightWidthNeeded, 0.0),
_topLobeRadius,
_twoSeventyDegrees,
_twoSeventyDegrees + math.pi - rightTheta,
); );
_addArc(path, _topLobeCenter - topLobeShift, _topLobeRadius, _ninetyDegrees + theta,
_twoSeventyDegrees);
_addArc(path, _topLobeCenter + topLobeShift, _topLobeRadius, _twoSeventyDegrees,
_twoSeventyDegrees + math.pi - theta);
final Offset neckRightCenter = new Offset(-neckLeftCenter.dx, neckLeftCenter.dy);
_addArc( _addArc(
path, path,
neckRightCenter, neckRightCenter,
_topNeckRadius, _topNeckRadius,
math.pi + neckArcAngle, rightNeckArcAngle,
math.pi, math.pi,
); );
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
// Draw the label. // Draw the label.
canvas.save(); canvas.save();
canvas.translate(0.0, -_distanceBetweenTopBottomCenters); canvas.translate(shift, -_distanceBetweenTopBottomCenters);
canvas.scale(inverseTextScale, inverseTextScale); canvas.scale(inverseTextScale, inverseTextScale);
labelPainter.paint(canvas, Offset.zero - new Offset(labelHalfWidth, labelPainter.height / 2.0)); labelPainter.paint(canvas, Offset.zero - new Offset(labelHalfWidth, labelPainter.height / 2.0));
canvas.restore(); canvas.restore();
@ -701,6 +831,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
@override @override
void paint( void paint(
RenderBox parentBox,
PaintingContext context, PaintingContext context,
bool isDiscrete, bool isDiscrete,
Offset thumbCenter, Offset thumbCenter,
@ -709,21 +840,20 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
TextPainter labelPainter, TextPainter labelPainter,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
double textScaleFactor,
double value, double value,
) { ) {
assert(labelPainter != null); assert(labelPainter != null);
final ColorTween colorTween =
new ColorTween(begin: Colors.transparent, end: sliderTheme.valueIndicatorColor);
final ColorTween enableColor = new ColorTween( final ColorTween enableColor = new ColorTween(
begin: sliderTheme.disabledThumbColor, end: colorTween.evaluate(activationAnimation)); begin: sliderTheme.disabledThumbColor,
end: sliderTheme.valueIndicatorColor,
);
_drawValueIndicator( _drawValueIndicator(
parentBox,
context.canvas, context.canvas,
thumbCenter, thumbCenter,
new Paint()..color = enableColor.evaluate(enableAnimation), new Paint()..color = enableColor.evaluate(enableAnimation),
_slideUpTween.evaluate(activationAnimation), activationAnimation.value,
labelPainter, labelPainter,
textScaleFactor,
); );
} }
} }

View File

@ -149,15 +149,9 @@ class ThemeData extends Diagnosticable {
hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000); hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
errorColor ??= Colors.red[700]; errorColor ??= Colors.red[700];
inputDecorationTheme ??= const InputDecorationTheme(); inputDecorationTheme ??= const InputDecorationTheme();
iconTheme ??= isDark iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
? const IconThemeData(color: Colors.white) primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
: const IconThemeData(color: Colors.black); accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
primaryIconTheme ??= primaryIsDark
? const IconThemeData(color: Colors.white)
: const IconThemeData(color: Colors.black);
accentIconTheme ??= accentIsDark
? const IconThemeData(color: Colors.white)
: const IconThemeData(color: Colors.black);
platform ??= defaultTargetPlatform; platform ??= defaultTargetPlatform;
final Typography typography = new Typography(platform: platform); final Typography typography = new Typography(platform: platform);
textTheme ??= isDark ? typography.white : typography.black; textTheme ??= isDark ? typography.white : typography.black;
@ -168,7 +162,7 @@ class ThemeData extends Diagnosticable {
primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily); primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily);
accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily); accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily);
} }
sliderTheme ??= new SliderThemeData.materialDefaults( sliderTheme ??= new SliderThemeData.fromPrimaryColors(
primaryColor: primaryColor, primaryColor: primaryColor,
primaryColorLight: primaryColorLight, primaryColorLight: primaryColorLight,
primaryColorDark: primaryColorDark, primaryColorDark: primaryColorDark,
@ -721,43 +715,43 @@ class ThemeData extends Diagnosticable {
@override @override
int get hashCode { int get hashCode {
return hashValues( return hashValues(
brightness, brightness,
primaryColor, primaryColor,
primaryColorBrightness, primaryColorBrightness,
canvasColor, canvasColor,
scaffoldBackgroundColor, scaffoldBackgroundColor,
bottomAppBarColor, bottomAppBarColor,
cardColor, cardColor,
dividerColor, dividerColor,
highlightColor, highlightColor,
splashColor, splashColor,
splashFactory, splashFactory,
selectedRowColor, selectedRowColor,
unselectedWidgetColor, unselectedWidgetColor,
disabledColor, disabledColor,
buttonColor, buttonColor,
buttonTheme, buttonTheme,
secondaryHeaderColor, secondaryHeaderColor,
textSelectionColor, textSelectionColor,
textSelectionHandleColor, textSelectionHandleColor,
hashValues( // Too many values. hashValues( // Too many values.
backgroundColor, backgroundColor,
accentColor, accentColor,
accentColorBrightness, accentColorBrightness,
indicatorColor, indicatorColor,
dialogBackgroundColor, dialogBackgroundColor,
hintColor, hintColor,
errorColor, errorColor,
textTheme, textTheme,
primaryTextTheme, primaryTextTheme,
accentTextTheme, accentTextTheme,
iconTheme, iconTheme,
inputDecorationTheme, inputDecorationTheme,
primaryIconTheme, primaryIconTheme,
accentIconTheme, accentIconTheme,
sliderTheme, sliderTheme,
platform, platform,
), ),
); );
} }
@ -765,64 +759,36 @@ class ThemeData extends Diagnosticable {
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
final ThemeData defaultData = new ThemeData.fallback(); final ThemeData defaultData = new ThemeData.fallback();
description.add(new EnumProperty<TargetPlatform>('platform', platform, description.add(new EnumProperty<TargetPlatform>('platform', platform, defaultValue: defaultTargetPlatform));
defaultValue: defaultTargetPlatform)); description.add(new EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness));
description.add(new EnumProperty<Brightness>('brightness', brightness, description.add(new DiagnosticsProperty<Color>('primaryColor', primaryColor, defaultValue: defaultData.primaryColor));
defaultValue: defaultData.brightness)); description.add(new EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness));
description.add(new DiagnosticsProperty<Color>('primaryColor', primaryColor, description.add(new DiagnosticsProperty<Color>('accentColor', accentColor, defaultValue: defaultData.accentColor));
defaultValue: defaultData.primaryColor)); description.add(new EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, defaultValue: defaultData.accentColorBrightness));
description.add(new EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, description.add(new DiagnosticsProperty<Color>('canvasColor', canvasColor, defaultValue: defaultData.canvasColor));
defaultValue: defaultData.primaryColorBrightness)); description.add(new DiagnosticsProperty<Color>('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
description.add(new DiagnosticsProperty<Color>('accentColor', accentColor, description.add(new DiagnosticsProperty<Color>('bottomAppBarColor', bottomAppBarColor, defaultValue: defaultData.bottomAppBarColor));
defaultValue: defaultData.accentColor)); description.add(new DiagnosticsProperty<Color>('cardColor', cardColor, defaultValue: defaultData.cardColor));
description.add(new EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, description.add(new DiagnosticsProperty<Color>('dividerColor', dividerColor, defaultValue: defaultData.dividerColor));
defaultValue: defaultData.accentColorBrightness)); description.add(new DiagnosticsProperty<Color>('highlightColor', highlightColor, defaultValue: defaultData.highlightColor));
description.add(new DiagnosticsProperty<Color>('canvasColor', canvasColor, description.add(new DiagnosticsProperty<Color>('splashColor', splashColor, defaultValue: defaultData.splashColor));
defaultValue: defaultData.canvasColor)); description.add(new DiagnosticsProperty<Color>('selectedRowColor', selectedRowColor, defaultValue: defaultData.selectedRowColor));
description.add(new DiagnosticsProperty<Color>( description.add(new DiagnosticsProperty<Color>('unselectedWidgetColor', unselectedWidgetColor, defaultValue: defaultData.unselectedWidgetColor));
'scaffoldBackgroundColor', scaffoldBackgroundColor, description.add(new DiagnosticsProperty<Color>('disabledColor', disabledColor, defaultValue: defaultData.disabledColor));
defaultValue: defaultData.scaffoldBackgroundColor)); description.add(new DiagnosticsProperty<Color>('buttonColor', buttonColor, defaultValue: defaultData.buttonColor));
description.add(new DiagnosticsProperty<Color>('bottomAppBarColor', bottomAppBarColor, description.add(new DiagnosticsProperty<Color>('secondaryHeaderColor', secondaryHeaderColor, defaultValue: defaultData.secondaryHeaderColor));
defaultValue: defaultData.bottomAppBarColor)); description.add(new DiagnosticsProperty<Color>('textSelectionColor', textSelectionColor, defaultValue: defaultData.textSelectionColor));
description.add(new DiagnosticsProperty<Color>('cardColor', cardColor, description.add(new DiagnosticsProperty<Color>('textSelectionHandleColor', textSelectionHandleColor, defaultValue: defaultData.textSelectionHandleColor));
defaultValue: defaultData.cardColor)); description.add(new DiagnosticsProperty<Color>('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
description.add(new DiagnosticsProperty<Color>('dividerColor', dividerColor, description.add(new DiagnosticsProperty<Color>('dialogBackgroundColor', dialogBackgroundColor, defaultValue: defaultData.dialogBackgroundColor));
defaultValue: defaultData.dividerColor)); description.add(new DiagnosticsProperty<Color>('indicatorColor', indicatorColor, defaultValue: defaultData.indicatorColor));
description.add(new DiagnosticsProperty<Color>('highlightColor', highlightColor, description.add(new DiagnosticsProperty<Color>('hintColor', hintColor, defaultValue: defaultData.hintColor));
defaultValue: defaultData.highlightColor)); description.add(new DiagnosticsProperty<Color>('errorColor', errorColor, defaultValue: defaultData.errorColor));
description.add(new DiagnosticsProperty<Color>('splashColor', splashColor,
defaultValue: defaultData.splashColor));
description.add(new DiagnosticsProperty<Color>('selectedRowColor', selectedRowColor,
defaultValue: defaultData.selectedRowColor));
description.add(new DiagnosticsProperty<Color>('unselectedWidgetColor', unselectedWidgetColor,
defaultValue: defaultData.unselectedWidgetColor));
description.add(new DiagnosticsProperty<Color>('disabledColor', disabledColor,
defaultValue: defaultData.disabledColor));
description.add(new DiagnosticsProperty<Color>('buttonColor', buttonColor,
defaultValue: defaultData.buttonColor));
description.add(new DiagnosticsProperty<Color>('secondaryHeaderColor', secondaryHeaderColor,
defaultValue: defaultData.secondaryHeaderColor));
description.add(new DiagnosticsProperty<Color>('textSelectionColor', textSelectionColor,
defaultValue: defaultData.textSelectionColor));
description.add(new DiagnosticsProperty<Color>(
'textSelectionHandleColor', textSelectionHandleColor,
defaultValue: defaultData.textSelectionHandleColor));
description.add(new DiagnosticsProperty<Color>('backgroundColor', backgroundColor,
defaultValue: defaultData.backgroundColor));
description.add(new DiagnosticsProperty<Color>('dialogBackgroundColor', dialogBackgroundColor,
defaultValue: defaultData.dialogBackgroundColor));
description.add(new DiagnosticsProperty<Color>('indicatorColor', indicatorColor,
defaultValue: defaultData.indicatorColor));
description.add(new DiagnosticsProperty<Color>('hintColor', hintColor,
defaultValue: defaultData.hintColor));
description.add(new DiagnosticsProperty<Color>('errorColor', errorColor,
defaultValue: defaultData.errorColor));
description.add(new DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme)); description.add(new DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme));
description.add(new DiagnosticsProperty<TextTheme>('textTheme', textTheme)); description.add(new DiagnosticsProperty<TextTheme>('textTheme', textTheme));
description.add(new DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme)); description.add(new DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme));
description.add(new DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme)); description.add(new DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme));
description.add(new DiagnosticsProperty<InputDecorationTheme>( description.add(new DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme));
'inputDecorationTheme', inputDecorationTheme));
description.add(new DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme)); description.add(new DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme));
description.add(new DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme)); description.add(new DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme));
description.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme)); description.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
@ -846,8 +812,7 @@ class _IdentityThemeDataCacheKey {
// We are explicitly ignoring the possibility that the types might not // We are explicitly ignoring the possibility that the types might not
// match in the interests of speed. // match in the interests of speed.
final _IdentityThemeDataCacheKey otherKey = other; final _IdentityThemeDataCacheKey otherKey = other;
return identical(baseTheme, otherKey.baseTheme) && return identical(baseTheme, otherKey.baseTheme) && identical(localTextGeometry, otherKey.localTextGeometry);
identical(localTextGeometry, otherKey.localTextGeometry);
} }
} }

View File

@ -34,7 +34,10 @@ class IconThemeData extends Diagnosticable {
/// the new values. /// the new values.
IconThemeData copyWith({Color color, double opacity, double size}) { IconThemeData copyWith({Color color, double opacity, double size}) {
return new IconThemeData( return new IconThemeData(
color: color ?? this.color, opacity: opacity ?? this.opacity, size: size ?? this.size); color: color ?? this.color,
opacity: opacity ?? this.opacity,
size: size ?? this.size,
);
} }
/// Returns a new icon theme that matches this icon theme but with some values /// Returns a new icon theme that matches this icon theme but with some values
@ -43,7 +46,11 @@ class IconThemeData extends Diagnosticable {
IconThemeData merge(IconThemeData other) { IconThemeData merge(IconThemeData other) {
if (other == null) if (other == null)
return this; return this;
return copyWith(color: other.color, opacity: other.opacity, size: other.size); return copyWith(
color: other.color,
opacity: other.opacity,
size: other.size,
);
} }
/// Whether all the properties of this object are non-null. /// Whether all the properties of this object are non-null.
@ -97,11 +104,8 @@ class IconThemeData extends Diagnosticable {
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
if (color == null && _opacity == null && size == null) { description.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null));
return; description.add(new DoubleProperty('opacity', opacity, defaultValue: null));
} description.add(new DoubleProperty('size', size, defaultValue: null));
description.add(new DiagnosticsProperty<Color>('color', color));
description.add(new DoubleProperty('opacity', _opacity));
description.add(new DoubleProperty('size', size));
} }
} }

View File

@ -12,6 +12,36 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart'; import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
// A thumb shape that also logs its repaint center.
class LoggingThumbShape extends SliderComponentShape {
LoggingThumbShape(this.log);
final List<Offset> log;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return const Size(10.0, 10.0);
}
@override
void paint(
RenderBox parentBox,
PaintingContext context,
bool isDiscrete,
Offset thumbCenter,
Animation<double> activationAnimation,
Animation<double> enableAnimation,
TextPainter labelPainter,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
) {
log.add(thumbCenter);
final Paint thumbPaint = new Paint()..color = Colors.red;
context.canvas.drawCircle(thumbCenter, 5.0, thumbPaint);
}
}
void main() { void main() {
testWidgets('Slider can move when tapped (LTR)', (WidgetTester tester) async { testWidgets('Slider can move when tapped (LTR)', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey(); final Key sliderKey = new UniqueKey();
@ -22,16 +52,19 @@ void main() {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new StatefulBuilder( child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Slider( child: new Material(
key: sliderKey, child: new Center(
value: value, child: new Slider(
onChanged: (double newValue) { key: sliderKey,
setState(() { value: value,
value = newValue; onChanged: (double newValue) {
}); setState(() {
}, value = newValue;
});
},
),
), ),
), ),
); );
@ -65,16 +98,19 @@ void main() {
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: new StatefulBuilder( child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Slider( child: new Material(
key: sliderKey, child: new Center(
value: value, child: new Slider(
onChanged: (double newValue) { key: sliderKey,
setState(() { value: value,
value = newValue; onChanged: (double newValue) {
}); setState(() {
}, value = newValue;
});
},
),
), ),
), ),
); );
@ -99,6 +135,117 @@ void main() {
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
}); });
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(343.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));
// 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 take on discrete values', (WidgetTester tester) async { testWidgets('Slider take on discrete values', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey(); final Key sliderKey = new UniqueKey();
double value = 0.0; double value = 0.0;
@ -108,21 +255,24 @@ void main() {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new StatefulBuilder( child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new SizedBox( child: new Material(
width: 144.0 + 2 * 16.0, // _kPreferredTotalWidth child: new Center(
child: new Slider( child: new SizedBox(
key: sliderKey, width: 144.0 + 2 * 16.0, // _kPreferredTotalWidth
min: 0.0, child: new Slider(
max: 100.0, key: sliderKey,
divisions: 10, min: 0.0,
value: value, max: 100.0,
onChanged: (double newValue) { divisions: 10,
setState(() { value: value,
value = newValue; onChanged: (double newValue) {
}); setState(() {
}, value = newValue;
});
},
),
), ),
), ),
), ),
@ -154,14 +304,17 @@ void main() {
final List<double> log = <double>[]; final List<double> log = <double>[];
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Slider( data: new MediaQueryData.fromWindow(window),
value: 0.0, child: new Material(
min: 0.0, child: new Slider(
max: 1.0, value: 0.0,
onChanged: (double newValue) { min: 0.0,
log.add(newValue); max: 1.0,
}, onChanged: (double newValue) {
log.add(newValue);
},
),
), ),
), ),
)); ));
@ -172,14 +325,17 @@ void main() {
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Slider( data: new MediaQueryData.fromWindow(window),
value: 0.0, child: new Material(
min: 0.0, child: new Slider(
max: 0.0, value: 0.0,
onChanged: (double newValue) { min: 0.0,
log.add(newValue); max: 0.0,
}, onChanged: (double newValue) {
log.add(newValue);
},
),
), ),
), ),
)); ));
@ -189,8 +345,7 @@ void main() {
log.clear(); log.clear();
}); });
testWidgets('Slider uses the right theme colors for the right components', testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async {
(WidgetTester tester) async {
const Color customColor1 = const Color(0xcafefeed); const Color customColor1 = const Color(0xcafefeed);
const Color customColor2 = const Color(0xdeadbeef); const Color customColor2 = const Color(0xdeadbeef);
final ThemeData theme = new ThemeData( final ThemeData theme = new ThemeData(
@ -212,17 +367,20 @@ void main() {
}; };
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Theme( child: new Material(
data: theme, child: new Center(
child: new Slider( child: new Theme(
value: value, data: theme,
label: '$value', child: new Slider(
divisions: divisions, value: value,
activeColor: activeColor, label: '$value',
inactiveColor: inactiveColor, divisions: divisions,
onChanged: onChanged, activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
),
), ),
), ),
), ),
@ -235,11 +393,7 @@ void main() {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
// Check default theme for enabled widget. // Check default theme for enabled widget.
expect( expect(sliderBox, paints..rect(color: sliderTheme.activeRailColor)..rect(color: sliderTheme.inactiveRailColor));
sliderBox,
paints
..rect(color: sliderTheme.activeRailColor)
..rect(color: sliderTheme.inactiveRailColor));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor)); expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
@ -249,8 +403,7 @@ void main() {
// Test setting only the activeColor. // Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1)); await tester.pumpWidget(buildApp(activeColor: customColor1));
expect( expect(sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveRailColor));
sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveRailColor));
expect(sliderBox, paints..circle(color: customColor1)); expect(sliderBox, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
@ -276,11 +429,7 @@ void main() {
// Test colors for discrete slider. // Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3)); await tester.pumpWidget(buildApp(divisions: 3));
expect( expect(sliderBox, paints..rect(color: sliderTheme.activeRailColor)..rect(color: sliderTheme.inactiveRailColor));
sliderBox,
paints
..rect(color: sliderTheme.activeRailColor)
..rect(color: sliderTheme.inactiveRailColor));
expect( expect(
sliderBox, sliderBox,
paints paints
@ -294,8 +443,7 @@ void main() {
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
// Test colors for discrete slider with inactiveColor and activeColor set. // Test colors for discrete slider with inactiveColor and activeColor set.
await tester await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, divisions: 3));
.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, divisions: 3));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2)); expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
expect( expect(
sliderBox, sliderBox,
@ -326,8 +474,7 @@ void main() {
expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveRailColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveRailColor)));
// Test setting the activeColor and inactiveColor for disabled widget. // Test setting the activeColor and inactiveColor for disabled widget.
await tester.pumpWidget( await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
expect( expect(
sliderBox, sliderBox,
paints paints
@ -343,8 +490,8 @@ void main() {
Offset center = tester.getCenter(find.byType(Slider)); Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center); TestGesture gesture = await tester.startGesture(center);
await tester.pump(); await tester.pump();
await tester // Wait for value indicator animation to finish.
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish. await tester.pump(const Duration(milliseconds: 500));
expect(value, equals(2.0 / 3.0)); expect(value, equals(2.0 / 3.0));
expect( expect(
sliderBox, sliderBox,
@ -361,8 +508,8 @@ void main() {
); );
await gesture.up(); await gesture.up();
await tester.pump(); await tester.pump();
await tester // Wait for value indicator animation to finish.
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish. await tester.pump(const Duration(milliseconds: 500));
// Testing the custom colors are used for the indicator. // Testing the custom colors are used for the indicator.
await tester.pumpWidget(buildApp( await tester.pumpWidget(buildApp(
@ -373,8 +520,8 @@ void main() {
center = tester.getCenter(find.byType(Slider)); center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center); gesture = await tester.startGesture(center);
await tester.pump(); await tester.pump();
await tester // Wait for value indicator animation to finish.
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish. await tester.pump(const Duration(milliseconds: 500));
expect(value, equals(2.0 / 3.0)); expect(value, equals(2.0 / 3.0));
expect( expect(
sliderBox, sliderBox,
@ -395,19 +542,22 @@ void main() {
double value = 0.0; double value = 0.0;
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new ListView( data: new MediaQueryData.fromWindow(window),
children: <Widget>[ child: new Material(
new Slider( child: new ListView(
value: value, children: <Widget>[
onChanged: (double newValue) { new Slider(
value = newValue; value: value,
}, onChanged: (double newValue) {
), value = newValue;
new Container( },
height: 2000.0, ),
), new Container(
], height: 2000.0,
),
],
),
), ),
), ),
)); ));
@ -420,13 +570,16 @@ void main() {
double value = 0.0; double value = 0.0;
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Slider( child: new Material(
value: value, child: new Center(
onChanged: (double newValue) { child: new Slider(
value = newValue; value: value,
}, onChanged: (double newValue) {
value = newValue;
},
),
), ),
), ),
), ),
@ -448,13 +601,16 @@ void main() {
double value = 0.0; double value = 0.0;
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Slider( child: new Material(
value: value, child: new Center(
onChanged: (double newValue) { child: new Slider(
value = newValue; value: value,
}, onChanged: (double newValue) {
value = newValue;
},
),
), ),
), ),
), ),
@ -473,62 +629,70 @@ void main() {
}); });
testWidgets('Slider sizing', (WidgetTester tester) async { testWidgets('Slider sizing', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: const Material( child: new MediaQuery(
child: const Center( data: new MediaQueryData.fromWindow(window),
child: const Slider( child: const Material(
value: 0.5, child: const Center(
onChanged: null, child: const Slider(
value: 0.5,
onChanged: null,
),
), ),
), ),
), ),
)); ));
expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(800.0, 600.0)); expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(800.0, 600.0));
await tester.pumpWidget(const Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: const Material( child: new MediaQuery(
child: const Center( data: new MediaQueryData.fromWindow(window),
child: const IntrinsicWidth( child: const Material(
child: const Slider( child: const Center(
value: 0.5, child: const IntrinsicWidth(
onChanged: null, child: const Slider(
value: 0.5,
onChanged: null,
),
), ),
), ),
), ),
), ),
)); ));
expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 600.0));
const Size(144.0 + 2.0 * 16.0, 600.0));
await tester.pumpWidget(const Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: const Material( child: new MediaQuery(
child: const Center( data: new MediaQueryData.fromWindow(window),
child: const OverflowBox( child: const Material(
maxWidth: double.INFINITY, child: const Center(
maxHeight: double.INFINITY, child: const OverflowBox(
child: const Slider( maxWidth: double.INFINITY,
value: 0.5, maxHeight: double.INFINITY,
onChanged: null, child: const Slider(
value: 0.5,
onChanged: null,
),
), ),
), ),
), ),
), ),
)); ));
expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 32.0));
const Size(144.0 + 2.0 * 16.0, 32.0));
}); });
testWidgets('Slider respects textScaleFactor', (WidgetTester tester) async { testWidgets('Slider respects textScaleFactor', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey(); final Key sliderKey = new UniqueKey();
double value = 0.0; double value = 0.0;
Widget buildSlider( Widget buildSlider({
{double textScaleFactor, double textScaleFactor,
bool isDiscrete: true, bool isDiscrete: true,
ShowValueIndicator show: ShowValueIndicator.onlyForDiscrete}) { ShowValueIndicator show: ShowValueIndicator.onlyForDiscrete,
}) {
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new StatefulBuilder( child: new StatefulBuilder(
@ -538,8 +702,8 @@ void main() {
child: new Material( child: new Material(
child: new Theme( child: new Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
sliderTheme: sliderTheme: Theme.of(context).sliderTheme.copyWith(showValueIndicator: show),
Theme.of(context).sliderTheme.copyWith(showValueIndicator: show)), ),
child: new Center( child: new Center(
child: new OverflowBox( child: new OverflowBox(
maxWidth: double.INFINITY, maxWidth: double.INFINITY,
@ -621,15 +785,161 @@ void main() {
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
}); });
testWidgets('Slider has correct animations when reparented', (WidgetTester tester) async {
final Key sliderKey = new GlobalKey(debugLabel: 'A');
double value = 0.0;
Widget buildSlider(int parents) {
Widget createParents(int parents, StateSetter setState) {
Widget slider = new Slider(
key: sliderKey,
value: value,
divisions: 4,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
);
for (int i = 0; i < parents; ++i) {
slider = new Column(children: <Widget>[slider]);
}
return slider;
}
return 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: createParents(parents, setState),
),
);
},
),
);
}
Future<Null> testReparenting(bool reparent) async {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final Offset center = tester.getCenter(find.byType(Slider));
// Move to 0.0.
TestGesture 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),
);
gesture = await tester.startGesture(center);
await tester.pump();
// Wait for animations to start.
await tester.pump(const Duration(milliseconds: 25));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect(
sliderBox,
paints
..circle(x: 310.9375, 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),
);
// Reparenting in the middle of an animation should do nothing.
if (reparent) {
await tester.pumpWidget(buildSlider(2));
}
// Move a little further in the animations.
await tester.pump(const Duration(milliseconds: 10));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect(
sliderBox,
paints
..circle(x: 396.6802978515625, 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: 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),
);
// Wait for animations to finish.
await tester.pump(const Duration(milliseconds: 300));
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
paints
..circle(x: 400.0, y: 16.0, radius: 16.0)
..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, 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: 400.0, y: 16.0, radius: 6.0),
);
await gesture.up();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
paints
..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, 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: 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));
// Do it once without reparenting in the middle of an animation
await testReparenting(false);
// Now do it again with reparenting in the middle of an animation.
await testReparenting(true);
});
testWidgets('Slider Semantics', (WidgetTester tester) async { testWidgets('Slider Semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Slider( data: new MediaQueryData.fromWindow(window),
value: 0.5, child: new Material(
onChanged: (double v) {}, child: new Slider(
value: 0.5,
onChanged: (double v) {},
),
), ),
), ),
)); ));
@ -648,12 +958,15 @@ void main() {
)); ));
// Disable slider // Disable slider
await tester.pumpWidget(const Directionality( await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: const Material( child: new MediaQuery(
child: const Slider( data: new MediaQueryData.fromWindow(window),
value: 0.5, child: const Material(
onChanged: null, child: const Slider(
value: 0.5,
onChanged: null,
),
), ),
), ),
)); ));
@ -680,17 +993,20 @@ void main() {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null; final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Theme( child: new Material(
data: baseTheme, child: new Center(
child: new SliderTheme( child: new Theme(
data: sliderTheme, data: baseTheme,
child: new Slider( child: new SliderTheme(
value: value, data: sliderTheme,
label: '$value', child: new Slider(
divisions: divisions, value: value,
onChanged: onChanged, label: '$value',
divisions: divisions,
onChanged: onChanged,
),
), ),
), ),
), ),
@ -699,15 +1015,19 @@ void main() {
); );
} }
Future<Null> expectValueIndicator( Future<Null> expectValueIndicator({
{bool isVisible, SliderThemeData theme, int divisions, bool enabled: true}) async { bool isVisible,
// discrete enabled widget. SliderThemeData theme,
int divisions,
bool enabled: true,
}) async {
// Discrete enabled widget.
await tester.pumpWidget(buildApp(sliderTheme: theme, divisions: divisions, enabled: enabled)); await tester.pumpWidget(buildApp(sliderTheme: theme, divisions: divisions, enabled: enabled));
final Offset center = tester.getCenter(find.byType(Slider)); final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center); final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); await tester.pump();
await tester // Wait for value indicator animation to finish.
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish. await tester.pump(const Duration(milliseconds: 500));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect( expect(

View File

@ -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:ui' show window;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -31,53 +33,12 @@ void main() {
Widget buildSlider(SliderThemeData data) { Widget buildSlider(SliderThemeData data) {
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Theme( child: new Material(
data: theme, child: new Center(
child: const Slider( child: new Theme(
value: 0.5, data: theme,
label: '0.5',
onChanged: null,
),
),
),
),
);
}
await tester.pumpWidget(buildSlider(sliderTheme));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(
sliderBox,
paints
..rect(color: sliderTheme.disabledActiveRailColor)
..rect(color: sliderTheme.disabledInactiveRailColor));
});
testWidgets('Slider overrides ThemeData theme if SliderTheme present',
(WidgetTester tester) async {
final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
final SliderThemeData customTheme = sliderTheme.copyWith(
activeRailColor: Colors.purple,
inactiveRailColor: Colors.purple.withAlpha(0x3d),
);
Widget buildSlider(SliderThemeData data) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new Theme(
data: theme,
child: new SliderTheme(
data: customTheme,
child: const Slider( child: const Slider(
value: 0.5, value: 0.5,
label: '0.5', label: '0.5',
@ -94,20 +55,57 @@ void main() {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect( expect(sliderBox, paints..rect(color: sliderTheme.disabledActiveRailColor)..rect(color: sliderTheme.disabledInactiveRailColor));
sliderBox,
paints
..rect(color: customTheme.disabledActiveRailColor)
..rect(color: customTheme.disabledInactiveRailColor));
}); });
testWidgets('SliderThemeData generates correct opacities for materialDefaults', testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
(WidgetTester tester) async { final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
final SliderThemeData customTheme = sliderTheme.copyWith(
activeRailColor: Colors.purple,
inactiveRailColor: Colors.purple.withAlpha(0x3d),
);
Widget buildSlider(SliderThemeData data) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window),
child: new Material(
child: new Center(
child: new Theme(
data: theme,
child: new SliderTheme(
data: customTheme,
child: const Slider(
value: 0.5,
label: '0.5',
onChanged: null,
),
),
),
),
),
),
);
}
await tester.pumpWidget(buildSlider(sliderTheme));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(sliderBox, paints..rect(color: customTheme.disabledActiveRailColor)..rect(color: customTheme.disabledInactiveRailColor));
});
testWidgets('SliderThemeData generates correct opacities for materialDefaults', (WidgetTester tester) async {
const Color customColor1 = const Color(0xcafefeed); const Color customColor1 = const Color(0xcafefeed);
const Color customColor2 = const Color(0xdeadbeef); const Color customColor2 = const Color(0xdeadbeef);
const Color customColor3 = const Color(0xdecaface); const Color customColor3 = const Color(0xdecaface);
final SliderThemeData sliderTheme = new SliderThemeData.materialDefaults( final SliderThemeData sliderTheme = new SliderThemeData.fromPrimaryColors(
primaryColor: customColor1, primaryColor: customColor1,
primaryColorDark: customColor2, primaryColorDark: customColor2,
primaryColorLight: customColor3, primaryColorLight: customColor3,
@ -126,18 +124,17 @@ void main() {
expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29))); expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29)));
expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff))); expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
expect(sliderTheme.thumbShape, equals(const isInstanceOf<RoundSliderThumbShape>())); expect(sliderTheme.thumbShape, equals(const isInstanceOf<RoundSliderThumbShape>()));
expect(sliderTheme.valueIndicatorShape, expect(sliderTheme.valueIndicatorShape, equals(const isInstanceOf<PaddleSliderValueIndicatorShape>()));
equals(const isInstanceOf<PaddleSliderValueIndicatorShape>()));
expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete)); expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete));
}); });
testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async { testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
final SliderThemeData sliderThemeBlack = new SliderThemeData.materialDefaults( final SliderThemeData sliderThemeBlack = new SliderThemeData.fromPrimaryColors(
primaryColor: Colors.black, primaryColor: Colors.black,
primaryColorDark: Colors.black, primaryColorDark: Colors.black,
primaryColorLight: Colors.black, primaryColorLight: Colors.black,
); );
final SliderThemeData sliderThemeWhite = new SliderThemeData.materialDefaults( final SliderThemeData sliderThemeWhite = new SliderThemeData.fromPrimaryColors(
primaryColor: Colors.white, primaryColor: Colors.white,
primaryColorDark: Colors.white, primaryColorDark: Colors.white,
primaryColorLight: Colors.white, primaryColorLight: Colors.white,
@ -172,15 +169,18 @@ void main() {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null; final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new SliderTheme( child: new Material(
data: sliderTheme, child: new Center(
child: new Slider( child: new SliderTheme(
value: value, data: sliderTheme,
label: '$value', child: new Slider(
divisions: divisions, value: value,
onChanged: onChanged, label: '$value',
divisions: divisions,
onChanged: onChanged,
),
), ),
), ),
), ),
@ -225,21 +225,27 @@ void main() {
platform: TargetPlatform.android, platform: TargetPlatform.android,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
); );
final SliderThemeData sliderTheme = theme.sliderTheme final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always); Widget buildApp(String value, {double sliderValue = 0.5}) {
Widget buildApp(String value) {
return new Directionality( return new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new SliderTheme( child: new Material(
data: sliderTheme, child: new Row(
child: new Slider( children: <Widget>[
value: 0.5, new Expanded(
label: '$value', child: new SliderTheme(
divisions: 3, data: sliderTheme,
onChanged: (double d) {}, child: new Slider(
), value: sliderValue,
label: '$value',
divisions: 3,
onChanged: (double d) {},
),
),
),
],
), ),
), ),
), ),
@ -290,5 +296,47 @@ void main() {
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)], excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
)); ));
await gesture.up(); await gesture.up();
// Test that it avoids the left edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
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));
expect(
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(98.0, -40.0),
const Offset(-16.0, -40.0),
],
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)],
));
await gesture.up();
// Test that it avoids the right edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
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));
expect(
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(16.0, -40.0),
const Offset(-98.0, -40.0),
],
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
));
await gesture.up();
}); });
} }

View File

@ -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:ui' show window;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -292,13 +294,16 @@ void main() {
child: new SemanticsDebugger( child: new SemanticsDebugger(
child: new Directionality( child: new Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: new Material( child: new MediaQuery(
child: new Center( data: new MediaQueryData.fromWindow(window),
child: new Slider( child: new Material(
value: value, child: new Center(
onChanged: (double newValue) { child: new Slider(
value = newValue; value: value,
}, onChanged: (double newValue) {
value = newValue;
},
),
), ),
), ),
), ),