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,
TextDirection textDirection,
Thumb thumb,
bool isPressed,
}) {
final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween(
@ -131,8 +130,6 @@ class _CustomThumbShape extends SliderComponentShape {
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
double textScaleFactor,
Size sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween(
@ -172,8 +169,6 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
double textScaleFactor,
Size sizeWithOverflow,
}) {
final Canvas canvas = context.canvas;
final ColorTween enableColor = ColorTween(
@ -273,21 +268,15 @@ class _SlidersState extends State<_Sliders> {
),
),
),
SliderTheme(
data: const SliderThemeData(
showValueIndicator: ShowValueIndicator.always,
),
child: Slider.adaptive(
label: _continuousValue.toStringAsFixed(6).toString(),
value: _continuousValue,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_continuousValue = value;
});
},
),
Slider.adaptive(
value: _continuousValue,
min: 0.0,
max: 100.0,
onChanged: (double value) {
setState(() {
_continuousValue = value;
});
},
),
const Text('Continuous with Editable Numerical Value'),
],
@ -325,7 +314,7 @@ class _SlidersState extends State<_Sliders> {
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: theme.colorScheme.onSurface.withOpacity(0.5),
activeTickMarkColor: theme.colorScheme.onSurface.withOpacity(0.7),
inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7),
inactiveTickMarkColor: theme.colorScheme.surface.withOpacity(0.7),
overlayColor: theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,

View File

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

View File

@ -22,11 +22,6 @@ import 'theme.dart';
// RangeValues _dollarsRange = RangeValues(50, 100);
// 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.
///
/// Used to select a range from a range of values.
@ -132,7 +127,6 @@ class RangeSlider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
this.useV2Slider = false,
}) : assert(values != null),
assert(min != null),
assert(max != null),
@ -141,7 +135,6 @@ class RangeSlider extends StatefulWidget {
assert(values.start >= min && values.start <= max),
assert(values.end >= min && values.end <= max),
assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key);
/// The currently selected values for this range slider.
@ -340,19 +333,6 @@ class RangeSlider extends StatefulWidget {
/// {@end-tool}
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.
static const double _minTouchTargetWidth = kMinInteractiveDimension;
@ -374,7 +354,6 @@ class RangeSlider extends StatefulWidget {
properties.add(StringProperty('labelEnd', labels?.end));
properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>.has('semanticFormatterCallback', semanticFormatterCallback));
}
}
@ -398,10 +377,6 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
AnimationController startPositionController;
AnimationController endPositionController;
Timer interactionTimer;
// Value Indicator paint Animation that appears on the Overlay.
PaintRangeValueIndicator paintTopValueIndicator;
PaintRangeValueIndicator paintBottomValueIndicator;
@override
void initState() {
@ -545,7 +520,14 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
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
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
// the default shapes and text styles are aligned to the Material
// 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(
trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
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),
thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
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),
valueIndicatorColor: valueIndicatorColor,
valueIndicatorColor: widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary,
rangeTrackShape: sliderTheme.rangeTrackShape ?? _defaultTrackShape,
rangeTickMarkShape: sliderTheme.rangeTickMarkShape ?? _defaultTickMarkShape,
rangeThumbShape: sliderTheme.rangeThumbShape ?? _defaultThumbShape,
overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
rangeValueIndicatorShape: valueIndicatorShape,
rangeValueIndicatorShape: sliderTheme.rangeValueIndicatorShape ?? _defaultValueIndicatorShape,
showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith(
color: theme.colorScheme.onPrimary,
@ -612,49 +571,19 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
thumbSelector: sliderTheme.thumbSelector ?? _defaultRangeThumbSelector,
);
// This size is used as the max bounds for the painting of the value
// 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),
divisions: widget.divisions,
labels: widget.labels,
sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
screenSize: _screenSize(),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
useV2Slider: widget.useV2Slider,
),
return _RangeSliderRenderObjectWidget(
values: _unlerpRangeValues(widget.values),
divisions: widget.divisions,
labels: widget.labels,
sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
);
}
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 {
@ -665,13 +594,11 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
this.labels,
this.sliderTheme,
this.textScaleFactor,
this.screenSize,
this.onChanged,
this.onChangeStart,
this.onChangeEnd,
this.state,
this.semanticFormatterCallback,
this.useV2Slider,
}) : super(key: key);
final RangeValues values;
@ -679,13 +606,11 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
final RangeLabels labels;
final SliderThemeData sliderTheme;
final double textScaleFactor;
final Size screenSize;
final ValueChanged<RangeValues> onChanged;
final ValueChanged<RangeValues> onChangeStart;
final ValueChanged<RangeValues> onChangeEnd;
final RangeSemanticFormatterCallback semanticFormatterCallback;
final _RangeSliderState state;
final bool useV2Slider;
@override
_RenderRangeSlider createRenderObject(BuildContext context) {
@ -696,7 +621,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
sliderTheme: sliderTheme,
theme: Theme.of(context),
textScaleFactor: textScaleFactor,
screenSize: screenSize,
onChanged: onChanged,
onChangeStart: onChangeStart,
onChangeEnd: onChangeEnd,
@ -704,7 +628,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform,
useV2Slider: useV2Slider,
);
}
@ -717,7 +640,6 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
..sliderTheme = sliderTheme
..theme = Theme.of(context)
..textScaleFactor = textScaleFactor
..screenSize = screenSize
..onChanged = onChanged
..onChangeStart = onChangeStart
..onChangeEnd = onChangeEnd
@ -735,7 +657,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
SliderThemeData sliderTheme,
ThemeData theme,
double textScaleFactor,
Size screenSize,
TargetPlatform platform,
ValueChanged<RangeValues> onChanged,
RangeSemanticFormatterCallback semanticFormatterCallback,
@ -743,7 +664,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
this.onChangeEnd,
@required _RangeSliderState state,
@required TextDirection textDirection,
bool useV2Slider,
}) : assert(values != null),
assert(values.start >= 0.0 && values.start <= 1.0),
assert(values.end >= 0.0 && values.end <= 1.0),
@ -757,11 +677,9 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_sliderTheme = sliderTheme,
_theme = theme,
_textScaleFactor = textScaleFactor,
_screenSize = screenSize,
_onChanged = onChanged,
_state = state,
_textDirection = textDirection,
_useV2Slider = useV2Slider {
_textDirection = textDirection {
_updateLabelPainters();
final GestureArenaTeam team = GestureArenaTeam();
_drag = HorizontalDragGestureRecognizer()
@ -782,12 +700,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
_state.overlayEntry.remove();
_state.overlayEntry = null;
}
});
);
_enableAnimation = CurvedAnimation(
parent: _state.enableController,
curve: Curves.easeInOut,
@ -936,15 +849,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
_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> _onChanged;
set onChanged(ValueChanged<RangeValues> value) {
@ -1009,8 +913,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
return 0.05;
}
final bool _useV2Slider;
void _updateLabelPainters() {
_updateLabelPainter(Thumb.start);
_updateLabelPainter(Thumb.end);
@ -1107,7 +1009,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
void _startInteraction(Offset globalPosition) {
_state.showValueIndicator();
final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0) as double;
_lastThumbSelection = sliderTheme.thumbSelector(textDirection, values, tapValue, _thumbSize, size, 0);
@ -1299,11 +1200,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
isEnabled: isEnabled,
);
final bool startThumbSelected = _lastThumbSelection == Thumb.start;
final bool endThumbSelected = _lastThumbSelection == Thumb.end;
if (!_overlayAnimation.isDismissed) {
if (startThumbSelected) {
if (_lastThumbSelection == Thumb.start) {
_sliderTheme.overlayShape.paint(
context,
startThumbCenter,
@ -1317,7 +1215,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
value: startValue,
);
}
if (endThumbSelected) {
if (_lastThumbSelection == Thumb.end) {
_sliderTheme.overlayShape.paint(
context,
endThumbCenter,
@ -1338,8 +1236,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
isEnabled: isEnabled,
sliderTheme: _sliderTheme,
).width;
final double padding = _useV2Slider ? trackRect.height : tickMarkWidth;
final double adjustedTrackWidth = trackRect.width - padding;
final double adjustedTrackWidth = trackRect.width - tickMarkWidth;
// If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy;
@ -1347,7 +1244,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
final double value = i / divisions;
// The ticks are mapped to be within the track, so the tick mark 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);
_sliderTheme.rangeTickMarkShape.paint(
context,
@ -1376,27 +1273,22 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
final double bottomValue = isLastThumbStart ? endValue : startValue;
final double topValue = isLastThumbStart ? startValue : endValue;
final bool shouldPaintValueIndicators = isEnabled && labels != null && !_valueIndicatorAnimation.isDismissed && showValueIndicator;
final Size resolvedscreenSize = screenSize.isEmpty ? size : screenSize;
if (shouldPaintValueIndicators) {
_state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
bottomThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: false,
labelPainter: bottomLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: bottomThumb,
value: bottomValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
};
_sliderTheme.rangeValueIndicatorShape.paint(
context,
bottomThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: false,
labelPainter: bottomLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: bottomThumb,
value: bottomValue,
);
}
_sliderTheme.rangeThumbShape.paint(
@ -1409,7 +1301,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
textDirection: textDirection,
sliderTheme: _sliderTheme,
thumb: bottomThumb,
isPressed: bottomThumb == Thumb.start ? startThumbSelected : endThumbSelected,
);
if (shouldPaintValueIndicators) {
@ -1418,29 +1309,15 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
center: startThumbCenter,
labelPainter: _startLabelPainter,
activationAnimation: _valueIndicatorAnimation,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
final double endOffset = sliderTheme.rangeValueIndicatorShape.getHorizontalShift(
parentBox: this,
center: endThumbCenter,
labelPainter: _endLabelPainter,
activationAnimation: _valueIndicatorAnimation,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
final double startHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(
isEnabled,
isDiscrete,
labelPainter: _startLabelPainter,
textScaleFactor: textScaleFactor,
).width / 2;
final double endHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(
isEnabled,
isDiscrete,
labelPainter: _endLabelPainter,
textScaleFactor: textScaleFactor,
).width / 2;
final double startHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(isEnabled, isDiscrete, labelPainter: _startLabelPainter).width / 2;
final double endHalfWidth = sliderTheme.rangeValueIndicatorShape.getPreferredSize(isEnabled, isDiscrete, labelPainter: _endLabelPainter).width / 2;
double innerOverflow = startHalfWidth + endHalfWidth;
switch (textDirection) {
case TextDirection.ltr:
@ -1453,37 +1330,32 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
break;
}
_state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.rangeValueIndicatorShape.paint(
context,
topThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: thumbDelta < innerOverflow,
labelPainter: topLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: topThumb,
value: topValue,
textScaleFactor: textScaleFactor,
sizeWithOverflow: resolvedscreenSize,
);
};
_sliderTheme.rangeValueIndicatorShape.paint(
context,
topThumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: thumbDelta < innerOverflow,
labelPainter: topLabelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
thumb: topThumb,
value: topValue,
);
}
_sliderTheme.rangeThumbShape.paint(
context,
topThumbCenter,
activationAnimation: _overlayAnimation,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
isOnTop: thumbDelta < sliderTheme.rangeThumbShape.getPreferredSize(isEnabled, isDiscrete).width,
textDirection: textDirection,
sliderTheme: _sliderTheme,
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;
}
}
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.
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 }
/// A Material Design slider.
@ -129,7 +124,6 @@ class Slider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
this.useV2Slider = false,
}) : _sliderType = _SliderType.material,
assert(value != null),
assert(min != null),
@ -137,7 +131,6 @@ class Slider extends StatefulWidget {
assert(min <= max),
assert(value >= min && value <= max),
assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key);
/// Creates a [CupertinoSlider] if the target platform is iOS, creates a
@ -160,7 +153,6 @@ class Slider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
this.useV2Slider = false,
}) : _sliderType = _SliderType.adaptive,
assert(value != null),
assert(min != null),
@ -168,7 +160,6 @@ class Slider extends StatefulWidget {
assert(min <= max),
assert(value >= min && value <= max),
assert(divisions == null || divisions > 0),
assert(useV2Slider != null),
super(key: key);
/// The currently selected value for this slider.
@ -383,19 +374,6 @@ class Slider extends StatefulWidget {
/// Ignored if this slider is created with [Slider.adaptive]
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 ;
@override
@ -414,7 +392,6 @@ class Slider extends StatefulWidget {
properties.add(StringProperty('label', label));
properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
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.
AnimationController positionController;
Timer interactionTimer;
// Value Indicator Animation that appears on the Overlay.
PaintValueIndicator paintValueIndicator;
@override
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;
}
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
Widget build(BuildContext 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
// the default shapes and text styles are aligned to the Material
// 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(
trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
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),
disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
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),
valueIndicatorColor: valueIndicatorColor,
valueIndicatorColor: widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary,
trackShape: sliderTheme.trackShape ?? _defaultTrackShape,
tickMarkShape: sliderTheme.tickMarkShape ?? _defaultTickMarkShape,
thumbShape: sliderTheme.thumbShape ?? _defaultThumbShape,
overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
valueIndicatorShape: valueIndicatorShape,
valueIndicatorShape: sliderTheme.valueIndicatorShape ?? _defaultValueIndicatorShape,
showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith(
color: theme.colorScheme.onPrimary,
),
);
// This size is used as the max bounds for the painting of the value
// 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),
divisions: widget.divisions,
label: widget.label,
sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
screenSize: _screenSize(),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
useV2Slider: widget.useV2Slider,
),
return _SliderRenderObjectWidget(
value: _unlerp(widget.value),
divisions: widget.divisions,
label: widget.label,
sliderTheme: sliderTheme,
mediaQueryData: MediaQuery.of(context),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
);
}
@ -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 {
const _SliderRenderObjectWidget({
Key key,
@ -660,28 +591,24 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.divisions,
this.label,
this.sliderTheme,
this.textScaleFactor,
this.screenSize,
this.mediaQueryData,
this.onChanged,
this.onChangeStart,
this.onChangeEnd,
this.state,
this.semanticFormatterCallback,
this.useV2Slider,
}) : super(key: key);
final double value;
final int divisions;
final String label;
final SliderThemeData sliderTheme;
final double textScaleFactor;
final Size screenSize;
final MediaQueryData mediaQueryData;
final ValueChanged<double> onChanged;
final ValueChanged<double> onChangeStart;
final ValueChanged<double> onChangeEnd;
final SemanticFormatterCallback semanticFormatterCallback;
final _SliderState state;
final bool useV2Slider;
@override
_RenderSlider createRenderObject(BuildContext context) {
@ -690,8 +617,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
divisions: divisions,
label: label,
sliderTheme: sliderTheme,
textScaleFactor: textScaleFactor,
screenSize: screenSize,
mediaQueryData: mediaQueryData,
onChanged: onChanged,
onChangeStart: onChangeStart,
onChangeEnd: onChangeEnd,
@ -699,7 +625,6 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform,
useV2Slider: useV2Slider,
);
}
@ -711,8 +636,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..label = label
..sliderTheme = sliderTheme
..theme = Theme.of(context)
..textScaleFactor = textScaleFactor
..screenSize = screenSize
..mediaQueryData = mediaQueryData
..onChanged = onChanged
..onChangeStart = onChangeStart
..onChangeEnd = onChangeEnd
@ -730,8 +654,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
int divisions,
String label,
SliderThemeData sliderTheme,
double textScaleFactor,
Size screenSize,
MediaQueryData mediaQueryData,
TargetPlatform platform,
ValueChanged<double> onChanged,
SemanticFormatterCallback semanticFormatterCallback,
@ -739,22 +662,19 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
this.onChangeEnd,
@required _SliderState state,
@required TextDirection textDirection,
bool useV2Slider,
}) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(state != null),
assert(textDirection != null),
_platform = platform,
_semanticFormatterCallback = semanticFormatterCallback,
_label = label,
_value = value,
_divisions = divisions,
_sliderTheme = sliderTheme,
_textScaleFactor = textScaleFactor,
_screenSize = screenSize,
_onChanged = onChanged,
_state = state,
_textDirection = textDirection,
_useV2Slider = useV2Slider {
assert(state != null),
assert(textDirection != null),
_platform = platform,
_semanticFormatterCallback = semanticFormatterCallback,
_label = label,
_value = value,
_divisions = divisions,
_sliderTheme = sliderTheme,
_mediaQueryData = mediaQueryData,
_onChanged = onChanged,
_state = state,
_textDirection = textDirection {
_updateLabelPainter();
final GestureArenaTeam team = GestureArenaTeam();
_drag = HorizontalDragGestureRecognizer()
@ -775,12 +695,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_valueIndicatorAnimation = CurvedAnimation(
parent: _state.valueIndicatorController,
curve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
_state.overlayEntry.remove();
_state.overlayEntry = null;
}
});
);
_enableAnimation = CurvedAnimation(
parent: _state.enableController,
curve: Curves.easeInOut,
@ -911,26 +826,18 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
markNeedsPaint();
}
double get textScaleFactor => _textScaleFactor;
double _textScaleFactor;
set textScaleFactor(double value) {
if (value == _textScaleFactor) {
MediaQueryData get mediaQueryData => _mediaQueryData;
MediaQueryData _mediaQueryData;
set mediaQueryData(MediaQueryData value) {
if (value == _mediaQueryData) {
return;
}
_textScaleFactor = value;
_mediaQueryData = value;
// Media query data includes the textScaleFactor, so we need to update the
// label painter.
_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> _onChanged;
set onChanged(ValueChanged<double> value) {
@ -964,8 +871,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_updateLabelPainter();
}
final bool _useV2Slider;
bool get showValueIndicator {
bool showValueIndicator;
switch (_sliderTheme.showValueIndicator) {
@ -1010,7 +915,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
text: label,
)
..textDirection = textDirection
..textScaleFactor = textScaleFactor
..textScaleFactor = _mediaQueryData.textScaleFactor
..layout();
} else {
_labelPainter.text = null;
@ -1070,7 +975,6 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
void _startInteraction(Offset globalPosition) {
_state.showValueIndicator();
if (isInteractive) {
_active = true;
// 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;
_currentDragValue = 0.0;
_state.overlayController.reverse();
if (showValueIndicator && _state.interactionTimer == null) {
_state.valueIndicatorController.reverse();
}
@ -1227,8 +1130,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
isEnabled: isInteractive,
sliderTheme: _sliderTheme,
).width;
final double padding = _useV2Slider ? trackRect.height : tickMarkWidth;
final double adjustedTrackWidth = trackRect.width - padding;
final double adjustedTrackWidth = trackRect.width - tickMarkWidth;
// If the tick marks would be too dense, don't bother painting them.
if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) {
final double dy = trackRect.center.dy;
@ -1236,7 +1138,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
final double value = i / divisions;
// The ticks are mapped to be within the track, so the tick mark 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);
_sliderTheme.tickMarkShape.paint(
context,
@ -1254,36 +1156,31 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
if (showValueIndicator) {
_state.paintValueIndicator = (PaintingContext context, Offset offset) {
_sliderTheme.valueIndicatorShape.paint(
context,
offset + thumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
value: _value,
textScaleFactor: textScaleFactor,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
);
};
_sliderTheme.valueIndicatorShape.paint(
context,
thumbCenter,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
value: _value,
);
}
}
_sliderTheme.thumbShape.paint(
context,
thumbCenter,
activationAnimation: _overlayAnimation,
activationAnimation: _valueIndicatorAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
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 {
final ThemeData theme = ThemeData(
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 {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
@ -258,40 +218,6 @@ void main() {
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 {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
@ -314,7 +240,12 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
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(
sliderBox,
paints
@ -428,38 +359,32 @@ 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(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
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,
),
return 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) { },
),
),
],
),
),
],
),
),
),
@ -468,195 +393,14 @@ void main() {
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));
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,
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) { },
),
),
),
],
),
),
),
),
);
}
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,
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
@ -678,7 +422,7 @@ void main() {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
@ -699,7 +443,7 @@ void main() {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
@ -720,7 +464,7 @@ void main() {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
@ -741,7 +485,7 @@ void main() {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
sliderBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
@ -767,7 +511,7 @@ void main() {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
sliderBox,
paints
..path(
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 {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
thumbShape: const RoundSliderThumbShape(
@ -892,6 +606,7 @@ void main() {
);
});
testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5),
@ -913,7 +628,7 @@ void main() {
..circle(x: 771, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, enabled: false));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, enabled: false));
await tester.pumpAndSettle();
expect(
@ -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 {
const double uniqueOverlayRadius = 23;
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
@ -1118,7 +800,7 @@ void main() {
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.
final Offset center = tester.getCenter(find.byType(Slider));
@ -1126,132 +808,41 @@ void main() {
await tester.pumpAndSettle();
// Only 1 value indicator.
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawRect, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, 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));
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('PaddleRangeSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildRangeApp(
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(
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
),
values: const RangeValues(0, 0.5),
value: 0.5,
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.
final Offset center = tester.getCenter(find.byType(RangeSlider));
final Offset center = tester.getCenter(find.byType(Slider));
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();
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));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
@ -1262,7 +853,6 @@ Widget _buildApp(
double value = 0.0,
bool enabled = true,
int divisions,
bool useV2Slider = false,
}) {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return MaterialApp(
@ -1275,31 +865,6 @@ Widget _buildApp(
label: '$value',
onChanged: onChanged,
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) { },
);
final RenderObject renderObject = tester.renderObject(find.byType(RangeSlider));
bool sliderBoxNeedsLayout;
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
expect(renderObject.debugNeedsLayout, isTrue);
});
testWidgets('Slider relayout upon system fonts changes', (WidgetTester tester) async {
@ -194,11 +191,8 @@ void main() {
SystemChannels.system.codec.encodeMessage(data),
(ByteData data) { },
);
final RenderObject renderObject = tester.renderObject(find.byType(Slider));
bool sliderBoxNeedsLayout;
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
final RenderObject renderObject = tester.renderObject(find.byType(Slider));
expect(renderObject.debugNeedsLayout, isTrue);
});
testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async {