Unverified Commit cfe992d0 authored by Anthony's avatar Anthony Committed by GitHub

[Material] Custom track, tick mark, and overlay shape painters for Slider (#25008)

Create a slider shape for custom track, tick mark, and overlay painting, for the material slider.
parent d5d47f63
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
...@@ -584,15 +585,23 @@ class _RenderSlider extends RenderBox { ...@@ -584,15 +585,23 @@ class _RenderSlider extends RenderBox {
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
} }
static const Duration _positionAnimationDuration = Duration(milliseconds: 75); static const Duration _positionAnimationDuration = Duration(milliseconds: 75);
static const double _overlayRadius = 16.0;
static const double _overlayDiameter = _overlayRadius * 2.0;
static const double _trackHeight = 2.0;
static const double _preferredTrackWidth = 144.0;
static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter;
static const Duration _minimumInteractionTime = Duration(milliseconds: 500); static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
static final Animatable<double> _overlayRadiusTween = Tween<double>(begin: 0.0, end: _overlayRadius);
// This value is the touch target, 48, multiplied by 3.
static const double _minPreferredTrackWidth = 144.0;
// Compute the largest width and height needed to paint the slider shapes,
// other than the track shape. It is assumed that these shapes are vertically
// centered on the track.
double get _maxSliderPartWidth => _sliderPartSizes.map((Size size) => size.width).reduce(math.max);
double get _maxSliderPartHeight => _sliderPartSizes.map((Size size) => size.width).reduce(math.max);
List<Size> get _sliderPartSizes => <Size>[
_sliderTheme.overlayShape.getPreferredSize(isInteractive, isDiscrete),
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete),
_sliderTheme.tickMarkShape.getPreferredSize(isEnabled: isInteractive, sliderTheme: sliderTheme),
];
double get _minPreferredTrackHeight =>_sliderTheme.trackHeight;
_SliderState _state; _SliderState _state;
Animation<double> _overlayAnimation; Animation<double> _overlayAnimation;
...@@ -604,7 +613,15 @@ class _RenderSlider extends RenderBox { ...@@ -604,7 +613,15 @@ class _RenderSlider extends RenderBox {
bool _active = false; bool _active = false;
double _currentDragValue = 0.0; double _currentDragValue = 0.0;
double get _trackLength => size.width - _overlayDiameter; // This rect is used in gesture calculations, where the gesture coordinates
// are relative to the sliders origin. Therefore, the offset is passed as
// (0,0).
Rect get _trackRect => _sliderTheme.trackShape.getPreferredRect(
parentBox: this,
offset: Offset.zero,
sliderTheme: _sliderTheme,
isDiscrete: false,
);
bool get isInteractive => onChanged != null; bool get isInteractive => onChanged != null;
...@@ -818,7 +835,7 @@ class _RenderSlider extends RenderBox { ...@@ -818,7 +835,7 @@ class _RenderSlider extends RenderBox {
} }
double _getValueFromGlobalPosition(Offset globalPosition) { double _getValueFromGlobalPosition(Offset globalPosition) {
final double visualPosition = (globalToLocal(globalPosition).dx - _overlayRadius) / _trackLength; final double visualPosition = (globalToLocal(globalPosition).dx - _trackRect.left) / _trackRect.width;
return _getValueFromVisualPosition(visualPosition); return _getValueFromVisualPosition(visualPosition);
} }
...@@ -874,7 +891,7 @@ class _RenderSlider extends RenderBox { ...@@ -874,7 +891,7 @@ class _RenderSlider extends RenderBox {
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (isInteractive) { if (isInteractive) {
final double valueDelta = details.primaryDelta / _trackLength; final double valueDelta = details.primaryDelta / _trackRect.width;
switch (textDirection) { switch (textDirection) {
case TextDirection.rtl: case TextDirection.rtl:
_currentDragValue -= valueDelta; _currentDragValue -= valueDelta;
...@@ -907,25 +924,16 @@ class _RenderSlider extends RenderBox { ...@@ -907,25 +924,16 @@ class _RenderSlider extends RenderBox {
} }
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
return math.max(
_overlayDiameter,
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width,
);
}
@override @override
double computeMaxIntrinsicWidth(double height) { double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
// This doesn't quite match the definition of computeMaxIntrinsicWidth,
// but it seems within the spirit...
return _preferredTotalWidth;
}
@override @override
double computeMinIntrinsicHeight(double width) => _overlayDiameter; double computeMinIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight);
@override @override
double computeMaxIntrinsicHeight(double width) => _overlayDiameter; double computeMaxIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight);
@override @override
bool get sizedByParent => true; bool get sizedByParent => true;
...@@ -933,126 +941,91 @@ class _RenderSlider extends RenderBox { ...@@ -933,126 +941,91 @@ class _RenderSlider extends RenderBox {
@override @override
void performResize() { void performResize() {
size = Size( size = Size(
constraints.hasBoundedWidth ? constraints.maxWidth : _preferredTotalWidth, constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
constraints.hasBoundedHeight ? constraints.maxHeight : _overlayDiameter, constraints.hasBoundedHeight ? constraints.maxHeight : max(_minPreferredTrackHeight, _maxSliderPartHeight),
); );
} }
void _paintTickMarks(
Canvas canvas,
Rect trackLeft,
Rect trackRight,
Paint leftPaint,
Paint rightPaint,
) {
if (isDiscrete) {
// The ticks are tiny circles that are the same height as the track.
const double tickRadius = _trackHeight / 2.0;
final double trackWidth = trackRight.right - trackLeft.left;
final double dx = (trackWidth - _trackHeight) / divisions;
// If the ticks would be too dense, don't bother painting them.
if (dx >= 3.0 * _trackHeight) {
for (int i = 0; i <= divisions; i += 1) {
final double left = trackLeft.left + i * dx;
final Offset center = Offset(left + tickRadius, trackLeft.top + tickRadius);
if (trackLeft.contains(center)) {
canvas.drawCircle(center, tickRadius, leftPaint);
} else if (trackRight.contains(center)) {
canvas.drawCircle(center, tickRadius, rightPaint);
}
}
}
}
}
void _paintOverlay(Canvas canvas, Offset center) {
if (!_overlayAnimation.isDismissed) {
// TODO(gspencer): We don't really follow the spec here for overlays.
// The spec says to use 16% opacity for drawing over light material,
// and 32% for colored material, but we don't really have a way to
// know what the underlying color is, so there's no easy way to
// implement this. Choosing the "light" version for now.
final Paint overlayPaint = Paint()..color = _sliderTheme.overlayColor;
final double radius = _overlayRadiusTween.evaluate(_overlayAnimation);
canvas.drawCircle(center, radius, overlayPaint);
}
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
final double trackLength = size.width - 2 * _overlayRadius;
final double value = _state.positionController.value; final double value = _state.positionController.value;
final ColorTween activeTrackEnableColor = ColorTween(begin: _sliderTheme.disabledActiveTrackColor, end: _sliderTheme.activeTrackColor);
final ColorTween inactiveTrackEnableColor = ColorTween(begin: _sliderTheme.disabledInactiveTrackColor, end: _sliderTheme.inactiveTrackColor);
final ColorTween activeTickMarkEnableColor = ColorTween(begin: _sliderTheme.disabledActiveTickMarkColor, end: _sliderTheme.activeTickMarkColor);
final ColorTween inactiveTickMarkEnableColor = ColorTween(begin: _sliderTheme.disabledInactiveTickMarkColor, end: _sliderTheme.inactiveTickMarkColor);
final Paint activeTrackPaint = Paint()..color = activeTrackEnableColor.evaluate(_enableAnimation);
final Paint inactiveTrackPaint = Paint()..color = inactiveTrackEnableColor.evaluate(_enableAnimation);
final Paint activeTickMarkPaint = Paint()..color = activeTickMarkEnableColor.evaluate(_enableAnimation);
final Paint inactiveTickMarkPaint = Paint()..color = inactiveTickMarkEnableColor.evaluate(_enableAnimation);
// The visual position is the position of the thumb from 0 to 1 from left
// to right. In left to right, this is the same as the value, but it is
// reversed for right to left text.
double visualPosition; double visualPosition;
Paint leftTrackPaint;
Paint rightTrackPaint;
Paint leftTickMarkPaint;
Paint rightTickMarkPaint;
switch (textDirection) { switch (textDirection) {
case TextDirection.rtl: case TextDirection.rtl:
visualPosition = 1.0 - value; visualPosition = 1.0 - value;
leftTrackPaint = inactiveTrackPaint;
rightTrackPaint = activeTrackPaint;
leftTickMarkPaint = inactiveTickMarkPaint;
rightTickMarkPaint = activeTickMarkPaint;
break; break;
case TextDirection.ltr: case TextDirection.ltr:
visualPosition = value; visualPosition = value;
leftTrackPaint = activeTrackPaint;
rightTrackPaint = inactiveTrackPaint;
leftTickMarkPaint = activeTickMarkPaint;
rightTickMarkPaint = inactiveTickMarkPaint;
break; break;
} }
const double trackRadius = _trackHeight / 2.0; final Rect trackRect = _sliderTheme.trackShape.getPreferredRect(
const double thumbGap = 2.0; parentBox: this,
offset: offset,
final double trackVerticalCenter = offset.dy + (size.height) / 2.0; sliderTheme: _sliderTheme,
final double trackLeft = offset.dx + _overlayRadius; isDiscrete: isDiscrete
final double trackTop = trackVerticalCenter - trackRadius; );
final double trackBottom = trackVerticalCenter + trackRadius; final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy);
final double trackRight = trackLeft + trackLength;
final double trackActive = trackLeft + trackLength * visualPosition;
final double thumbRadius = _sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width / 2.0;
final double trackActiveLeft = math.max(0.0, trackActive - thumbRadius - thumbGap * (1.0 - _enableAnimation.value));
final double trackActiveRight = math.min(trackActive + thumbRadius + thumbGap * (1.0 - _enableAnimation.value), trackRight);
final Rect trackLeftRect = Rect.fromLTRB(trackLeft, trackTop, trackActiveLeft, trackBottom);
final Rect trackRightRect = Rect.fromLTRB(trackActiveRight, trackTop, trackRight, trackBottom);
final Offset thumbCenter = Offset(trackActive, trackVerticalCenter); _sliderTheme.trackShape.paint(
context,
offset,
parentBox: this,
sliderTheme: _sliderTheme,
enableAnimation: _enableAnimation,
textDirection: _textDirection,
thumbCenter: thumbCenter,
isDiscrete: isDiscrete,
isEnabled: isInteractive
);
// Paint the track. // TODO(closkmith): Move this to paint after the thumb.
if (visualPosition > 0.0) { if (!_overlayAnimation.isDismissed) {
canvas.drawRect(trackLeftRect, leftTrackPaint); _sliderTheme.overlayShape.paint(
} context,
if (visualPosition < 1.0) { thumbCenter,
canvas.drawRect(trackRightRect, rightTrackPaint); activationAnimation: _overlayAnimation,
enableAnimation: _enableAnimation,
isDiscrete: isDiscrete,
labelPainter: _labelPainter,
parentBox: this,
sliderTheme: _sliderTheme,
textDirection: _textDirection,
value: _value,
);
} }
_paintOverlay(canvas, thumbCenter); if (isDiscrete) {
// TODO(clocksmith): Align tick mark centers to ends of track by not subtracting diameter from length.
_paintTickMarks( final double tickMarkWidth = _sliderTheme.tickMarkShape.getPreferredSize(
canvas, isEnabled: isInteractive,
trackLeftRect, sliderTheme: _sliderTheme,
trackRightRect, ).width;
leftTickMarkPaint, for (int i = 0; i <= divisions; i++) {
rightTickMarkPaint, final double tickValue = 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 tickX = trackRect.left + tickValue * (trackRect.width - tickMarkWidth) + tickMarkWidth / 2;
final double tickY = trackRect.center.dy;
final Offset tickMarkOffset = Offset(tickX, tickY);
_sliderTheme.tickMarkShape.paint(
context,
tickMarkOffset,
parentBox: this,
sliderTheme: _sliderTheme,
enableAnimation: _enableAnimation,
textDirection: _textDirection,
thumbCenter: thumbCenter,
isEnabled: isInteractive,
); );
}
}
if (isInteractive && label != null && if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
_valueIndicatorAnimation.status != AnimationStatus.dismissed) {
if (showValueIndicator) { if (showValueIndicator) {
_sliderTheme.valueIndicatorShape.paint( _sliderTheme.valueIndicatorShape.paint(
context, context,
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' show Path; import 'dart:ui' show Path, lerpDouble;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -21,12 +21,21 @@ import 'theme_data.dart'; ...@@ -21,12 +21,21 @@ import 'theme_data.dart';
/// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically /// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically
/// rebuilt if the theme later changes. /// rebuilt if the theme later changes.
/// ///
/// The slider is as big as the largest of
/// the [SliderComponentShape.getPreferredSize] of the thumb shape,
/// the [SliderComponentShape.getPreferredSize] of the overlay shape,
/// and the [SliderTickMarkShape.getPreferredSize] of the tick mark shape
///
/// See also: /// See also:
/// ///
/// * [SliderThemeData], which describes the actual configuration of a slider /// * [SliderThemeData], which describes the actual configuration of a slider
/// theme. /// theme.
/// * [SliderComponentShape], which can be used to create custom shapes for /// * [SliderComponentShape], which can be used to create custom shapes for
/// the slider thumb and value indicator. /// the slider thumb, overlay, and value indicator.
/// * [SliderTrackShape], which can be used to create custom shapes for the
/// slider track.
/// * [SliderTickMarkShape], which can be used to create custom shapes for the
/// slider tick marks.
class SliderTheme extends InheritedWidget { class SliderTheme extends InheritedWidget {
/// Applies the given theme [data] to [child]. /// Applies the given theme [data] to [child].
/// ///
...@@ -123,8 +132,14 @@ enum ShowValueIndicator { ...@@ -123,8 +132,14 @@ enum ShowValueIndicator {
/// * The "thumb", which is a shape that slides horizontally when the user /// * The "thumb", which is a shape that slides horizontally when the user
/// drags it. /// drags it.
/// * The "track", which is the line that the slider thumb slides along. /// * The "track", which is the line that the slider thumb slides along.
/// * The "value indicator", which is a shape that pops up when the user /// * The "tick marks", which are regularly spaced marks that are drawn when
/// is dragging the thumb to indicate the value being selected. /// using discrete divisions.
/// * The "value indicator", which appears when the user is dragging the thumb
/// to indicate the value being selected.
/// * The "overlay", which appears around the thumb, and is shown when the
/// thumb is pressed, focused, or hovered. It is painted underneath the
/// thumb, so it must extend beyond the bounds of the thumb itself to
/// actually be visible.
/// * The "active" side of the slider is the side between the thumb and the /// * The "active" side of the slider is the side between the thumb and the
/// minimum value. /// minimum value.
/// * The "inactive" side of the slider is the side between the thumb and the /// * The "inactive" side of the slider is the side between the thumb and the
...@@ -132,10 +147,12 @@ enum ShowValueIndicator { ...@@ -132,10 +147,12 @@ enum ShowValueIndicator {
/// * The [Slider] is disabled when it is not accepting user input. See /// * The [Slider] is disabled when it is not accepting user input. See
/// [Slider] for details on when this happens. /// [Slider] for details on when this happens.
/// ///
/// The thumb and the value indicator may have their shapes and behavior /// The thumb, track, tick marks, value indicator, and overlay can be customized
/// customized by creating your own [SliderComponentShape] that does what /// by creating subclasses of [SliderTrackShape],
/// you want. See [RoundSliderThumbShape] and /// [SliderComponentShape], and/or [SliderTickMarkShape]. See
/// [PaddleSliderValueIndicatorShape] for examples. /// [RoundSliderThumbShape], [RectangularSliderTrackShape],
/// [RoundSliderTickMarkShape], [PaddleSliderValueIndicatorShape], and
/// [RoundSliderOverlayShape] for examples.
/// ///
/// See also: /// See also:
/// ///
...@@ -144,7 +161,9 @@ enum ShowValueIndicator { ...@@ -144,7 +161,9 @@ enum ShowValueIndicator {
/// * [Theme] widget, which performs a similar function to [SliderTheme], /// * [Theme] widget, which performs a similar function to [SliderTheme],
/// but for overall themes. /// but for overall themes.
/// * [ThemeData], which has a default [SliderThemeData]. /// * [ThemeData], which has a default [SliderThemeData].
/// * [SliderTrackShape], to define custom slider track shapes.
/// * [SliderComponentShape], to define custom slider component shapes. /// * [SliderComponentShape], to define custom slider component shapes.
/// * [SliderTickMarkShape], to define custom slider tick mark shapes.
class SliderThemeData extends Diagnosticable { class SliderThemeData extends Diagnosticable {
/// Create a [SliderThemeData] given a set of exact values. All the values /// Create a [SliderThemeData] given a set of exact values. All the values
/// must be specified. /// must be specified.
...@@ -181,6 +200,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -181,6 +200,7 @@ class SliderThemeData extends Diagnosticable {
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
const SliderThemeData({ const SliderThemeData({
@required this.trackHeight,
@required this.activeTrackColor, @required this.activeTrackColor,
@required this.inactiveTrackColor, @required this.inactiveTrackColor,
@required this.disabledActiveTrackColor, @required this.disabledActiveTrackColor,
...@@ -193,11 +213,15 @@ class SliderThemeData extends Diagnosticable { ...@@ -193,11 +213,15 @@ class SliderThemeData extends Diagnosticable {
@required this.disabledThumbColor, @required this.disabledThumbColor,
@required this.overlayColor, @required this.overlayColor,
@required this.valueIndicatorColor, @required this.valueIndicatorColor,
@required this.trackShape,
@required this.tickMarkShape,
@required this.thumbShape, @required this.thumbShape,
@required this.overlayShape,
@required this.valueIndicatorShape, @required this.valueIndicatorShape,
@required this.showValueIndicator, @required this.showValueIndicator,
@required this.valueIndicatorTextStyle, @required this.valueIndicatorTextStyle,
}) : assert(activeTrackColor != null), }) : assert(trackHeight != null),
assert(activeTrackColor != null),
assert(inactiveTrackColor != null), assert(inactiveTrackColor != null),
assert(disabledActiveTrackColor != null), assert(disabledActiveTrackColor != null),
assert(disabledInactiveTrackColor != null), assert(disabledInactiveTrackColor != null),
...@@ -209,7 +233,10 @@ class SliderThemeData extends Diagnosticable { ...@@ -209,7 +233,10 @@ class SliderThemeData extends Diagnosticable {
assert(disabledThumbColor != null), assert(disabledThumbColor != null),
assert(overlayColor != null), assert(overlayColor != null),
assert(valueIndicatorColor != null), assert(valueIndicatorColor != null),
assert(trackShape != null),
assert(tickMarkShape != null),
assert(thumbShape != null), assert(thumbShape != null),
assert(overlayShape != null),
assert(valueIndicatorShape != null), assert(valueIndicatorShape != null),
assert(valueIndicatorTextStyle != null), assert(valueIndicatorTextStyle != null),
assert(showValueIndicator != null); assert(showValueIndicator != null);
...@@ -256,6 +283,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -256,6 +283,7 @@ class SliderThemeData extends Diagnosticable {
const int overlayLightAlpha = 0x29; // 16% opacity const int overlayLightAlpha = 0x29; // 16% opacity
return SliderThemeData( return SliderThemeData(
trackHeight: 2.0,
activeTrackColor: primaryColor.withAlpha(activeTrackAlpha), activeTrackColor: primaryColor.withAlpha(activeTrackAlpha),
inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha), inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha),
disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha), disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha),
...@@ -268,13 +296,19 @@ class SliderThemeData extends Diagnosticable { ...@@ -268,13 +296,19 @@ class SliderThemeData extends Diagnosticable {
disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha), disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha),
overlayColor: primaryColor.withAlpha(overlayLightAlpha), overlayColor: primaryColor.withAlpha(overlayLightAlpha),
valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha), valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha),
trackShape: const RectangularSliderTrackShape(),
tickMarkShape: const RoundSliderTickMarkShape(),
thumbShape: const RoundSliderThumbShape(), thumbShape: const RoundSliderThumbShape(),
overlayShape: const RoundSliderOverlayShape(),
valueIndicatorShape: const PaddleSliderValueIndicatorShape(), valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
valueIndicatorTextStyle: valueIndicatorTextStyle, valueIndicatorTextStyle: valueIndicatorTextStyle,
showValueIndicator: ShowValueIndicator.onlyForDiscrete, showValueIndicator: ShowValueIndicator.onlyForDiscrete,
); );
} }
/// The height of the [Slider] track.
final double trackHeight;
/// The color of the [Slider] track between the [Slider.min] position and the /// The color of the [Slider] track between the [Slider.min] position and the
/// current thumb position. /// current thumb position.
final Color activeTrackColor; final Color activeTrackColor;
...@@ -323,17 +357,39 @@ class SliderThemeData extends Diagnosticable { ...@@ -323,17 +357,39 @@ class SliderThemeData extends Diagnosticable {
/// The color given to the [valueIndicatorShape] to draw itself with. /// The color given to the [valueIndicatorShape] to draw itself with.
final Color valueIndicatorColor; final Color valueIndicatorColor;
/// The shape and behavior that will be used to draw the [Slider]'s thumb. /// The shape that will be used to draw the [Slider]'s track.
/// ///
/// This can be customized by implementing a subclass of /// The [SliderTrackShape.getPreferredRect] method is used to to map
/// [SliderComponentShape]. /// slider-relative gesture coordinates to the correct thumb position on the
/// track. It is also used to horizontally position tick marks, when he slider
/// is discrete.
///
/// The default value is [RectangularSliderTrackShape].
final SliderTrackShape trackShape;
/// The shape that will be used to draw the [Slider]'s tick marks.
///
/// The [SliderTickMarkShape.getPreferredSize] is used to help determine the
/// location of each tick mark on the track. The slider's minimum size will
/// be at least this big.
///
/// The default value is [RoundSliderTickMarkShape].
final SliderTickMarkShape tickMarkShape;
/// The shape that will be used to draw the [Slider]'s overlay.
///
/// Both the [overlayColor] and a non default [overlayShape] may be specified.
/// In this case, the [overlayColor] is only used if the [overlayShape]
/// explicitly does so.
///
/// The default value is [RoundSliderOverlayShape].
final SliderComponentShape overlayShape;
/// The shape that will be used to draw the [Slider]'s thumb.
final SliderComponentShape thumbShape; final SliderComponentShape thumbShape;
/// The shape and behavior that will be used to draw the [Slider]'s value /// The shape that will be used to draw the [Slider]'s value
/// indicator. /// indicator.
///
/// This can be customized by implementing a subclass of
/// [SliderComponentShape].
final SliderComponentShape valueIndicatorShape; final SliderComponentShape valueIndicatorShape;
/// Whether the value indicator should be shown for different types of /// Whether the value indicator should be shown for different types of
...@@ -352,6 +408,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -352,6 +408,7 @@ class SliderThemeData extends Diagnosticable {
/// Creates a copy of this object but with the given fields replaced with the /// Creates a copy of this object but with the given fields replaced with the
/// new values. /// new values.
SliderThemeData copyWith({ SliderThemeData copyWith({
double trackHeight,
Color activeTrackColor, Color activeTrackColor,
Color inactiveTrackColor, Color inactiveTrackColor,
Color disabledActiveTrackColor, Color disabledActiveTrackColor,
...@@ -364,12 +421,16 @@ class SliderThemeData extends Diagnosticable { ...@@ -364,12 +421,16 @@ class SliderThemeData extends Diagnosticable {
Color disabledThumbColor, Color disabledThumbColor,
Color overlayColor, Color overlayColor,
Color valueIndicatorColor, Color valueIndicatorColor,
SliderTrackShape trackShape,
SliderTickMarkShape tickMarkShape,
SliderComponentShape thumbShape, SliderComponentShape thumbShape,
SliderComponentShape overlayShape,
SliderComponentShape valueIndicatorShape, SliderComponentShape valueIndicatorShape,
ShowValueIndicator showValueIndicator, ShowValueIndicator showValueIndicator,
TextStyle valueIndicatorTextStyle, TextStyle valueIndicatorTextStyle,
}) { }) {
return SliderThemeData( return SliderThemeData(
trackHeight: trackHeight ?? this.trackHeight,
activeTrackColor: activeTrackColor ?? this.activeTrackColor, activeTrackColor: activeTrackColor ?? this.activeTrackColor,
inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor, inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor,
disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor, disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor,
...@@ -382,7 +443,10 @@ class SliderThemeData extends Diagnosticable { ...@@ -382,7 +443,10 @@ class SliderThemeData extends Diagnosticable {
disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor, disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
overlayColor: overlayColor ?? this.overlayColor, overlayColor: overlayColor ?? this.overlayColor,
valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor, valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor,
trackShape: trackShape ?? this.trackShape,
tickMarkShape: tickMarkShape ?? this.tickMarkShape,
thumbShape: thumbShape ?? this.thumbShape, thumbShape: thumbShape ?? this.thumbShape,
overlayShape: overlayShape ?? this.overlayShape,
valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape, valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape,
showValueIndicator: showValueIndicator ?? this.showValueIndicator, showValueIndicator: showValueIndicator ?? this.showValueIndicator,
valueIndicatorTextStyle: valueIndicatorTextStyle ?? this.valueIndicatorTextStyle, valueIndicatorTextStyle: valueIndicatorTextStyle ?? this.valueIndicatorTextStyle,
...@@ -399,6 +463,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -399,6 +463,7 @@ class SliderThemeData extends Diagnosticable {
assert(b != null); assert(b != null);
assert(t != null); assert(t != null);
return SliderThemeData( return SliderThemeData(
trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t),
activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t),
inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t), inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t),
disabledActiveTrackColor: Color.lerp(a.disabledActiveTrackColor, b.disabledActiveTrackColor, t), disabledActiveTrackColor: Color.lerp(a.disabledActiveTrackColor, b.disabledActiveTrackColor, t),
...@@ -411,7 +476,10 @@ class SliderThemeData extends Diagnosticable { ...@@ -411,7 +476,10 @@ class SliderThemeData extends Diagnosticable {
disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t), disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t),
valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t), valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t),
trackShape: t < 0.5 ? a.trackShape : b.trackShape,
tickMarkShape: t < 0.5 ? a.tickMarkShape : b.tickMarkShape,
thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape, thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape,
overlayShape: t < 0.5 ? a.overlayShape : b.overlayShape,
valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape, valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator, showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
valueIndicatorTextStyle: TextStyle.lerp(a.valueIndicatorTextStyle, b.valueIndicatorTextStyle, t), valueIndicatorTextStyle: TextStyle.lerp(a.valueIndicatorTextStyle, b.valueIndicatorTextStyle, t),
...@@ -421,6 +489,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -421,6 +489,7 @@ class SliderThemeData extends Diagnosticable {
@override @override
int get hashCode { int get hashCode {
return hashValues( return hashValues(
trackHeight,
activeTrackColor, activeTrackColor,
inactiveTrackColor, inactiveTrackColor,
disabledActiveTrackColor, disabledActiveTrackColor,
...@@ -433,7 +502,10 @@ class SliderThemeData extends Diagnosticable { ...@@ -433,7 +502,10 @@ class SliderThemeData extends Diagnosticable {
disabledThumbColor, disabledThumbColor,
overlayColor, overlayColor,
valueIndicatorColor, valueIndicatorColor,
trackShape,
tickMarkShape,
thumbShape, thumbShape,
overlayShape,
valueIndicatorShape, valueIndicatorShape,
showValueIndicator, showValueIndicator,
valueIndicatorTextStyle, valueIndicatorTextStyle,
...@@ -449,22 +521,26 @@ class SliderThemeData extends Diagnosticable { ...@@ -449,22 +521,26 @@ class SliderThemeData extends Diagnosticable {
return false; return false;
} }
final SliderThemeData otherData = other; final SliderThemeData otherData = other;
return otherData.activeTrackColor == activeTrackColor && return otherData.trackHeight == trackHeight
otherData.inactiveTrackColor == inactiveTrackColor && && otherData.activeTrackColor == activeTrackColor
otherData.disabledActiveTrackColor == disabledActiveTrackColor && && otherData.inactiveTrackColor == inactiveTrackColor
otherData.disabledInactiveTrackColor == disabledInactiveTrackColor && && otherData.disabledActiveTrackColor == disabledActiveTrackColor
otherData.activeTickMarkColor == activeTickMarkColor && && otherData.disabledInactiveTrackColor == disabledInactiveTrackColor
otherData.inactiveTickMarkColor == inactiveTickMarkColor && && otherData.activeTickMarkColor == activeTickMarkColor
otherData.disabledActiveTickMarkColor == disabledActiveTickMarkColor && && otherData.inactiveTickMarkColor == inactiveTickMarkColor
otherData.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor && && otherData.disabledActiveTickMarkColor == disabledActiveTickMarkColor
otherData.thumbColor == thumbColor && && otherData.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor
otherData.disabledThumbColor == disabledThumbColor && && otherData.thumbColor == thumbColor
otherData.overlayColor == overlayColor && && otherData.disabledThumbColor == disabledThumbColor
otherData.valueIndicatorColor == valueIndicatorColor && && otherData.overlayColor == overlayColor
otherData.thumbShape == thumbShape && && otherData.valueIndicatorColor == valueIndicatorColor
otherData.valueIndicatorShape == valueIndicatorShape && && otherData.trackShape == trackShape
otherData.showValueIndicator == showValueIndicator && && otherData.tickMarkShape == tickMarkShape
otherData.valueIndicatorTextStyle == valueIndicatorTextStyle; && otherData.thumbShape == thumbShape
&& otherData.overlayShape == overlayShape
&& otherData.valueIndicatorShape == valueIndicatorShape
&& otherData.showValueIndicator == showValueIndicator
&& otherData.valueIndicatorTextStyle == valueIndicatorTextStyle;
} }
@override @override
...@@ -478,6 +554,7 @@ class SliderThemeData extends Diagnosticable { ...@@ -478,6 +554,7 @@ class SliderThemeData extends Diagnosticable {
valueIndicatorTextStyle: defaultTheme.accentTextTheme.body2, valueIndicatorTextStyle: defaultTheme.accentTextTheme.body2,
); );
properties.add(DiagnosticsProperty<Color>('activeTrackColor', activeTrackColor, defaultValue: defaultData.activeTrackColor)); properties.add(DiagnosticsProperty<Color>('activeTrackColor', activeTrackColor, defaultValue: defaultData.activeTrackColor));
properties.add(DiagnosticsProperty<Color>('activeTrackColor', activeTrackColor, defaultValue: defaultData.activeTrackColor));
properties.add(DiagnosticsProperty<Color>('inactiveTrackColor', inactiveTrackColor, defaultValue: defaultData.inactiveTrackColor)); properties.add(DiagnosticsProperty<Color>('inactiveTrackColor', inactiveTrackColor, defaultValue: defaultData.inactiveTrackColor));
properties.add(DiagnosticsProperty<Color>('disabledActiveTrackColor', disabledActiveTrackColor, defaultValue: defaultData.disabledActiveTrackColor, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<Color>('disabledActiveTrackColor', disabledActiveTrackColor, defaultValue: defaultData.disabledActiveTrackColor, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<Color>('disabledInactiveTrackColor', disabledInactiveTrackColor, defaultValue: defaultData.disabledInactiveTrackColor, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<Color>('disabledInactiveTrackColor', disabledInactiveTrackColor, defaultValue: defaultData.disabledInactiveTrackColor, level: DiagnosticLevel.debug));
...@@ -489,22 +566,213 @@ class SliderThemeData extends Diagnosticable { ...@@ -489,22 +566,213 @@ class SliderThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<Color>('overlayColor', overlayColor, defaultValue: defaultData.overlayColor, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<Color>('overlayColor', overlayColor, defaultValue: defaultData.overlayColor, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<Color>('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor)); properties.add(DiagnosticsProperty<Color>('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor));
properties.add(DiagnosticsProperty<SliderTrackShape>('trackShape', trackShape, defaultValue: defaultData.trackShape, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SliderTickMarkShape>('tickMarkShape', tickMarkShape, defaultValue: defaultData.tickMarkShape, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SliderComponentShape>('overlayShape', overlayShape, defaultValue: defaultData.overlayShape, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape, level: DiagnosticLevel.debug));
properties.add(EnumProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator)); properties.add(EnumProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator));
properties.add(DiagnosticsProperty<TextStyle>('valueIndicatorTextStyle', valueIndicatorTextStyle, defaultValue: defaultData.valueIndicatorTextStyle)); properties.add(DiagnosticsProperty<TextStyle>('valueIndicatorTextStyle', valueIndicatorTextStyle, defaultValue: defaultData.valueIndicatorTextStyle));
} }
} }
/// Base class for slider thumb and value indicator shapes. // TEMPLATES FOR ALL SHAPES
/// {@template flutter.material.slider.shape.context}
/// [context] is the same context for the render box of the [Slider].
/// {@endtemplate}
/// ///
/// Create a subclass of this if you would like a custom slider thumb or /// {@template flutter.material.slider.shape.center}
/// value indicator shape. /// [center] is the offset of the center where this shape should be painted.
/// This offset is relative to the origin of the [context] canvas.
/// {@endtemplate}
///
/// {@template flutter.material.slider.shape.sliderTheme}
/// [sliderTheme] is the theme assigned to the [Slider] that this shape
/// belongs to.
/// {@endtemplate}
///
/// {@template flutter.material.slider.shape.isEnabled}
/// [isEnabled] has the same value as [Slider.isInteractive]. If true, the
/// slider will respond to input.
/// {@endtemplate}
///
/// {@template flutter.material.slider.shape.enableAnimation}
/// [enableAnimation] is an animation triggered when the [Slider] is enabled,
/// and it reverses when the slider is disabled. Enabled is the
/// [Slider.isInteractive] state. Use this to paint intermediate frames for
/// this shape when the slider changes enabled state.
/// {@endtemplate}
///
/// {@template flutter.material.slider.shape.isDiscrete}
/// [isDiscrete] is true if [Slider.divisions] is non-null. If true, the
/// slider will render tick marks on top of the track.
/// {@endtemplate}
///
/// {@template flutter.material.slider.shape.parentBox}
/// [parentBox] is the [RenderBox] of the [Slider]. Its attributes, such as
/// size, can be used to assist in painting this shape.
/// {@endtemplate}
/// Base class for slider track shapes.
///
/// The slider's thumb moves along the track. A discrete slider's tick marks
/// are drawn after the track, but before the thumb, and are aligned with the
/// track.
///
/// The [getPreferredRect] helps position the slider thumb and tick marks
/// relative to the track.
/// ///
/// See also: /// See also:
/// ///
/// * [RoundSliderThumbShape] for a simple example of a thumb shape. /// * [RectangularSliderTrackShape], which is the the default track shape.
/// * [PaddleSliderValueIndicatorShape], for a complex example of a value /// * [SliderTickMarkShape], which is the default tick mark shape.
/// * [SliderComponentShape], which is the base class for custom a component
/// shape.
abstract class SliderTrackShape {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliderTrackShape();
/// Returns the preferred bounds of the shape.
///
/// It is used to provide horizontal boundaries for the thumb's position, and
/// to help position the slider thumb and tick marks relative to the track.
///
/// [parentBox] can be used to help determine the preferredRect relative to
/// attributes of the render box of the slider itself, such as size.
///
/// [offset] is relative to the caller's bounding box. It can be used to
/// convert gesture coordinates from global to slider-relative coordinates.
///
/// {@macro flutter.material.slider.shape.sliderTheme}
///
/// {@macro flutter.material.slider.shape.isEnabled}
///
/// {@macro flutter.material.slider.shape.isDiscrete}
Rect getPreferredRect({
RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData sliderTheme,
bool isEnabled,
bool isDiscrete,
});
/// Paints the track shape based on the state passed to it.
///
/// {@macro flutter.material.slider.shape.context}
///
/// [offset] is the offset of the origin of the [parentBox] to the origin of
/// its [context] canvas. This shape must be painted relative to this
/// offset. See [PaintingContextCallback].
///
/// {@macro flutter.material.slider.shape.parentBox}
///
/// {@macro flutter.material.slider.shape.sliderTheme}
///
/// {@macro flutter.material.slider.shape.enableAnimation}
///
/// [thumbCenter] is the offset of the center of the thumb relative to the
/// origin of the [PaintingContext.canvas]. It can be used as the point that
/// divides the track into 2 segments.
///
/// {@macro flutter.material.slider.shape.isEnabled}
///
/// {@macro flutter.material.slider.shape.isDiscrete}
///
/// [textDirection] can be used to determine how the track segments are
/// painted depending on whether they are active or not. The track segment
/// between the start of the slider and the thumb is the active track segment.
/// The track segment between the thumb and the end of the slider is the
/// inactive track segment. In LTR text direction, the start of the slider is
/// on the left, and in RTL text direction, the start of the slider is on the
/// right.
void paint(
PaintingContext context,
Offset offset, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
Offset thumbCenter,
bool isEnabled,
bool isDiscrete,
TextDirection textDirection,
});
}
/// Base class for slider tick mark shapes.
///
/// Create a subclass of this if you would like a custom slider tick mark shape.
/// This is a simplified version of [SliderComponentShape] with a
/// [SliderThemeData] passed when getting the preferred size.
///
/// See also:
///
/// * [RoundSliderTickMarkShape] for a simple example of a tick mark shape.
/// * [SliderTrackShape] for the base class for custom a track shape.
/// * [SliderComponentShape] for the base class for custom a component shape.
abstract class SliderTickMarkShape {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliderTickMarkShape();
/// Returns the preferred size of the shape.
///
/// It is used to help position the tick marks within the slider.
///
/// {@macro flutter.material.slider.shape.sliderTheme}
///
/// {@macro flutter.material.slider.shape.isEnabled}
Size getPreferredSize({
SliderThemeData sliderTheme,
bool isEnabled,
});
/// Paints the slider track.
///
/// {@macro flutter.material.slider.shape.context}
///
/// {@macro flutter.material.slider.shape.center}
///
/// {@macro flutter.material.slider.shape.parentBox}
///
/// {@macro flutter.material.slider.shape.sliderTheme}
///
/// {@macro flutter.material.slider.shape.enableAnimation}
///
/// {@macro flutter.material.slider.shape.isEnabled}
///
/// [textDirection] can be used to determine how the tick marks are painting
/// depending on whether they are on an active track segment or not. The track
/// segment between the start of the slider and the thumb is the active track
/// segment. The track segment between the thumb and the end of the slider is
/// the inactive track segment. In LTR text direction, the start of the slider
/// is on the left, and in RTL text direction, the start of the slider is on
/// the right.
void paint(
PaintingContext context,
Offset center, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
Offset thumbCenter,
bool isEnabled,
TextDirection textDirection,
});
}
/// Base class for slider thumb, thumb overlay, and value indicator shapes.
///
/// Create a subclass of this if you would like a custom shape.
///
/// All shapes are painted to the same canvas and ordering is important.
/// The overlay is painted first, then the value indicator, then the thumb.
///
/// See also:
///
/// * [RoundSliderThumbShape], which is the the default thumb shape.
/// * [RoundSliderOverlayShape], which is the the default overlay shape.
/// * [PaddleSliderValueIndicatorShape], which is the the default value
/// indicator shape. /// indicator shape.
abstract class SliderComponentShape { abstract class SliderComponentShape {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
...@@ -516,21 +784,34 @@ abstract class SliderComponentShape { ...@@ -516,21 +784,34 @@ abstract class SliderComponentShape {
/// Paints the shape, taking into account the state passed to it. /// Paints the shape, taking into account the state passed to it.
/// ///
/// {@macro flutter.material.slider.shape.context}
///
/// {@macro flutter.material.slider.shape.center}
///
/// [activationAnimation] is an animation triggered when the user beings /// [activationAnimation] is an animation triggered when the user beings
/// to interact with the slider. It reverses when the user stops interacting /// to interact with the slider. It reverses when the user stops interacting
/// with the slider. /// with the slider.
/// ///
/// [enableAnimation] is an animation triggered when the [Slider] is enabled, /// {@macro flutter.material.slider.shape.enableAnimation}
/// and it reverses when the slider is disabled.
/// ///
/// [value] is the current parametric value (from 0.0 to 1.0) of the slider. /// {@macro flutter.material.slider.shape.isDiscrete}
/// ///
/// If [labelPainter] is non-null, then [labelPainter.paint] should be /// If [labelPainter] is non-null, then [labelPainter.paint] should be
/// called with the location that the label should appear. If the labelPainter /// called with the location that the label should appear. If the labelPainter
/// passed is null, then no label was supplied to the [Slider]. /// passed is null, then no label was supplied to the [Slider].
///
/// {@macro flutter.material.slider.shape.parentBox}
///
/// {@macro flutter.material.slider.shape.sliderTheme}
///
/// [textDirection] can be used to determine how any extra text or graphics,
/// besides the text painted by the [labelPainter] should be positioned. The
/// [labelPainter] already has the [textDirection] set.
///
/// [value] is the current parametric value (from 0.0 to 1.0) of the slider.
void paint( void paint(
PaintingContext context, PaintingContext context,
Offset thumbCenter, { Offset center, {
Animation<double> activationAnimation, Animation<double> activationAnimation,
Animation<double> enableAnimation, Animation<double> enableAnimation,
bool isDiscrete, bool isDiscrete,
...@@ -542,14 +823,186 @@ abstract class SliderComponentShape { ...@@ -542,14 +823,186 @@ abstract class SliderComponentShape {
}); });
} }
/// This is the default shape to a [Slider]'s thumb if no // The following shapes are the material defaults.
/// other shape is specified.
/// This is the default shape of a [Slider]'s track.
///
/// It paints a solid colored rectangle, vertically centered in the
/// [parentBox]. The track rectangle extends to the bounds of the [parentBox],
/// but is padded by the [RoundSliderOverlayShape] radius. The height is defined
/// by the [SliderThemeData.trackHeight]. The color is determined by the
/// [Slider]'s enabled state and the track piece's active state which are
/// defined by:
/// [SliderThemeData.activeTrackColor],
/// [SliderThemeData.inactiveTrackColor],
/// [SliderThemeData.disabledActiveTrackColor],
/// [SliderThemeData.disabledInactiveTrackColor].
/// ///
/// See also: /// See also:
/// ///
/// * [Slider] for the component that this is meant to display this shape. /// * [Slider] for the component that this is meant to display this shape.
/// * [SliderThemeData] where an instance of this class is set to inform the /// * [SliderThemeData] where an instance of this class is set to inform the
/// slider of the shape of the its thumb. /// slider of the visual details of the its track.
/// * [SliderTrackShape] Base component for creating other custom track
/// shapes.
class RectangularSliderTrackShape extends SliderTrackShape {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const RectangularSliderTrackShape();
@override
Rect getPreferredRect({
RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData sliderTheme,
bool isEnabled,
bool isDiscrete,
}) {
final double overlayWidth = sliderTheme.overlayShape.getPreferredSize(isEnabled, isDiscrete).width;
final double trackHeight = sliderTheme.trackHeight;
assert(overlayWidth >= 0);
assert(trackHeight >= 0);
assert(parentBox.size.width >= overlayWidth);
assert(parentBox.size.height >= trackHeight);
final double trackLeft = offset.dx + overlayWidth / 2;
final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
// TODO(clocksmith): Although this works for a material, perhaps the default
// rectangular track should be padded not just by the overlay, but by the
// max of the thumb and the overlay, in case there is no overlay.
final double trackWidth = parentBox.size.width - overlayWidth;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
// Spacing for disabled slider state.
static const double _thumbGap = 2.0;
@override
void paint(
PaintingContext context,
Offset offset, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
TextDirection textDirection,
Offset thumbCenter,
bool isDiscrete,
bool isEnabled,
}) {
// Assign the track segment paints, which are left: active, right: inactive,
// but reversed for right to left text.
final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor , end: sliderTheme.activeTrackColor);
final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor , end: sliderTheme.inactiveTrackColor);
final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation);
final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation);
Paint leftTrackPaint;
Paint rightTrackPaint;
switch (textDirection) {
case TextDirection.ltr:
leftTrackPaint = activePaint;
rightTrackPaint = inactivePaint;
break;
case TextDirection.rtl:
leftTrackPaint = inactivePaint;
rightTrackPaint = activePaint;
break;
}
// Used to create a gap around the thumb iff the slider is disabled.
double horizontalAdjustment = 0.0;
if (!isEnabled) {
final double thumbRadius = sliderTheme.thumbShape.getPreferredSize(isEnabled, isDiscrete).width / 2.0;
final double gap = _thumbGap * (1.0 - enableAnimation.value);
horizontalAdjustment = thumbRadius + gap;
}
final Rect trackRect = getPreferredRect(
parentBox: parentBox,
offset: offset,
sliderTheme: sliderTheme,
isEnabled: isEnabled,
isDiscrete: isDiscrete
);
final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, thumbCenter.dx - horizontalAdjustment, trackRect.bottom);
context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
final Rect rightTrackSegment = Rect.fromLTRB(thumbCenter.dx + horizontalAdjustment, trackRect.top, trackRect.right, trackRect.bottom);
context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
}
}
/// This is the default shape of each [Slider] tick mark.
///
/// Tick marks are only displayed if the slider is discrete, which can be done
/// by setting the [Slider.divisions] as non-null.
///
/// It paints a solid circle, centered in the on the track.
/// The color is determined by the [Slider]'s enabled state and track's active
/// states. These colors are defined in:
/// [SliderThemeData.activeTrackColor],
/// [SliderThemeData.inactiveTrackColor],
/// [SliderThemeData.disabledActiveTrackColor],
/// [SliderThemeData.disabledInactiveTrackColor].
///
/// See also:
///
/// * [Slider], which includes tick marks defined by this shape.
/// * [SliderTheme], which can be used to configure the tick mark shape of all
/// sliders in a widget subtree.
class RoundSliderTickMarkShape extends SliderTickMarkShape {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const RoundSliderTickMarkShape();
@override
Size getPreferredSize({
bool isEnabled,
SliderThemeData sliderTheme,
}) {
return Size.fromRadius(sliderTheme.trackHeight / 2);
}
@override
void paint(
PaintingContext context,
Offset center, {
RenderBox parentBox,
SliderThemeData sliderTheme,
Animation<double> enableAnimation,
TextDirection textDirection,
Offset thumbCenter,
bool isEnabled,
}) {
// The paint color of the tick mark depends on its position relative
// to the thumb and the text direction.
Color begin;
Color end;
switch (textDirection) {
case TextDirection.ltr:
final bool isTickMarkRightOfThumb = center.dx > thumbCenter.dx;
begin = isTickMarkRightOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
end = isTickMarkRightOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
break;
case TextDirection.rtl:
final bool isTickMarkLeftOfThumb = center.dx < thumbCenter.dx;
begin = isTickMarkLeftOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
end = isTickMarkLeftOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
break;
}
final Paint paint = Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation);
// The tick marks are tiny circles that are the same height as the track.
final double tickMarkRadius = sliderTheme.trackHeight / 2;
context.canvas.drawCircle(center, tickMarkRadius, paint);
}
}
/// This is the default shape of a [Slider]'s thumb.
///
/// See also:
///
/// * [Slider], which includes a thumb defined by this shape.
/// * [SliderTheme], which can be used to configure the thumb shape of all
/// sliders in a widget subtree.
class RoundSliderThumbShape extends SliderComponentShape { class RoundSliderThumbShape extends SliderComponentShape {
/// Create a slider thumb that draws a circle. /// Create a slider thumb that draws a circle.
const RoundSliderThumbShape(); const RoundSliderThumbShape();
...@@ -565,7 +1018,7 @@ class RoundSliderThumbShape extends SliderComponentShape { ...@@ -565,7 +1018,7 @@ class RoundSliderThumbShape extends SliderComponentShape {
@override @override
void paint( void paint(
PaintingContext context, PaintingContext context,
Offset thumbCenter, { Offset center, {
Animation<double> activationAnimation, Animation<double> activationAnimation,
Animation<double> enableAnimation, Animation<double> enableAnimation,
bool isDiscrete, bool isDiscrete,
...@@ -585,21 +1038,78 @@ class RoundSliderThumbShape extends SliderComponentShape { ...@@ -585,21 +1038,78 @@ class RoundSliderThumbShape extends SliderComponentShape {
end: sliderTheme.thumbColor, end: sliderTheme.thumbColor,
); );
canvas.drawCircle( canvas.drawCircle(
thumbCenter, center,
radiusTween.evaluate(enableAnimation), radiusTween.evaluate(enableAnimation),
Paint()..color = colorTween.evaluate(enableAnimation), Paint()..color = colorTween.evaluate(enableAnimation),
); );
} }
} }
/// This is the default shape to a [Slider]'s value indicator if no /// This is the default shape of a [Slider]'s thumb overlay.
/// other shape is specified. ///
/// The shape of the overlay is a circle with the same center as the thumb, but
/// with a larger radius. It animates to full size when the thumb is pressed,
/// and animates back down to size 0 when it is released. It is painted behind
/// the thumb, and is expected to extend beyond the bounds of the thumb so that
/// it is visible.
///
/// The overlay color is defined by [SliderThemeData.overlayColor].
/// ///
/// See also: /// See also:
/// ///
/// * [Slider] for the component that this is meant to display this shape. /// * [Slider], which includes an overlay defined by this shape.
/// * [SliderThemeData] where an instance of this class is set to inform the /// * [SliderTheme], which can be used to configure the overlay shape of all
/// slider of the shape of the its value indicator. /// sliders in a widget subtree.
class RoundSliderOverlayShape extends SliderComponentShape {
/// Create a slider thumb overlay that draws a circle.
const RoundSliderOverlayShape();
static const double _overlayRadius = 16.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return const Size.fromRadius(_overlayRadius);
}
@override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: 0.0,
end: _overlayRadius,
);
// TODO(gspencer): We don't really follow the spec here for overlays.
// The spec says to use 16% opacity for drawing over light material,
// and 32% for colored material, but we don't really have a way to
// know what the underlying color is, so there's no easy way to
// implement this. Choosing the "light" version for now.
canvas.drawCircle(
center,
radiusTween.evaluate(activationAnimation),
Paint()..color = sliderTheme.overlayColor,
);
}
}
/// This is the default shape of a [Slider]'s value indicator.
///
/// See also:
///
/// * [Slider], which includes a value indicator defined by this shape.
/// * [SliderTheme], which can be used to configure the slider value indicator
/// of all sliders in a widget subtree.
class PaddleSliderValueIndicatorShape extends SliderComponentShape { class PaddleSliderValueIndicatorShape extends SliderComponentShape {
/// Create a slider value indicator in the shape of an upside-down pear. /// Create a slider value indicator in the shape of an upside-down pear.
const PaddleSliderValueIndicatorShape(); const PaddleSliderValueIndicatorShape();
...@@ -881,7 +1391,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape { ...@@ -881,7 +1391,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
@override @override
void paint( void paint(
PaintingContext context, PaintingContext context,
Offset thumbCenter, { Offset center, {
Animation<double> activationAnimation, Animation<double> activationAnimation,
Animation<double> enableAnimation, Animation<double> enableAnimation,
bool isDiscrete, bool isDiscrete,
...@@ -898,7 +1408,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape { ...@@ -898,7 +1408,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
_drawValueIndicator( _drawValueIndicator(
parentBox, parentBox,
context.canvas, context.canvas,
thumbCenter, center,
Paint()..color = enableColor.evaluate(enableAnimation), Paint()..color = enableColor.evaluate(enableAnimation),
activationAnimation.value, activationAnimation.value,
labelPainter, labelPainter,
......
...@@ -30,7 +30,9 @@ class LoggingThumbShape extends SliderComponentShape { ...@@ -30,7 +30,9 @@ class LoggingThumbShape extends SliderComponentShape {
Offset thumbCenter, { Offset thumbCenter, {
Animation<double> activationAnimation, Animation<double> activationAnimation,
Animation<double> enableAnimation, Animation<double> enableAnimation,
bool isEnabled,
bool isDiscrete, bool isDiscrete,
bool onActiveTrack,
TextPainter labelPainter, TextPainter labelPainter,
RenderBox parentBox, RenderBox parentBox,
SliderThemeData sliderTheme, SliderThemeData sliderTheme,
...@@ -720,14 +722,15 @@ void main() { ...@@ -720,14 +722,15 @@ void main() {
expect( expect(
sliderBox, sliderBox,
paints paints
..rect(color: customColor1) ..rect(color: customColor1) // active track
..rect(color: customColor2) ..rect(color: customColor2) // inactive track
..circle(color: customColor1.withAlpha(0x29)) ..circle(color: customColor1.withAlpha(0x29)) // overlay
..circle(color: customColor2) ..circle(color: customColor2) // 1st tick mark
..circle(color: customColor2) ..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor1) ..circle(color: customColor2) // 3rd tick mark
..path(color: customColor1) ..circle(color: customColor1) // 4th tick mark
..circle(color: customColor1), ..path(color: customColor1) // indicator
..circle(color: customColor1), // thumb
); );
await gesture.up(); await gesture.up();
}); });
...@@ -1025,6 +1028,7 @@ void main() { ...@@ -1025,6 +1028,7 @@ void main() {
expect( expect(
sliderBox, sliderBox,
paints paints
..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, y: 16.0, radius: 1.0) ..circle(x: 208.5, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 1.0) ..circle(x: 400.0, y: 16.0, radius: 1.0)
..circle(x: 591.5, y: 16.0, radius: 1.0) ..circle(x: 591.5, y: 16.0, radius: 1.0)
...@@ -1077,6 +1081,7 @@ void main() { ...@@ -1077,6 +1081,7 @@ void main() {
..circle(x: 400.0, y: 16.0, radius: 16.0) ..circle(x: 400.0, y: 16.0, radius: 16.0)
..circle(x: 17.0, y: 16.0, radius: 1.0) ..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, y: 16.0, radius: 1.0) ..circle(x: 208.5, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 1.0)
..circle(x: 591.5, y: 16.0, radius: 1.0) ..circle(x: 591.5, y: 16.0, radius: 1.0)
..circle(x: 783.0, y: 16.0, radius: 1.0) ..circle(x: 783.0, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 6.0), ..circle(x: 400.0, y: 16.0, radius: 6.0),
...@@ -1089,6 +1094,7 @@ void main() { ...@@ -1089,6 +1094,7 @@ void main() {
paints paints
..circle(x: 17.0, y: 16.0, radius: 1.0) ..circle(x: 17.0, y: 16.0, radius: 1.0)
..circle(x: 208.5, y: 16.0, radius: 1.0) ..circle(x: 208.5, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 1.0)
..circle(x: 591.5, y: 16.0, radius: 1.0) ..circle(x: 591.5, y: 16.0, radius: 1.0)
..circle(x: 783.0, y: 16.0, radius: 1.0) ..circle(x: 783.0, y: 16.0, radius: 1.0)
..circle(x: 400.0, y: 16.0, radius: 6.0), ..circle(x: 400.0, y: 16.0, radius: 6.0),
......
...@@ -100,6 +100,20 @@ void main() { ...@@ -100,6 +100,20 @@ void main() {
expect(sliderBox, paints..rect(color: customTheme.disabledActiveTrackColor)..rect(color: customTheme.disabledInactiveTrackColor)); expect(sliderBox, paints..rect(color: customTheme.disabledActiveTrackColor)..rect(color: customTheme.disabledInactiveTrackColor));
}); });
testWidgets('SliderThemeData assigns the correct default shapes', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme;
expect(sliderTheme.trackShape, equals(isInstanceOf<RectangularSliderTrackShape>()));
expect(sliderTheme.tickMarkShape, equals(isInstanceOf<RoundSliderTickMarkShape>()));
expect(sliderTheme.thumbShape, equals(isInstanceOf<RoundSliderThumbShape>()));
expect(sliderTheme.valueIndicatorShape, equals(isInstanceOf<PaddleSliderValueIndicatorShape>()));
expect(sliderTheme.overlayShape, equals(isInstanceOf<RoundSliderOverlayShape>()));
});
testWidgets('SliderThemeData assigns the correct default flags', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme;
expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete));
});
testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async { testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async {
const Color customColor1 = Color(0xcafefeed); const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef); const Color customColor2 = Color(0xdeadbeef);
...@@ -125,9 +139,6 @@ void main() { ...@@ -125,9 +139,6 @@ void main() {
expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52))); expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29))); expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29)));
expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff))); expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
expect(sliderTheme.thumbShape, equals(isInstanceOf<RoundSliderThumbShape>()));
expect(sliderTheme.valueIndicatorShape, equals(isInstanceOf<PaddleSliderValueIndicatorShape>()));
expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete));
expect(sliderTheme.valueIndicatorTextStyle.color, equals(customColor4)); expect(sliderTheme.valueIndicatorTextStyle.color, equals(customColor4));
}); });
...@@ -137,15 +148,17 @@ void main() { ...@@ -137,15 +148,17 @@ void main() {
primaryColorDark: Colors.black, primaryColorDark: Colors.black,
primaryColorLight: Colors.black, primaryColorLight: Colors.black,
valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black), valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black),
); ).copyWith(trackHeight: 2.0);
final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors( final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
primaryColor: Colors.white, primaryColor: Colors.white,
primaryColorDark: Colors.white, primaryColorDark: Colors.white,
primaryColorLight: Colors.white, primaryColorLight: Colors.white,
valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white), valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white),
); ).copyWith(trackHeight: 6.0);
final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5); final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
const Color middleGrey = Color(0xff7f7f7f); const Color middleGrey = Color(0xff7f7f7f);
expect(lerp.trackHeight, equals(4.0));
expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff))); expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff)));
expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d))); expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d)));
expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52))); expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52)));
...@@ -161,7 +174,142 @@ void main() { ...@@ -161,7 +174,142 @@ void main() {
expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff))); expect(lerp.valueIndicatorTextStyle.color, equals(middleGrey.withAlpha(0xff)));
}); });
testWidgets('Default slider thumb shape draws correctly', (WidgetTester tester) async { testWidgets('Default slider 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);
double value = 0.25;
Widget buildApp({ bool enabled = true }) {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData.fromWindow(window),
child: Material(
child: Center(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: value,
label: '$value',
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
expect(
sliderBox,
paints
..rect(rect: Rect.fromLTRB(16.0, 299.0, 208.0, 301.0), color: sliderTheme.activeTrackColor)
..rect(rect: Rect.fromLTRB(208.0, 299.0, 784.0, 301.0), color: sliderTheme.inactiveTrackColor)
);
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
// The disabled thumb is smaller so the track has to paint longer to get
// to the edge.
expect(
sliderBox,
paints
..rect(rect: Rect.fromLTRB(16.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor)
..rect(rect: Rect.fromLTRB(214.0, 299.0, 784.0, 301.0), color: sliderTheme.disabledInactiveTrackColor)
);
});
testWidgets('Default slider overlay 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);
double value = 0.25;
Widget buildApp({ bool enabled = true }) {
final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData.fromWindow(window),
child: Material(
child: Center(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: value,
label: '$value',
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
// With no touch, paints only the thumb.
expect(
sliderBox,
paints
..circle(
color: sliderTheme.thumbColor,
x: 208.0,
y: 300.0,
radius: 6.0,
)
);
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for overlay animation to finish.
await tester.pumpAndSettle();
// After touch, paints thumb and overlay.
expect(
sliderBox,
paints
..circle(
color: sliderTheme.overlayColor,
x: 208.0,
y: 300.0,
radius: 16.0,
)
..circle(
color: sliderTheme.thumbColor,
x: 208.0,
y: 300.0,
radius: 6.0,
)
);
await gesture.up();
await tester.pumpAndSettle();
// After the gesture is up and complete, it again paints only the thumb.
expect(
sliderBox,
paints
..circle(
color: sliderTheme.thumbColor,
x: 208.0,
y: 300.0,
radius: 6.0,
)
);
});
testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
...@@ -213,7 +361,8 @@ void main() { ...@@ -213,7 +361,8 @@ void main() {
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor, radius: 6.0)); ..circle(color: sliderTheme.thumbColor, radius: 6.0)
);
await tester.pumpWidget(buildApp(divisions: 3, enabled: false)); await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
...@@ -223,7 +372,9 @@ void main() { ...@@ -223,7 +372,9 @@ void main() {
..circle(color: sliderTheme.disabledActiveTickMarkColor) ..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor) ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor) ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledThumbColor, radius: 4.0)); ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledThumbColor, radius: 4.0)
);
}); });
testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async { testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
...@@ -277,7 +428,8 @@ void main() { ...@@ -277,7 +428,8 @@ void main() {
const Offset(-15.9, -40.0), const Offset(-15.9, -40.0),
], ],
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)], excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
)); )
);
await gesture.up(); await gesture.up();
...@@ -298,7 +450,8 @@ void main() { ...@@ -298,7 +450,8 @@ void main() {
const Offset(-35.9, -40.0), const Offset(-35.9, -40.0),
], ],
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)], excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
)); )
);
await gesture.up(); await gesture.up();
// Test that it avoids the left edge of the screen. // Test that it avoids the left edge of the screen.
...@@ -318,7 +471,8 @@ void main() { ...@@ -318,7 +471,8 @@ void main() {
const Offset(-16.0, -40.0), const Offset(-16.0, -40.0),
], ],
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)], excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)],
)); )
);
await gesture.up(); await gesture.up();
// Test that it avoids the right edge of the screen. // Test that it avoids the right edge of the screen.
...@@ -338,7 +492,8 @@ void main() { ...@@ -338,7 +492,8 @@ void main() {
const Offset(-98.0, -40.0), const Offset(-98.0, -40.0),
], ],
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)], excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
)); )
);
await gesture.up(); await gesture.up();
// Test that the neck stretches when the text scale gets smaller. // Test that the neck stretches when the text scale gets smaller.
...@@ -363,7 +518,8 @@ void main() { ...@@ -363,7 +518,8 @@ void main() {
const Offset(90.1, -49.0), const Offset(90.1, -49.0),
const Offset(-24.1, -49.0), const Offset(-24.1, -49.0),
], ],
)); )
);
await gesture.up(); await gesture.up();
// Test that the neck shrinks when the text scale gets larger. // Test that the neck shrinks when the text scale gets larger.
...@@ -388,7 +544,8 @@ void main() { ...@@ -388,7 +544,8 @@ void main() {
const Offset(98.5, -38.8), const Offset(98.5, -38.8),
const Offset(-16.1, -38.8), const Offset(-16.1, -38.8),
], ],
)); )
);
await gesture.up(); await gesture.up();
}); });
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment