Revert "[Slider] Rebase. (#52663)" (#53698)

This reverts commit e71cf1cdbe.
This commit is contained in:
Jose Alba 2020-03-31 19:14:22 -04:00 committed by GitHub
parent ad07c4041a
commit d14a301e41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1252 additions and 3772 deletions

View File

@ -69,7 +69,6 @@ class _CustomRangeThumbShape extends RangeSliderThumbShape {
@required SliderThemeData sliderTheme, @required SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
Thumb thumb, Thumb thumb,
bool isPressed,
}) { }) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween( final ColorTween colorTween = ColorTween(
@ -131,8 +130,6 @@ class _CustomThumbShape extends SliderComponentShape {
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
double value, double value,
double textScaleFactor,
Size sizeWithOverflow,
}) { }) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween( final ColorTween colorTween = ColorTween(
@ -172,8 +169,6 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
TextDirection textDirection, TextDirection textDirection,
double value, double value,
double textScaleFactor,
Size sizeWithOverflow,
}) { }) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final ColorTween enableColor = ColorTween( final ColorTween enableColor = ColorTween(
@ -273,12 +268,7 @@ class _SlidersState extends State<_Sliders> {
), ),
), ),
), ),
SliderTheme( Slider.adaptive(
data: const SliderThemeData(
showValueIndicator: ShowValueIndicator.always,
),
child: Slider.adaptive(
label: _continuousValue.toStringAsFixed(6).toString(),
value: _continuousValue, value: _continuousValue,
min: 0.0, min: 0.0,
max: 100.0, max: 100.0,
@ -288,7 +278,6 @@ class _SlidersState extends State<_Sliders> {
}); });
}, },
), ),
),
const Text('Continuous with Editable Numerical Value'), const Text('Continuous with Editable Numerical Value'),
], ],
), ),

View File

@ -21,12 +21,10 @@ ThemeData _buildDarkTheme() {
final ColorScheme colorScheme = const ColorScheme.dark().copyWith( final ColorScheme colorScheme = const ColorScheme.dark().copyWith(
primary: primaryColor, primary: primaryColor,
secondary: secondaryColor, secondary: secondaryColor,
onPrimary: secondaryColor,
); );
final ThemeData base = ThemeData( final ThemeData base = ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
accentColorBrightness: Brightness.dark, accentColorBrightness: Brightness.dark,
colorScheme: colorScheme,
primaryColor: primaryColor, primaryColor: primaryColor,
primaryColorDark: const Color(0xFF0050a0), primaryColorDark: const Color(0xFF0050a0),
primaryColorLight: secondaryColor, primaryColorLight: secondaryColor,

View File

@ -22,11 +22,6 @@ import 'theme.dart';
// RangeValues _dollarsRange = RangeValues(50, 100); // RangeValues _dollarsRange = RangeValues(50, 100);
// void setState(VoidCallback fn) { } // void setState(VoidCallback fn) { }
/// [RangeSlider] uses this callback to paint the value indicator on the overlay.
/// Since the value indicator is painted on the Overlay; this method paints the
/// value indicator in a [RenderBox] that appears in the [Overlay].
typedef PaintRangeValueIndicator = void Function(PaintingContext context, Offset offset);
/// A Material Design range slider. /// A Material Design range slider.
/// ///
/// Used to select a range from a range of values. /// Used to select a range from a range of values.
@ -132,7 +127,6 @@ class RangeSlider extends StatefulWidget {
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.useV2Slider = false,
}) : assert(values != null), }) : assert(values != null),
assert(min != null), assert(min != null),
assert(max != null), assert(max != null),
@ -141,7 +135,6 @@ class RangeSlider extends StatefulWidget {
assert(values.start >= min && values.start <= max), assert(values.start >= min && values.start <= max),
assert(values.end >= min && values.end <= max), assert(values.end >= min && values.end <= max),
assert(divisions == null || divisions > 0), assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key); super(key: key);
/// The currently selected values for this range slider. /// The currently selected values for this range slider.
@ -340,19 +333,6 @@ class RangeSlider extends StatefulWidget {
/// {@end-tool} /// {@end-tool}
final RangeSemanticFormatterCallback semanticFormatterCallback; final RangeSemanticFormatterCallback semanticFormatterCallback;
/// Whether to use the updated Material spec version of the [RangeSlider].
/// * The v2 [RangeSlider] has an updated value indicator that matches the latest specs.
/// * The value indicator is painted on the Overlay.
/// * The active track is bigger than the inactive track.
/// * The thumb that is activated has elevation.
/// * Updated value indicators in case they overlap with each other.
/// * <https://groups.google.com/g/flutter-announce/c/69dmlKUL5Ew/m/tQh-ajiEAAAJl>
///
/// This is a temporary flag for migrating the slider from v1 to v2. Currently
/// this defaults to false, because the changes may break existing tests. This
/// value will be defaulted to true in the future.
final bool useV2Slider;
// Touch width for the tap boundary of the slider thumbs. // Touch width for the tap boundary of the slider thumbs.
static const double _minTouchTargetWidth = kMinInteractiveDimension; static const double _minTouchTargetWidth = kMinInteractiveDimension;
@ -374,7 +354,6 @@ class RangeSlider extends StatefulWidget {
properties.add(StringProperty('labelEnd', labels?.end)); properties.add(StringProperty('labelEnd', labels?.end));
properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor)); properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>.has('semanticFormatterCallback', semanticFormatterCallback)); properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>.has('semanticFormatterCallback', semanticFormatterCallback));
} }
} }
@ -398,10 +377,6 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
AnimationController startPositionController; AnimationController startPositionController;
AnimationController endPositionController; AnimationController endPositionController;
Timer interactionTimer; Timer interactionTimer;
// Value Indicator paint Animation that appears on the Overlay.
PaintRangeValueIndicator paintTopValueIndicator;
PaintRangeValueIndicator paintBottomValueIndicator;
@override @override
void initState() { void initState() {
@ -545,7 +520,14 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
return null; return null;
}; };
static const double _defaultTrackHeight = 2;
static const RangeSliderTrackShape _defaultTrackShape = RoundedRectRangeSliderTrackShape();
static const RangeSliderTickMarkShape _defaultTickMarkShape = RoundRangeSliderTickMarkShape();
static const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
static const RangeSliderThumbShape _defaultThumbShape = RoundRangeSliderThumbShape();
static const RangeSliderValueIndicatorShape _defaultValueIndicatorShape = PaddleRangeSliderValueIndicatorShape();
static const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
static const double _defaultMinThumbSeparation = 8;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -561,29 +543,6 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
// colors come from the ThemeData.colorScheme. These colors, along with // colors come from the ThemeData.colorScheme. These colors, along with
// the default shapes and text styles are aligned to the Material // the default shapes and text styles are aligned to the Material
// Guidelines. // Guidelines.
final bool useV2Slider = widget.useV2Slider;
final double _defaultTrackHeight = useV2Slider ? 4 : 2;
final RangeSliderTrackShape _defaultTrackShape = RoundedRectRangeSliderTrackShape(useV2Slider: useV2Slider);
final RangeSliderTickMarkShape _defaultTickMarkShape = RoundRangeSliderTickMarkShape(useV2Slider: useV2Slider);
const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
final RangeSliderThumbShape _defaultThumbShape = RoundRangeSliderThumbShape(useV2Slider: useV2Slider);
final RangeSliderValueIndicatorShape _defaultValueIndicatorShape = useV2Slider ? const RectangularRangeSliderValueIndicatorShape() : const PaddleRangeSliderValueIndicatorShape();
const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
const double _defaultMinThumbSeparation = 8;
// The value indicator's color is not the same as the thumb and active track
// (which can be defined by activeColor) if the
// RectangularSliderValueIndicatorShape is used. In all other cases, the
// value indicator is assumed to be the same as the active color.
final RangeSliderValueIndicatorShape valueIndicatorShape = sliderTheme.rangeValueIndicatorShape ?? _defaultValueIndicatorShape;
Color valueIndicatorColor;
if (valueIndicatorShape is RectangularRangeSliderValueIndicatorShape) {
valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));
} else {
valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
}
sliderTheme = sliderTheme.copyWith( sliderTheme = sliderTheme.copyWith(
trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight, trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary, activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
@ -596,14 +555,14 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12), disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary, thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
overlappingShapeStrokeColor: sliderTheme.overlappingShapeStrokeColor ?? theme.colorScheme.surface, overlappingShapeStrokeColor: sliderTheme.overlappingShapeStrokeColor ?? theme.colorScheme.surface,
disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), const Color(0xFFFFFFFF)), disabledThumbColor: sliderTheme.disabledThumbColor ?? theme.colorScheme.onSurface.withOpacity(0.38),
overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12), overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
valueIndicatorColor: valueIndicatorColor, valueIndicatorColor: widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary,
rangeTrackShape: sliderTheme.rangeTrackShape ?? _defaultTrackShape, rangeTrackShape: sliderTheme.rangeTrackShape ?? _defaultTrackShape,
rangeTickMarkShape: sliderTheme.rangeTickMarkShape ?? _defaultTickMarkShape, rangeTickMarkShape: sliderTheme.rangeTickMarkShape ?? _defaultTickMarkShape,
rangeThumbShape: sliderTheme.rangeThumbShape ?? _defaultThumbShape, rangeThumbShape: sliderTheme.rangeThumbShape ?? _defaultThumbShape,
overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape, overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
rangeValueIndicatorShape: valueIndicatorShape, rangeValueIndicatorShape: sliderTheme.rangeValueIndicatorShape ?? _defaultValueIndicatorShape,
showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator, showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith( valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
@ -612,49 +571,19 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
thumbSelector: sliderTheme.thumbSelector ?? _defaultRangeThumbSelector, thumbSelector: sliderTheme.thumbSelector ?? _defaultRangeThumbSelector,
); );
// This size is used as the max bounds for the painting of the value return _RangeSliderRenderObjectWidget(
// indicators. It must be kept in sync with the function with the same name
// in slider.dart.
Size _screenSize() => MediaQuery.of(context).size;
return CompositedTransformTarget(
link: _layerLink,
child: _RangeSliderRenderObjectWidget(
values: _unlerpRangeValues(widget.values), values: _unlerpRangeValues(widget.values),
divisions: widget.divisions, divisions: widget.divisions,
labels: widget.labels, labels: widget.labels,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor, textScaleFactor: MediaQuery.of(context).textScaleFactor,
screenSize: _screenSize(),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this, state: this,
semanticFormatterCallback: widget.semanticFormatterCallback, semanticFormatterCallback: widget.semanticFormatterCallback,
useV2Slider: widget.useV2Slider,
),
); );
} }
final LayerLink _layerLink = LayerLink();
OverlayEntry overlayEntry;
void showValueIndicator() {
if (overlayEntry == null) {
overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return CompositedTransformFollower(
link: _layerLink,
child: _ValueIndicatorRenderObjectWidget(
state: this,
),
);
},
);
Overlay.of(context).insert(overlayEntry);
}
}
} }
class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget { class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
@ -665,13 +594,11 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
this.labels, this.labels,
this.sliderTheme, this.sliderTheme,
this.textScaleFactor, this.textScaleFactor,
this.screenSize,
this.onChanged, this.onChanged,
this.onChangeStart, this.onChangeStart,
this.onChangeEnd, this.onChangeEnd,
this.state, this.state,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.useV2Slider,
}) : super(key: key); }) : super(key: key);
final RangeValues values; final RangeValues values;
@ -679,13 +606,11 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
final RangeLabels labels; final RangeLabels labels;
final SliderThemeData sliderTheme; final SliderThemeData sliderTheme;
final double textScaleFactor; final double textScaleFactor;
final Size screenSize;
final ValueChanged<RangeValues> onChanged; final ValueChanged<RangeValues> onChanged;
final ValueChanged<RangeValues> onChangeStart; final ValueChanged<RangeValues> onChangeStart;
final ValueChanged<RangeValues> onChangeEnd; final ValueChanged<RangeValues> onChangeEnd;
final RangeSemanticFormatterCallback semanticFormatterCallback; final RangeSemanticFormatterCallback semanticFormatterCallback;
final _RangeSliderState state; final _RangeSliderState state;
final bool useV2Slider;
@override @override
_RenderRangeSlider createRenderObject(BuildContext context) { _RenderRangeSlider createRenderObject(BuildContext context) {
@ -696,7 +621,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
theme: Theme.of(context), theme: Theme.of(context),
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
screenSize: screenSize,
onChanged: onChanged, onChanged: onChanged,
onChangeStart: onChangeStart, onChangeStart: onChangeStart,
onChangeEnd: onChangeEnd, onChangeEnd: onChangeEnd,
@ -704,7 +628,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback, semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform, platform: Theme.of(context).platform,
useV2Slider: useV2Slider,
); );
} }
@ -717,7 +640,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
..sliderTheme = sliderTheme ..sliderTheme = sliderTheme
..theme = Theme.of(context) ..theme = Theme.of(context)
..textScaleFactor = textScaleFactor ..textScaleFactor = textScaleFactor
..screenSize = screenSize
..onChanged = onChanged ..onChanged = onChanged
..onChangeStart = onChangeStart ..onChangeStart = onChangeStart
..onChangeEnd = onChangeEnd ..onChangeEnd = onChangeEnd
@ -735,7 +657,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
ThemeData theme, ThemeData theme,
double textScaleFactor, double textScaleFactor,
Size screenSize,
TargetPlatform platform, TargetPlatform platform,
ValueChanged<RangeValues> onChanged, ValueChanged<RangeValues> onChanged,
RangeSemanticFormatterCallback semanticFormatterCallback, RangeSemanticFormatterCallback semanticFormatterCallback,
@ -743,7 +664,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
this.onChangeEnd, this.onChangeEnd,
@required _RangeSliderState state, @required _RangeSliderState state,
@required TextDirection textDirection, @required TextDirection textDirection,
bool useV2Slider,
}) : assert(values != null), }) : assert(values != null),
assert(values.start >= 0.0 && values.start <= 1.0), assert(values.start >= 0.0 && values.start <= 1.0),
assert(values.end >= 0.0 && values.end <= 1.0), assert(values.end >= 0.0 && values.end <= 1.0),
@ -757,11 +677,9 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_sliderTheme = sliderTheme, _sliderTheme = sliderTheme,
_theme = theme, _theme = theme,
_textScaleFactor = textScaleFactor, _textScaleFactor = textScaleFactor,
_screenSize = screenSize,
_onChanged = onChanged, _onChanged = onChanged,
_state = state, _state = state,
_textDirection = textDirection, _textDirection = textDirection {
_useV2Slider = useV2Slider {
_updateLabelPainters(); _updateLabelPainters();
final GestureArenaTeam team = GestureArenaTeam(); final GestureArenaTeam team = GestureArenaTeam();
_drag = HorizontalDragGestureRecognizer() _drag = HorizontalDragGestureRecognizer()
@ -782,12 +700,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_valueIndicatorAnimation = CurvedAnimation( _valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController, parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) { );
if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
_state.overlayEntry.remove();
_state.overlayEntry = null;
}
});
_enableAnimation = CurvedAnimation( _enableAnimation = CurvedAnimation(
parent: _state.enableController, parent: _state.enableController,
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -936,15 +849,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_updateLabelPainters(); _updateLabelPainters();
} }
Size get screenSize => _screenSize;
Size _screenSize;
set screenSize(Size value) {
if (value == screenSize)
return;
_screenSize = value;
markNeedsPaint();
}
ValueChanged<RangeValues> get onChanged => _onChanged; ValueChanged<RangeValues> get onChanged => _onChanged;
ValueChanged<RangeValues> _onChanged; ValueChanged<RangeValues> _onChanged;
set onChanged(ValueChanged<RangeValues> value) { set onChanged(ValueChanged<RangeValues> value) {
@ -1009,8 +913,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
return 0.05; return 0.05;
} }
final bool _useV2Slider;
void _updateLabelPainters() { void _updateLabelPainters() {
_updateLabelPainter(Thumb.start); _updateLabelPainter(Thumb.start);
_updateLabelPainter(Thumb.end); _updateLabelPainter(Thumb.end);
@ -1107,7 +1009,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
} }
void _startInteraction(Offset globalPosition) { void _startInteraction(Offset globalPosition) {
_state.showValueIndicator();
final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0) as double; final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0) as double;
_lastThumbSelection = sliderTheme.thumbSelector(textDirection, values, tapValue, _thumbSize, size, 0); _lastThumbSelection = sliderTheme.thumbSelector(textDirection, values, tapValue, _thumbSize, size, 0);
@ -1299,11 +1200,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
isEnabled: isEnabled, isEnabled: isEnabled,
); );
final bool startThumbSelected = _lastThumbSelection == Thumb.start;
final bool endThumbSelected = _lastThumbSelection == Thumb.end;
if (!_overlayAnimation.isDismissed) { if (!_overlayAnimation.isDismissed) {
if (startThumbSelected) { if (_lastThumbSelection == Thumb.start) {
_sliderTheme.overlayShape.paint( _sliderTheme.overlayShape.paint(
context, context,
startThumbCenter, startThumbCenter,
@ -1317,7 +1215,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
value: startValue, value: startValue,
); );
} }
if (endThumbSelected) { if (_lastThumbSelection == Thumb.end) {
_sliderTheme.overlayShape.paint( _sliderTheme.overlayShape.paint(
context, context,
endThumbCenter, endThumbCenter,
@ -1338,8 +1236,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
isEnabled: isEnabled, isEnabled: isEnabled,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
).width; ).width;
final double padding = _useV2Slider ? trackRect.height : tickMarkWidth; final double adjustedTrackWidth = trackRect.width - tickMarkWidth;
final double adjustedTrackWidth = trackRect.width - padding;
// If the tick marks would be too dense, don't bother painting them. // If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) { if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy; final double dy = trackRect.center.dy;
@ -1347,7 +1244,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
final double value = i / divisions; final double value = i / divisions;
// The ticks are mapped to be within the track, so the tick mark width // The ticks are mapped to be within the track, so the tick mark width
// must be subtracted from the track width. // must be subtracted from the track width.
final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2; final double dx = trackRect.left + value * adjustedTrackWidth + tickMarkWidth / 2;
final Offset tickMarkOffset = Offset(dx, dy); final Offset tickMarkOffset = Offset(dx, dy);
_sliderTheme.rangeTickMarkShape.paint( _sliderTheme.rangeTickMarkShape.paint(
context, context,
@ -1376,10 +1273,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
final double bottomValue = isLastThumbStart ? endValue : startValue; final double bottomValue = isLastThumbStart ? endValue : startValue;
final double topValue = isLastThumbStart ? startValue : endValue; final double topValue = isLastThumbStart ? startValue : endValue;
final bool shouldPaintValueIndicators = isEnabled && labels != null && !_valueIndicatorAnimation.isDismissed && showValueIndicator; final bool shouldPaintValueIndicators = isEnabled && labels != null && !_valueIndicatorAnimation.isDismissed && showValueIndicator;
final Size resolvedscreenSize = screenSize.isEmpty ? size : screenSize;
if (shouldPaintValueIndicators) { if (shouldPaintValueIndicators) {
_state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint( _sliderTheme.rangeValueIndicatorShape.paint(
context, context,
bottomThumbCenter, bottomThumbCenter,
@ -1393,10 +1288,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
textDirection: _textDirection, textDirection: _textDirection,
thumb: bottomThumb, thumb: bottomThumb,
value: bottomValue, value: bottomValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
); );
};
} }
_sliderTheme.rangeThumbShape.paint( _sliderTheme.rangeThumbShape.paint(
@ -1409,7 +1301,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
textDirection: textDirection, textDirection: textDirection,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
thumb: bottomThumb, thumb: bottomThumb,
isPressed: bottomThumb == Thumb.start ? startThumbSelected : endThumbSelected,
); );
if (shouldPaintValueIndicators) { if (shouldPaintValueIndicators) {
@ -1418,29 +1309,15 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
center: startThumbCenter, center: startThumbCenter,
labelPainter: _startLabelPainter, labelPainter: _startLabelPainter,
activationAnimation: _valueIndicatorAnimation, activationAnimation: _valueIndicatorAnimation,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
); );
final double endOffset = sliderTheme.rangeValueIndicatorShape.getHorizontalShift( final double endOffset = sliderTheme.rangeValueIndicatorShape.getHorizontalShift(
parentBox: this, parentBox: this,
center: endThumbCenter, center: endThumbCenter,
labelPainter: _endLabelPainter, labelPainter: _endLabelPainter,
activationAnimation: _valueIndicatorAnimation, activationAnimation: _valueIndicatorAnimation,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
); );
final double startHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize( final double startHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(isEnabled, isDiscrete, labelPainter: _startLabelPainter).width / 2;
isEnabled, final double endHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(isEnabled, isDiscrete, labelPainter: _endLabelPainter).width / 2;
isDiscrete,
labelPainter: _startLabelPainter,
textScaleFactor: textScaleFactor,
).width / 2;
final double endHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(
isEnabled,
isDiscrete,
labelPainter: _endLabelPainter,
textScaleFactor: textScaleFactor,
).width / 2;
double innerOverflow = startHalfWidth + endHalfWidth; double innerOverflow = startHalfWidth + endHalfWidth;
switch (textDirection) { switch (textDirection) {
case TextDirection.ltr: case TextDirection.ltr:
@ -1453,7 +1330,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
break; break;
} }
_state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint( _sliderTheme.rangeValueIndicatorShape.paint(
context, context,
topThumbCenter, topThumbCenter,
@ -1467,23 +1343,19 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
textDirection: _textDirection, textDirection: _textDirection,
thumb: topThumb, thumb: topThumb,
value: topValue, value: topValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
); );
};
} }
_sliderTheme.rangeThumbShape.paint( _sliderTheme.rangeThumbShape.paint(
context, context,
topThumbCenter, topThumbCenter,
activationAnimation: _overlayAnimation, activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation, enableAnimation: _enableAnimation,
isDiscrete: isDiscrete, isDiscrete: isDiscrete,
isOnTop: thumbDelta < sliderTheme.rangeThumbShape.getPreferredSize(isEnabled, isDiscrete).width, isOnTop: thumbDelta < sliderTheme.rangeThumbShape.getPreferredSize(isEnabled, isDiscrete).width,
textDirection: textDirection, textDirection: textDirection,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
thumb: topThumb, thumb: topThumb,
isPressed: topThumb == Thumb.start ? startThumbSelected : endThumbSelected,
); );
} }
@ -1547,66 +1419,3 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
return (value - _semanticActionUnit).clamp(0.0, 1.0) as double; return (value - _semanticActionUnit).clamp(0.0, 1.0) as double;
} }
} }
class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
const _ValueIndicatorRenderObjectWidget({
this.state,
});
final _RangeSliderState state;
@override
_RenderValueIndicator createRenderObject(BuildContext context) {
return _RenderValueIndicator(
state: state,
);
}
@override
void updateRenderObject(BuildContext context, _RenderValueIndicator renderObject) {
renderObject._state = state;
}
}
class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_RenderValueIndicator({
_RangeSliderState state,
}) :_state = state {
_valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn,
);
}
Animation<double> _valueIndicatorAnimation;
_RangeSliderState _state;
@override
bool get sizedByParent => true;
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_valueIndicatorAnimation.addListener(markNeedsPaint);
_state.startPositionController.addListener(markNeedsPaint);
_state.endPositionController.addListener(markNeedsPaint);
}
@override
void detach() {
_valueIndicatorAnimation.removeListener(markNeedsPaint);
_state.startPositionController.removeListener(markNeedsPaint);
_state.endPositionController.removeListener(markNeedsPaint);
super.detach();
}
@override
void paint(PaintingContext context, Offset offset) {
if (_state.paintBottomValueIndicator != null) {
_state.paintBottomValueIndicator(context, offset);
}
if (_state.paintTopValueIndicator != null) {
_state.paintTopValueIndicator(context, offset);
}
}
}

View File

@ -31,11 +31,6 @@ import 'theme.dart';
/// * [Slider.semanticFormatterCallback], which shows an example use case. /// * [Slider.semanticFormatterCallback], which shows an example use case.
typedef SemanticFormatterCallback = String Function(double value); typedef SemanticFormatterCallback = String Function(double value);
/// [Slider] uses this callback to paint the value indicator on the overlay.
/// Since the value indicator is painted on the Overlay; this method paints the
/// value indicator in a [RenderBox] that appears in the [Overlay].
typedef PaintValueIndicator = void Function(PaintingContext context, Offset offset);
enum _SliderType { material, adaptive } enum _SliderType { material, adaptive }
/// A Material Design slider. /// A Material Design slider.
@ -129,7 +124,6 @@ class Slider extends StatefulWidget {
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.useV2Slider = false,
}) : _sliderType = _SliderType.material, }) : _sliderType = _SliderType.material,
assert(value != null), assert(value != null),
assert(min != null), assert(min != null),
@ -137,7 +131,6 @@ class Slider extends StatefulWidget {
assert(min <= max), assert(min <= max),
assert(value >= min && value <= max), assert(value >= min && value <= max),
assert(divisions == null || divisions > 0), assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key); super(key: key);
/// Creates a [CupertinoSlider] if the target platform is iOS, creates a /// Creates a [CupertinoSlider] if the target platform is iOS, creates a
@ -160,7 +153,6 @@ class Slider extends StatefulWidget {
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.useV2Slider = false,
}) : _sliderType = _SliderType.adaptive, }) : _sliderType = _SliderType.adaptive,
assert(value != null), assert(value != null),
assert(min != null), assert(min != null),
@ -168,7 +160,6 @@ class Slider extends StatefulWidget {
assert(min <= max), assert(min <= max),
assert(value >= min && value <= max), assert(value >= min && value <= max),
assert(divisions == null || divisions > 0), assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key); super(key: key);
/// The currently selected value for this slider. /// The currently selected value for this slider.
@ -383,19 +374,6 @@ class Slider extends StatefulWidget {
/// Ignored if this slider is created with [Slider.adaptive] /// Ignored if this slider is created with [Slider.adaptive]
final SemanticFormatterCallback semanticFormatterCallback; final SemanticFormatterCallback semanticFormatterCallback;
/// Whether to use the updated Material spec version of the [Slider].
/// * The v2 Slider has an updated value indicator that matches the latest specs.
/// * The value indicator is painted on the Overlay.
/// * The active track is bigger than the inactive track.
/// * The thumb that is activated has elevation.
/// * Updated value indicators in case they overlap with each other.
/// * <https://groups.google.com/g/flutter-announce/c/69dmlKUL5Ew/m/tQh-ajiEAAAJl>
///
/// This is a temporary flag for migrating the slider from v1 to v2. To avoid
/// unexpected breaking changes, this value should be set to true. Setting
/// this to false is considered deprecated.
final bool useV2Slider;
final _SliderType _sliderType ; final _SliderType _sliderType ;
@override @override
@ -414,7 +392,6 @@ class Slider extends StatefulWidget {
properties.add(StringProperty('label', label)); properties.add(StringProperty('label', label));
properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor)); properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback)); properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
} }
} }
@ -435,8 +412,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// and the next on a discrete slider. // and the next on a discrete slider.
AnimationController positionController; AnimationController positionController;
Timer interactionTimer; Timer interactionTimer;
// Value Indicator Animation that appears on the Overlay.
PaintValueIndicator paintValueIndicator;
@override @override
void initState() { void initState() {
@ -504,6 +479,14 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
return widget.max > widget.min ? (value - widget.min) / (widget.max - widget.min) : 0.0; return widget.max > widget.min ? (value - widget.min) / (widget.max - widget.min) : 0.0;
} }
static const double _defaultTrackHeight = 2;
static const SliderTrackShape _defaultTrackShape = RoundedRectSliderTrackShape();
static const SliderTickMarkShape _defaultTickMarkShape = RoundSliderTickMarkShape();
static const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
static const SliderComponentShape _defaultThumbShape = RoundSliderThumbShape();
static const SliderComponentShape _defaultValueIndicatorShape = PaddleSliderValueIndicatorShape();
static const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
@ -542,28 +525,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// colors come from the ThemeData.colorScheme. These colors, along with // colors come from the ThemeData.colorScheme. These colors, along with
// the default shapes and text styles are aligned to the Material // the default shapes and text styles are aligned to the Material
// Guidelines. // Guidelines.
final bool useV2Slider = widget.useV2Slider;
final double _defaultTrackHeight = useV2Slider ? 4 : 2;
final SliderTrackShape _defaultTrackShape = RoundedRectSliderTrackShape(useV2Slider: useV2Slider);
final SliderTickMarkShape _defaultTickMarkShape = RoundSliderTickMarkShape(useV2Slider: useV2Slider);
const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
final SliderComponentShape _defaultThumbShape = RoundSliderThumbShape(useV2Slider: useV2Slider);
final SliderComponentShape _defaultValueIndicatorShape = useV2Slider ? const RectangularSliderValueIndicatorShape() : const PaddleSliderValueIndicatorShape();
const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
// The value indicator's color is not the same as the thumb and active track
// (which can be defined by activeColor) if the
// RectangularSliderValueIndicatorShape is used. In all other cases, the
// value indicator is assumed to be the same as the active color.
final SliderComponentShape valueIndicatorShape = sliderTheme.valueIndicatorShape ?? _defaultValueIndicatorShape;
Color valueIndicatorColor;
if (valueIndicatorShape is RectangularSliderValueIndicatorShape) {
valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));
} else {
valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
}
sliderTheme = sliderTheme.copyWith( sliderTheme = sliderTheme.copyWith(
trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight, trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary, activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
@ -575,41 +536,31 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12), disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12),
disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12), disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary, thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), const Color(0xFFFFFFFF)), disabledThumbColor: sliderTheme.disabledThumbColor ?? theme.colorScheme.onSurface.withOpacity(0.38),
overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12), overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
valueIndicatorColor: valueIndicatorColor, valueIndicatorColor: widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary,
trackShape: sliderTheme.trackShape ?? _defaultTrackShape, trackShape: sliderTheme.trackShape ?? _defaultTrackShape,
tickMarkShape: sliderTheme.tickMarkShape ?? _defaultTickMarkShape, tickMarkShape: sliderTheme.tickMarkShape ?? _defaultTickMarkShape,
thumbShape: sliderTheme.thumbShape ?? _defaultThumbShape, thumbShape: sliderTheme.thumbShape ?? _defaultThumbShape,
overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape, overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
valueIndicatorShape: valueIndicatorShape, valueIndicatorShape: sliderTheme.valueIndicatorShape ?? _defaultValueIndicatorShape,
showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator, showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith( valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
), ),
); );
// This size is used as the max bounds for the painting of the value return _SliderRenderObjectWidget(
// indicators It must be kept in sync with the function with the same name
// in range_slider.dart.
Size _screenSize() => MediaQuery.of(context).size;
return CompositedTransformTarget(
link: _layerLink,
child: _SliderRenderObjectWidget(
value: _unlerp(widget.value), value: _unlerp(widget.value),
divisions: widget.divisions, divisions: widget.divisions,
label: widget.label, label: widget.label,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor, mediaQueryData: MediaQuery.of(context),
screenSize: _screenSize(),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this, state: this,
semanticFormatterCallback: widget.semanticFormatterCallback, semanticFormatterCallback: widget.semanticFormatterCallback,
useV2Slider: widget.useV2Slider,
),
); );
} }
@ -631,28 +582,8 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
), ),
); );
} }
final LayerLink _layerLink = LayerLink();
OverlayEntry overlayEntry;
void showValueIndicator() {
if (overlayEntry == null) {
overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return CompositedTransformFollower(
link: _layerLink,
child: _ValueIndicatorRenderObjectWidget(
state: this,
),
);
},
);
Overlay.of(context).insert(overlayEntry);
}
}
} }
class _SliderRenderObjectWidget extends LeafRenderObjectWidget { class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
const _SliderRenderObjectWidget({ const _SliderRenderObjectWidget({
Key key, Key key,
@ -660,28 +591,24 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.divisions, this.divisions,
this.label, this.label,
this.sliderTheme, this.sliderTheme,
this.textScaleFactor, this.mediaQueryData,
this.screenSize,
this.onChanged, this.onChanged,
this.onChangeStart, this.onChangeStart,
this.onChangeEnd, this.onChangeEnd,
this.state, this.state,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.useV2Slider,
}) : super(key: key); }) : super(key: key);
final double value; final double value;
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 Size screenSize;
final ValueChanged<double> onChanged; final ValueChanged<double> onChanged;
final ValueChanged<double> onChangeStart; final ValueChanged<double> onChangeStart;
final ValueChanged<double> onChangeEnd; final ValueChanged<double> onChangeEnd;
final SemanticFormatterCallback semanticFormatterCallback; final SemanticFormatterCallback semanticFormatterCallback;
final _SliderState state; final _SliderState state;
final bool useV2Slider;
@override @override
_RenderSlider createRenderObject(BuildContext context) { _RenderSlider createRenderObject(BuildContext context) {
@ -690,8 +617,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
divisions: divisions, divisions: divisions,
label: label, label: label,
sliderTheme: sliderTheme, sliderTheme: sliderTheme,
textScaleFactor: textScaleFactor, mediaQueryData: mediaQueryData,
screenSize: screenSize,
onChanged: onChanged, onChanged: onChanged,
onChangeStart: onChangeStart, onChangeStart: onChangeStart,
onChangeEnd: onChangeEnd, onChangeEnd: onChangeEnd,
@ -699,7 +625,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback, semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform, platform: Theme.of(context).platform,
useV2Slider: useV2Slider,
); );
} }
@ -711,8 +636,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
..screenSize = screenSize
..onChanged = onChanged ..onChanged = onChanged
..onChangeStart = onChangeStart ..onChangeStart = onChangeStart
..onChangeEnd = onChangeEnd ..onChangeEnd = onChangeEnd
@ -730,8 +654,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
int divisions, int divisions,
String label, String label,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
double textScaleFactor, MediaQueryData mediaQueryData,
Size screenSize,
TargetPlatform platform, TargetPlatform platform,
ValueChanged<double> onChanged, ValueChanged<double> onChanged,
SemanticFormatterCallback semanticFormatterCallback, SemanticFormatterCallback semanticFormatterCallback,
@ -739,7 +662,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
this.onChangeEnd, this.onChangeEnd,
@required _SliderState state, @required _SliderState state,
@required TextDirection textDirection, @required TextDirection textDirection,
bool useV2Slider,
}) : assert(value != null && value >= 0.0 && value <= 1.0), }) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(state != null), assert(state != null),
assert(textDirection != null), assert(textDirection != null),
@ -749,12 +671,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_value = value, _value = value,
_divisions = divisions, _divisions = divisions,
_sliderTheme = sliderTheme, _sliderTheme = sliderTheme,
_textScaleFactor = textScaleFactor, _mediaQueryData = mediaQueryData,
_screenSize = screenSize,
_onChanged = onChanged, _onChanged = onChanged,
_state = state, _state = state,
_textDirection = textDirection, _textDirection = textDirection {
_useV2Slider = useV2Slider {
_updateLabelPainter(); _updateLabelPainter();
final GestureArenaTeam team = GestureArenaTeam(); final GestureArenaTeam team = GestureArenaTeam();
_drag = HorizontalDragGestureRecognizer() _drag = HorizontalDragGestureRecognizer()
@ -775,12 +695,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_valueIndicatorAnimation = CurvedAnimation( _valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController, parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) { );
if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
_state.overlayEntry.remove();
_state.overlayEntry = null;
}
});
_enableAnimation = CurvedAnimation( _enableAnimation = CurvedAnimation(
parent: _state.enableController, parent: _state.enableController,
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -911,26 +826,18 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
markNeedsPaint(); markNeedsPaint();
} }
double get textScaleFactor => _textScaleFactor; MediaQueryData get mediaQueryData => _mediaQueryData;
double _textScaleFactor; MediaQueryData _mediaQueryData;
set textScaleFactor(double value) { set mediaQueryData(MediaQueryData value) {
if (value == _textScaleFactor) { if (value == _mediaQueryData) {
return; return;
} }
_textScaleFactor = value; _mediaQueryData = value;
// Media query data includes the textScaleFactor, so we need to update the
// label painter.
_updateLabelPainter(); _updateLabelPainter();
} }
Size get screenSize => _screenSize;
Size _screenSize;
set screenSize(Size value) {
if (value == _screenSize) {
return;
}
_screenSize = value;
markNeedsPaint();
}
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) {
@ -964,8 +871,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_updateLabelPainter(); _updateLabelPainter();
} }
final bool _useV2Slider;
bool get showValueIndicator { bool get showValueIndicator {
bool showValueIndicator; bool showValueIndicator;
switch (_sliderTheme.showValueIndicator) { switch (_sliderTheme.showValueIndicator) {
@ -1010,7 +915,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
text: label, text: label,
) )
..textDirection = textDirection ..textDirection = textDirection
..textScaleFactor = textScaleFactor ..textScaleFactor = _mediaQueryData.textScaleFactor
..layout(); ..layout();
} else { } else {
_labelPainter.text = null; _labelPainter.text = null;
@ -1070,7 +975,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
} }
void _startInteraction(Offset globalPosition) { void _startInteraction(Offset globalPosition) {
_state.showValueIndicator();
if (isInteractive) { if (isInteractive) {
_active = true; _active = true;
// We supply the *current* value as the start location, so that if we have // We supply the *current* value as the start location, so that if we have
@ -1104,7 +1008,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_active = false; _active = false;
_currentDragValue = 0.0; _currentDragValue = 0.0;
_state.overlayController.reverse(); _state.overlayController.reverse();
if (showValueIndicator && _state.interactionTimer == null) { if (showValueIndicator && _state.interactionTimer == null) {
_state.valueIndicatorController.reverse(); _state.valueIndicatorController.reverse();
} }
@ -1227,8 +1130,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
isEnabled: isInteractive, isEnabled: isInteractive,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
).width; ).width;
final double padding = _useV2Slider ? trackRect.height : tickMarkWidth; final double adjustedTrackWidth = trackRect.width - tickMarkWidth;
final double adjustedTrackWidth = trackRect.width - padding;
// If the tick marks would be too dense, don't bother painting them. // If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) { if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy; final double dy = trackRect.center.dy;
@ -1236,7 +1138,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
final double value = i / divisions; final double value = i / divisions;
// The ticks are mapped to be within the track, so the tick mark width // The ticks are mapped to be within the track, so the tick mark width
// must be subtracted from the track width. // must be subtracted from the track width.
final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2; final double dx = trackRect.left + value * adjustedTrackWidth + tickMarkWidth / 2;
final Offset tickMarkOffset = Offset(dx, dy); final Offset tickMarkOffset = Offset(dx, dy);
_sliderTheme.tickMarkShape.paint( _sliderTheme.tickMarkShape.paint(
context, context,
@ -1254,10 +1156,9 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) { if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
if (showValueIndicator) { if (showValueIndicator) {
_state.paintValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.valueIndicatorShape.paint( _sliderTheme.valueIndicatorShape.paint(
context, context,
offset + thumbCenter, thumbCenter,
activationAnimation: _valueIndicatorAnimation, activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation, enableAnimation: _enableAnimation,
isDiscrete: isDiscrete, isDiscrete: isDiscrete,
@ -1266,24 +1167,20 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
textDirection: _textDirection, textDirection: _textDirection,
value: _value, value: _value,
textScaleFactor: textScaleFactor,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
); );
};
} }
} }
_sliderTheme.thumbShape.paint( _sliderTheme.thumbShape.paint(
context, context,
thumbCenter, thumbCenter,
activationAnimation: _overlayAnimation, activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation, enableAnimation: _enableAnimation,
isDiscrete: isDiscrete, isDiscrete: isDiscrete,
labelPainter: _labelPainter, labelPainter: _labelPainter,
parentBox: this, parentBox: this,
sliderTheme: _sliderTheme, sliderTheme: _sliderTheme,
textDirection: _textDirection, textDirection: _textDirection,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
value: _value, value: _value,
); );
} }
@ -1323,59 +1220,3 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
} }
} }
} }
class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
const _ValueIndicatorRenderObjectWidget({
this.state,
});
final _SliderState state;
@override
_RenderValueIndicator createRenderObject(BuildContext context) {
return _RenderValueIndicator(
state: state,
);
}
@override
void updateRenderObject(BuildContext context, _RenderValueIndicator renderObject) {
renderObject._state = state;
}
}
class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_RenderValueIndicator({
_SliderState state,
}) : _state = state {
_valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn,
);
}
Animation<double> _valueIndicatorAnimation;
_SliderState _state;
@override
bool get sizedByParent => true;
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_valueIndicatorAnimation.addListener(markNeedsPaint);
_state.positionController.addListener(markNeedsPaint);
}
@override
void detach() {
_valueIndicatorAnimation.removeListener(markNeedsPaint);
_state.positionController.removeListener(markNeedsPaint);
super.detach();
}
@override
void paint(PaintingContext context, Offset offset) {
if (_state.paintValueIndicator != null) {
_state.paintValueIndicator(context, offset);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -94,24 +94,6 @@ void main() {
]); ]);
}); });
testWidgets('Slider V2 uses ThemeData slider theme if present', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(
sliderBox,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor),
);
});
testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async { testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
@ -130,28 +112,6 @@ void main() {
); );
}); });
testWidgets('Slider V2 overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
final SliderThemeData customTheme = sliderTheme.copyWith(
activeTrackColor: Colors.purple,
inactiveTrackColor: Colors.purple.withAlpha(0x3d),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(
sliderBox,
paints
..rrect(color: customTheme.disabledActiveTrackColor)
..rrect(color: customTheme.disabledInactiveTrackColor),
);
});
testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async { testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
@ -258,40 +218,6 @@ void main() {
expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff))); expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff)));
}); });
testWidgets('Slider V2 track draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
const Radius radius = Radius.circular(2);
const Radius activatedRadius = Radius.circular(3);
// The enabled slider thumb has track segments that extend to and from
// the center of the thumb.
expect(
sliderBox,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false, useV2Slider: true));
await tester.pumpAndSettle(); // wait for disable animation
// The disabled slider thumb is the same size as the enabled thumb.
expect(
sliderBox,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
);
});
testWidgets('Default slider track draws correctly', (WidgetTester tester) async { testWidgets('Default slider track draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
@ -314,7 +240,12 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
// The disabled slider thumb is the same size as the enabled thumb. // The disabled slider thumb has a horizontal gap between itself and the
// track segments. Therefore, the track segments are shorter since they do
// not extend to the center of the thumb, but rather the outer edge of th
// gap. As a result, the `right` value of the first segment is less than it
// is above, and the `left` value of the second segment is more than it is
// above.
expect( expect(
sliderBox, sliderBox,
paints paints
@ -428,200 +359,14 @@ void main() {
); );
}); });
testWidgets('Slider V2 value indicator shape draws correctly', (WidgetTester tester) async { testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
); );
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith( final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) { Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp( return Directionality(
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale),
child: Material(
child: Row(
children: <Widget>[
Expanded(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: sliderValue,
label: value,
divisions: 3,
onChanged: (double d) { },
useV2Slider: true,
),
),
),
],
),
),
),
),
);
}
await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-20.0, -12.0),
Offset(20.0, -34.0),
Offset(0.0, -38.0),
],
color: const Color(0xf55f5f5f),
),
);
await gesture.up();
// Test that it expands with a larger label.
await tester.pumpWidget(buildApp('1000'));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect()
..rrect()
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-30.0, -12.0),
Offset(30.0, -34.0),
Offset(0.0, -38.0),
],
color: const Color(0xf55f5f5f),
),
);
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);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect()
..rrect()
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-12.0, -12.0),
Offset(110.0, -34.0),
Offset(0.0, -38.0),
],
color: const Color(0xf55f5f5f),
)
);
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);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect()
..rrect()
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-110.0, -12.0),
Offset(12.0, -34.0),
Offset(0.0, -38.0),
],
color: const Color(0xf55f5f5f),
)
);
await gesture.up();
// Test that the box decreases in height when the text scale gets smaller.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 0.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect()
..rrect()
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-12.0, -12.0),
Offset(61.0, -16.0),
Offset(0.0, -20.0),
],
excludes: const <Offset>[
Offset(0.0, -38.0)
],
color: const Color(0xf55f5f5f),
)
);
await gesture.up();
// Test that the box increases in height when the text scale gets bigger.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 2.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect()
..rrect()
..path(
includes: const <Offset>[
Offset(0.0, 0.0),
Offset(-12.0, -16.0),
Offset(208.0, -40.0),
Offset(0.0, -50.0),
],
color: const Color(0xf55f5f5f),
)
);
await gesture.up();
}, skip: isBrowser);
testWidgets('Default paddle slider value indicator shape draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: MediaQuery( child: MediaQuery(
data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale), data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScale),
@ -643,20 +388,19 @@ void main() {
), ),
), ),
), ),
),
); );
} }
await tester.pumpWidget(buildApp('1')); await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
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);
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -678,7 +422,7 @@ void main() {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -699,7 +443,7 @@ void main() {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -720,7 +464,7 @@ void main() {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -741,7 +485,7 @@ void main() {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -767,7 +511,7 @@ void main() {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
valueIndicatorBox, sliderBox,
paints paints
..path( ..path(
color: sliderTheme.valueIndicatorColor, color: sliderTheme.valueIndicatorColor,
@ -814,36 +558,6 @@ void main() {
); );
}); });
testWidgets('The slider V2 track height can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(trackHeight: 16);
const Radius radius = Radius.circular(8);
const Radius activatedRadius = Radius.circular(9);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
// Top and bottom are centerY (300) + and - trackRadius (8).
expect(
sliderBox,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false, useV2Slider: true));
await tester.pumpAndSettle(); // wait for disable animation
// The disabled thumb is smaller so the active track has to paint longer to
// get to the edge.
expect(
sliderBox,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
);
});
testWidgets('The default slider thumb shape sizes can be overridden', (WidgetTester tester) async { testWidgets('The default slider thumb shape sizes can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith( final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
thumbShape: const RoundSliderThumbShape( thumbShape: const RoundSliderThumbShape(
@ -892,6 +606,7 @@ void main() {
); );
}); });
testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async { testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith( final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5), tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5),
@ -925,39 +640,6 @@ void main() {
); );
}); });
testWidgets('The default slider V2 tick mark shape size can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5, useV2Slider: true),
activeTickMarkColor: const Color(0xfadedead),
inactiveTickMarkColor: const Color(0xfadebeef),
disabledActiveTickMarkColor: const Color(0xfadecafe),
disabledInactiveTickMarkColor: const Color(0xfadeface),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(
sliderBox,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, enabled: false, useV2Slider: true));
await tester.pumpAndSettle();
expect(
sliderBox,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.disabledInactiveTickMarkColor),
);
});
testWidgets('The default slider overlay shape size can be overridden', (WidgetTester tester) async { testWidgets('The default slider overlay shape size can be overridden', (WidgetTester tester) async {
const double uniqueOverlayRadius = 23; const double uniqueOverlayRadius = 23;
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith( final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
@ -1118,7 +800,7 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
// Tap the center of the track and wait for animations to finish. // Tap the center of the track and wait for animations to finish.
final Offset center = tester.getCenter(find.byType(Slider)); final Offset center = tester.getCenter(find.byType(Slider));
@ -1126,132 +808,41 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Only 1 value indicator. // Only 1 value indicator.
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawRect, 0)); expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawCircle, 0)); expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1)); expect(sliderBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('PaddleSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
),
value: 0.5,
divisions: 4,
));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Nothing to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('Default slider value indicator shape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
),
value: 0.5,
divisions: 4,
));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Nothing to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up(); await gesture.up();
}); });
testWidgets('PaddleRangeSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async { testWidgets('PaddleRangeSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator. // Pump a slider with just a value indicator.
await tester.pumpWidget(_buildRangeApp( await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
),
values: const RangeValues(0, 0.5),
divisions: 4,
));
// final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(RangeSlider));
final TestGesture gesture = await tester.startGesture(center);
// No value indicator path to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for each value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
await gesture.up();
});
testWidgets('Default range indicator shape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildRangeApp(
ThemeData().sliderTheme.copyWith( ThemeData().sliderTheme.copyWith(
trackHeight: 0, trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay, overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb, thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark, tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always, showValueIndicator: ShowValueIndicator.always,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
), ),
values: const RangeValues(0, 0.5), value: 0.5,
divisions: 4, divisions: 4,
)); ));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
// Tap the center of the track to kick off the animation of the value indicator. // Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(RangeSlider)); final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center); final TestGesture gesture = await tester.startGesture(center);
// No value indicator path to paint at scale 0. // Nothing to paint at scale 0.
await tester.pump(); await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0)); expect(sliderBox, paintsNothing);
// Painting a path for each value indicator. // Painting a path for the value indicator.
await tester.pump(const Duration(milliseconds: 16)); await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2)); expect(sliderBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up(); await gesture.up();
}); });
@ -1262,7 +853,6 @@ Widget _buildApp(
double value = 0.0, double value = 0.0,
bool enabled = true, bool enabled = true,
int divisions, int divisions,
bool useV2Slider = false,
}) { }) {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null; final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return MaterialApp( return MaterialApp(
@ -1275,31 +865,6 @@ Widget _buildApp(
label: '$value', label: '$value',
onChanged: onChanged, onChanged: onChanged,
divisions: divisions, divisions: divisions,
useV2Slider: useV2Slider
),
),
),
),
);
}
Widget _buildRangeApp(
SliderThemeData sliderTheme, {
RangeValues values = const RangeValues(0, 0),
bool enabled = true,
int divisions,
}) {
final ValueChanged<RangeValues> onChanged = enabled ? (RangeValues d) => values = d : null;
return MaterialApp(
home: Scaffold(
body: Center(
child: SliderTheme(
data: sliderTheme,
child: RangeSlider(
values: values,
labels: RangeLabels(values.start.toString(), values.end.toString()),
onChanged: onChanged,
divisions: divisions,
), ),
), ),
), ),

View File

@ -169,10 +169,7 @@ void main() {
(ByteData data) { }, (ByteData data) { },
); );
final RenderObject renderObject = tester.renderObject(find.byType(RangeSlider)); final RenderObject renderObject = tester.renderObject(find.byType(RangeSlider));
expect(renderObject.debugNeedsLayout, isTrue);
bool sliderBoxNeedsLayout;
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
}); });
testWidgets('Slider relayout upon system fonts changes', (WidgetTester tester) async { testWidgets('Slider relayout upon system fonts changes', (WidgetTester tester) async {
@ -195,10 +192,7 @@ void main() {
(ByteData data) { }, (ByteData data) { },
); );
final RenderObject renderObject = tester.renderObject(find.byType(Slider)); final RenderObject renderObject = tester.renderObject(find.byType(Slider));
expect(renderObject.debugNeedsLayout, isTrue);
bool sliderBoxNeedsLayout;
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
}); });
testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async { testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async {