[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.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -584,15 +585,23 @@ class _RenderSlider extends RenderBox {
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
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 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;
|
||||
Animation<double> _overlayAnimation;
|
||||
@@ -604,7 +613,15 @@ class _RenderSlider extends RenderBox {
|
||||
bool _active = false;
|
||||
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;
|
||||
|
||||
@@ -818,7 +835,7 @@ class _RenderSlider extends RenderBox {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -874,7 +891,7 @@ class _RenderSlider extends RenderBox {
|
||||
|
||||
void _handleDragUpdate(DragUpdateDetails details) {
|
||||
if (isInteractive) {
|
||||
final double valueDelta = details.primaryDelta / _trackLength;
|
||||
final double valueDelta = details.primaryDelta / _trackRect.width;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
_currentDragValue -= valueDelta;
|
||||
@@ -907,25 +924,16 @@ class _RenderSlider extends RenderBox {
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
return math.max(
|
||||
_overlayDiameter,
|
||||
_sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width,
|
||||
);
|
||||
}
|
||||
double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
// This doesn't quite match the definition of computeMaxIntrinsicWidth,
|
||||
// but it seems within the spirit...
|
||||
return _preferredTotalWidth;
|
||||
}
|
||||
double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) => _overlayDiameter;
|
||||
double computeMinIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight);
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) => _overlayDiameter;
|
||||
double computeMaxIntrinsicHeight(double width) => max(_minPreferredTrackHeight, _maxSliderPartHeight);
|
||||
|
||||
@override
|
||||
bool get sizedByParent => true;
|
||||
@@ -933,126 +941,91 @@ class _RenderSlider extends RenderBox {
|
||||
@override
|
||||
void performResize() {
|
||||
size = Size(
|
||||
constraints.hasBoundedWidth ? constraints.maxWidth : _preferredTotalWidth,
|
||||
constraints.hasBoundedHeight ? constraints.maxHeight : _overlayDiameter,
|
||||
constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
|
||||
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
|
||||
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 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;
|
||||
Paint leftTrackPaint;
|
||||
Paint rightTrackPaint;
|
||||
Paint leftTickMarkPaint;
|
||||
Paint rightTickMarkPaint;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
visualPosition = 1.0 - value;
|
||||
leftTrackPaint = inactiveTrackPaint;
|
||||
rightTrackPaint = activeTrackPaint;
|
||||
leftTickMarkPaint = inactiveTickMarkPaint;
|
||||
rightTickMarkPaint = activeTickMarkPaint;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
visualPosition = value;
|
||||
leftTrackPaint = activeTrackPaint;
|
||||
rightTrackPaint = inactiveTrackPaint;
|
||||
leftTickMarkPaint = activeTickMarkPaint;
|
||||
rightTickMarkPaint = inactiveTickMarkPaint;
|
||||
break;
|
||||
}
|
||||
|
||||
const double trackRadius = _trackHeight / 2.0;
|
||||
const double thumbGap = 2.0;
|
||||
final Rect trackRect = _sliderTheme.trackShape.getPreferredRect(
|
||||
parentBox: this,
|
||||
offset: offset,
|
||||
sliderTheme: _sliderTheme,
|
||||
isDiscrete: isDiscrete
|
||||
);
|
||||
final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy);
|
||||
|
||||
final double trackVerticalCenter = offset.dy + (size.height) / 2.0;
|
||||
final double trackLeft = offset.dx + _overlayRadius;
|
||||
final double trackTop = trackVerticalCenter - trackRadius;
|
||||
final double trackBottom = trackVerticalCenter + trackRadius;
|
||||
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);
|
||||
|
||||
// Paint the track.
|
||||
if (visualPosition > 0.0) {
|
||||
canvas.drawRect(trackLeftRect, leftTrackPaint);
|
||||
}
|
||||
if (visualPosition < 1.0) {
|
||||
canvas.drawRect(trackRightRect, rightTrackPaint);
|
||||
}
|
||||
|
||||
_paintOverlay(canvas, thumbCenter);
|
||||
|
||||
_paintTickMarks(
|
||||
canvas,
|
||||
trackLeftRect,
|
||||
trackRightRect,
|
||||
leftTickMarkPaint,
|
||||
rightTickMarkPaint,
|
||||
_sliderTheme.trackShape.paint(
|
||||
context,
|
||||
offset,
|
||||
parentBox: this,
|
||||
sliderTheme: _sliderTheme,
|
||||
enableAnimation: _enableAnimation,
|
||||
textDirection: _textDirection,
|
||||
thumbCenter: thumbCenter,
|
||||
isDiscrete: isDiscrete,
|
||||
isEnabled: isInteractive
|
||||
);
|
||||
|
||||
if (isInteractive && label != null &&
|
||||
_valueIndicatorAnimation.status != AnimationStatus.dismissed) {
|
||||
// TODO(closkmith): Move this to paint after the thumb.
|
||||
if (!_overlayAnimation.isDismissed) {
|
||||
_sliderTheme.overlayShape.paint(
|
||||
context,
|
||||
thumbCenter,
|
||||
activationAnimation: _overlayAnimation,
|
||||
enableAnimation: _enableAnimation,
|
||||
isDiscrete: isDiscrete,
|
||||
labelPainter: _labelPainter,
|
||||
parentBox: this,
|
||||
sliderTheme: _sliderTheme,
|
||||
textDirection: _textDirection,
|
||||
value: _value,
|
||||
);
|
||||
}
|
||||
|
||||
if (isDiscrete) {
|
||||
// TODO(clocksmith): Align tick mark centers to ends of track by not subtracting diameter from length.
|
||||
final double tickMarkWidth = _sliderTheme.tickMarkShape.getPreferredSize(
|
||||
isEnabled: isInteractive,
|
||||
sliderTheme: _sliderTheme,
|
||||
).width;
|
||||
for (int i = 0; i <= divisions; i++) {
|
||||
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 && !_valueIndicatorAnimation.isDismissed) {
|
||||
if (showValueIndicator) {
|
||||
_sliderTheme.valueIndicatorShape.paint(
|
||||
context,
|
||||
@@ -1117,4 +1090,4 @@ class _RenderSlider extends RenderBox {
|
||||
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show Path;
|
||||
import 'dart:ui' show Path, lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -21,12 +21,21 @@ import 'theme_data.dart';
|
||||
/// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically
|
||||
/// 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:
|
||||
///
|
||||
/// * [SliderThemeData], which describes the actual configuration of a slider
|
||||
/// theme.
|
||||
/// * [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 {
|
||||
/// Applies the given theme [data] to [child].
|
||||
///
|
||||
@@ -123,8 +132,14 @@ enum ShowValueIndicator {
|
||||
/// * The "thumb", which is a shape that slides horizontally when the user
|
||||
/// drags it.
|
||||
/// * 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
|
||||
/// is dragging the thumb to indicate the value being selected.
|
||||
/// * The "tick marks", which are regularly spaced marks that are drawn when
|
||||
/// 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
|
||||
/// minimum value.
|
||||
/// * The "inactive" side of the slider is the side between the thumb and the
|
||||
@@ -132,10 +147,12 @@ enum ShowValueIndicator {
|
||||
/// * The [Slider] is disabled when it is not accepting user input. See
|
||||
/// [Slider] for details on when this happens.
|
||||
///
|
||||
/// The thumb and the value indicator may have their shapes and behavior
|
||||
/// customized by creating your own [SliderComponentShape] that does what
|
||||
/// you want. See [RoundSliderThumbShape] and
|
||||
/// [PaddleSliderValueIndicatorShape] for examples.
|
||||
/// The thumb, track, tick marks, value indicator, and overlay can be customized
|
||||
/// by creating subclasses of [SliderTrackShape],
|
||||
/// [SliderComponentShape], and/or [SliderTickMarkShape]. See
|
||||
/// [RoundSliderThumbShape], [RectangularSliderTrackShape],
|
||||
/// [RoundSliderTickMarkShape], [PaddleSliderValueIndicatorShape], and
|
||||
/// [RoundSliderOverlayShape] for examples.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@@ -144,7 +161,9 @@ enum ShowValueIndicator {
|
||||
/// * [Theme] widget, which performs a similar function to [SliderTheme],
|
||||
/// but for overall themes.
|
||||
/// * [ThemeData], which has a default [SliderThemeData].
|
||||
/// * [SliderTrackShape], to define custom slider track shapes.
|
||||
/// * [SliderComponentShape], to define custom slider component shapes.
|
||||
/// * [SliderTickMarkShape], to define custom slider tick mark shapes.
|
||||
class SliderThemeData extends Diagnosticable {
|
||||
/// Create a [SliderThemeData] given a set of exact values. All the values
|
||||
/// must be specified.
|
||||
@@ -181,6 +200,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
const SliderThemeData({
|
||||
@required this.trackHeight,
|
||||
@required this.activeTrackColor,
|
||||
@required this.inactiveTrackColor,
|
||||
@required this.disabledActiveTrackColor,
|
||||
@@ -193,26 +213,33 @@ class SliderThemeData extends Diagnosticable {
|
||||
@required this.disabledThumbColor,
|
||||
@required this.overlayColor,
|
||||
@required this.valueIndicatorColor,
|
||||
@required this.trackShape,
|
||||
@required this.tickMarkShape,
|
||||
@required this.thumbShape,
|
||||
@required this.overlayShape,
|
||||
@required this.valueIndicatorShape,
|
||||
@required this.showValueIndicator,
|
||||
@required this.valueIndicatorTextStyle,
|
||||
}) : assert(activeTrackColor != null),
|
||||
assert(inactiveTrackColor != null),
|
||||
assert(disabledActiveTrackColor != null),
|
||||
assert(disabledInactiveTrackColor != null),
|
||||
assert(activeTickMarkColor != null),
|
||||
assert(inactiveTickMarkColor != null),
|
||||
assert(disabledActiveTickMarkColor != null),
|
||||
assert(disabledInactiveTickMarkColor != null),
|
||||
assert(thumbColor != null),
|
||||
assert(disabledThumbColor != null),
|
||||
assert(overlayColor != null),
|
||||
assert(valueIndicatorColor != null),
|
||||
assert(thumbShape != null),
|
||||
assert(valueIndicatorShape != null),
|
||||
assert(valueIndicatorTextStyle != null),
|
||||
assert(showValueIndicator != null);
|
||||
}) : assert(trackHeight != null),
|
||||
assert(activeTrackColor != null),
|
||||
assert(inactiveTrackColor != null),
|
||||
assert(disabledActiveTrackColor != null),
|
||||
assert(disabledInactiveTrackColor != null),
|
||||
assert(activeTickMarkColor != null),
|
||||
assert(inactiveTickMarkColor != null),
|
||||
assert(disabledActiveTickMarkColor != null),
|
||||
assert(disabledInactiveTickMarkColor != null),
|
||||
assert(thumbColor != null),
|
||||
assert(disabledThumbColor != null),
|
||||
assert(overlayColor != null),
|
||||
assert(valueIndicatorColor != null),
|
||||
assert(trackShape != null),
|
||||
assert(tickMarkShape != null),
|
||||
assert(thumbShape != null),
|
||||
assert(overlayShape != null),
|
||||
assert(valueIndicatorShape != null),
|
||||
assert(valueIndicatorTextStyle != null),
|
||||
assert(showValueIndicator != null);
|
||||
|
||||
/// Generates a SliderThemeData from three main colors.
|
||||
///
|
||||
@@ -256,6 +283,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
const int overlayLightAlpha = 0x29; // 16% opacity
|
||||
|
||||
return SliderThemeData(
|
||||
trackHeight: 2.0,
|
||||
activeTrackColor: primaryColor.withAlpha(activeTrackAlpha),
|
||||
inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha),
|
||||
disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha),
|
||||
@@ -268,13 +296,19 @@ class SliderThemeData extends Diagnosticable {
|
||||
disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha),
|
||||
overlayColor: primaryColor.withAlpha(overlayLightAlpha),
|
||||
valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha),
|
||||
trackShape: const RectangularSliderTrackShape(),
|
||||
tickMarkShape: const RoundSliderTickMarkShape(),
|
||||
thumbShape: const RoundSliderThumbShape(),
|
||||
overlayShape: const RoundSliderOverlayShape(),
|
||||
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
|
||||
valueIndicatorTextStyle: valueIndicatorTextStyle,
|
||||
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
|
||||
/// current thumb position.
|
||||
final Color activeTrackColor;
|
||||
@@ -323,17 +357,39 @@ class SliderThemeData extends Diagnosticable {
|
||||
/// The color given to the [valueIndicatorShape] to draw itself with.
|
||||
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
|
||||
/// [SliderComponentShape].
|
||||
/// The [SliderTrackShape.getPreferredRect] method is used to to map
|
||||
/// 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;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This can be customized by implementing a subclass of
|
||||
/// [SliderComponentShape].
|
||||
final SliderComponentShape valueIndicatorShape;
|
||||
|
||||
/// Whether the value indicator should be shown for different types of
|
||||
@@ -352,6 +408,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
SliderThemeData copyWith({
|
||||
double trackHeight,
|
||||
Color activeTrackColor,
|
||||
Color inactiveTrackColor,
|
||||
Color disabledActiveTrackColor,
|
||||
@@ -364,12 +421,16 @@ class SliderThemeData extends Diagnosticable {
|
||||
Color disabledThumbColor,
|
||||
Color overlayColor,
|
||||
Color valueIndicatorColor,
|
||||
SliderTrackShape trackShape,
|
||||
SliderTickMarkShape tickMarkShape,
|
||||
SliderComponentShape thumbShape,
|
||||
SliderComponentShape overlayShape,
|
||||
SliderComponentShape valueIndicatorShape,
|
||||
ShowValueIndicator showValueIndicator,
|
||||
TextStyle valueIndicatorTextStyle,
|
||||
}) {
|
||||
return SliderThemeData(
|
||||
trackHeight: trackHeight ?? this.trackHeight,
|
||||
activeTrackColor: activeTrackColor ?? this.activeTrackColor,
|
||||
inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor,
|
||||
disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor,
|
||||
@@ -382,7 +443,10 @@ class SliderThemeData extends Diagnosticable {
|
||||
disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
|
||||
overlayColor: overlayColor ?? this.overlayColor,
|
||||
valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor,
|
||||
trackShape: trackShape ?? this.trackShape,
|
||||
tickMarkShape: tickMarkShape ?? this.tickMarkShape,
|
||||
thumbShape: thumbShape ?? this.thumbShape,
|
||||
overlayShape: overlayShape ?? this.overlayShape,
|
||||
valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape,
|
||||
showValueIndicator: showValueIndicator ?? this.showValueIndicator,
|
||||
valueIndicatorTextStyle: valueIndicatorTextStyle ?? this.valueIndicatorTextStyle,
|
||||
@@ -399,6 +463,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
assert(b != null);
|
||||
assert(t != null);
|
||||
return SliderThemeData(
|
||||
trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t),
|
||||
activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t),
|
||||
inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t),
|
||||
disabledActiveTrackColor: Color.lerp(a.disabledActiveTrackColor, b.disabledActiveTrackColor, t),
|
||||
@@ -411,7 +476,10 @@ class SliderThemeData extends Diagnosticable {
|
||||
disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
|
||||
overlayColor: Color.lerp(a.overlayColor, b.overlayColor, 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,
|
||||
overlayShape: t < 0.5 ? a.overlayShape : b.overlayShape,
|
||||
valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
|
||||
showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
|
||||
valueIndicatorTextStyle: TextStyle.lerp(a.valueIndicatorTextStyle, b.valueIndicatorTextStyle, t),
|
||||
@@ -421,6 +489,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
trackHeight,
|
||||
activeTrackColor,
|
||||
inactiveTrackColor,
|
||||
disabledActiveTrackColor,
|
||||
@@ -433,7 +502,10 @@ class SliderThemeData extends Diagnosticable {
|
||||
disabledThumbColor,
|
||||
overlayColor,
|
||||
valueIndicatorColor,
|
||||
trackShape,
|
||||
tickMarkShape,
|
||||
thumbShape,
|
||||
overlayShape,
|
||||
valueIndicatorShape,
|
||||
showValueIndicator,
|
||||
valueIndicatorTextStyle,
|
||||
@@ -449,22 +521,26 @@ class SliderThemeData extends Diagnosticable {
|
||||
return false;
|
||||
}
|
||||
final SliderThemeData otherData = other;
|
||||
return otherData.activeTrackColor == activeTrackColor &&
|
||||
otherData.inactiveTrackColor == inactiveTrackColor &&
|
||||
otherData.disabledActiveTrackColor == disabledActiveTrackColor &&
|
||||
otherData.disabledInactiveTrackColor == disabledInactiveTrackColor &&
|
||||
otherData.activeTickMarkColor == activeTickMarkColor &&
|
||||
otherData.inactiveTickMarkColor == inactiveTickMarkColor &&
|
||||
otherData.disabledActiveTickMarkColor == disabledActiveTickMarkColor &&
|
||||
otherData.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor &&
|
||||
otherData.thumbColor == thumbColor &&
|
||||
otherData.disabledThumbColor == disabledThumbColor &&
|
||||
otherData.overlayColor == overlayColor &&
|
||||
otherData.valueIndicatorColor == valueIndicatorColor &&
|
||||
otherData.thumbShape == thumbShape &&
|
||||
otherData.valueIndicatorShape == valueIndicatorShape &&
|
||||
otherData.showValueIndicator == showValueIndicator &&
|
||||
otherData.valueIndicatorTextStyle == valueIndicatorTextStyle;
|
||||
return otherData.trackHeight == trackHeight
|
||||
&& otherData.activeTrackColor == activeTrackColor
|
||||
&& otherData.inactiveTrackColor == inactiveTrackColor
|
||||
&& otherData.disabledActiveTrackColor == disabledActiveTrackColor
|
||||
&& otherData.disabledInactiveTrackColor == disabledInactiveTrackColor
|
||||
&& otherData.activeTickMarkColor == activeTickMarkColor
|
||||
&& otherData.inactiveTickMarkColor == inactiveTickMarkColor
|
||||
&& otherData.disabledActiveTickMarkColor == disabledActiveTickMarkColor
|
||||
&& otherData.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor
|
||||
&& otherData.thumbColor == thumbColor
|
||||
&& otherData.disabledThumbColor == disabledThumbColor
|
||||
&& otherData.overlayColor == overlayColor
|
||||
&& otherData.valueIndicatorColor == valueIndicatorColor
|
||||
&& otherData.trackShape == trackShape
|
||||
&& otherData.tickMarkShape == tickMarkShape
|
||||
&& otherData.thumbShape == thumbShape
|
||||
&& otherData.overlayShape == overlayShape
|
||||
&& otherData.valueIndicatorShape == valueIndicatorShape
|
||||
&& otherData.showValueIndicator == showValueIndicator
|
||||
&& otherData.valueIndicatorTextStyle == valueIndicatorTextStyle;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -478,6 +554,7 @@ class SliderThemeData extends Diagnosticable {
|
||||
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>('inactiveTrackColor', inactiveTrackColor, defaultValue: defaultData.inactiveTrackColor));
|
||||
properties.add(DiagnosticsProperty<Color>('disabledActiveTrackColor', disabledActiveTrackColor, defaultValue: defaultData.disabledActiveTrackColor, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<Color>('disabledInactiveTrackColor', disabledInactiveTrackColor, defaultValue: defaultData.disabledInactiveTrackColor, level: DiagnosticLevel.debug));
|
||||
@@ -489,22 +566,213 @@ class SliderThemeData extends Diagnosticable {
|
||||
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>('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>('overlayShape', overlayShape, defaultValue: defaultData.overlayShape, 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(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
|
||||
/// value indicator shape.
|
||||
/// {@template flutter.material.slider.shape.center}
|
||||
/// [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:
|
||||
///
|
||||
/// * [RoundSliderThumbShape] for a simple example of a thumb shape.
|
||||
/// * [PaddleSliderValueIndicatorShape], for a complex example of a value
|
||||
/// * [RectangularSliderTrackShape], which is the the default track shape.
|
||||
/// * [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.
|
||||
abstract class SliderComponentShape {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
@@ -516,21 +784,34 @@ abstract class SliderComponentShape {
|
||||
|
||||
/// 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
|
||||
/// to interact with the slider. It reverses when the user stops interacting
|
||||
/// with the slider.
|
||||
///
|
||||
/// [enableAnimation] is an animation triggered when the [Slider] is enabled,
|
||||
/// and it reverses when the slider is disabled.
|
||||
/// {@macro flutter.material.slider.shape.enableAnimation}
|
||||
///
|
||||
/// [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
|
||||
/// called with the location that the label should appear. If the labelPainter
|
||||
/// 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(
|
||||
PaintingContext context,
|
||||
Offset thumbCenter, {
|
||||
Offset center, {
|
||||
Animation<double> activationAnimation,
|
||||
Animation<double> enableAnimation,
|
||||
bool isDiscrete,
|
||||
@@ -542,14 +823,186 @@ abstract class SliderComponentShape {
|
||||
});
|
||||
}
|
||||
|
||||
/// This is the default shape to a [Slider]'s thumb if no
|
||||
/// other shape is specified.
|
||||
// The following shapes are the material defaults.
|
||||
|
||||
/// 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:
|
||||
///
|
||||
/// * [Slider] for the component that this is meant to display this shape.
|
||||
/// * [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 {
|
||||
/// Create a slider thumb that draws a circle.
|
||||
const RoundSliderThumbShape();
|
||||
@@ -565,7 +1018,7 @@ class RoundSliderThumbShape extends SliderComponentShape {
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset thumbCenter, {
|
||||
Offset center, {
|
||||
Animation<double> activationAnimation,
|
||||
Animation<double> enableAnimation,
|
||||
bool isDiscrete,
|
||||
@@ -585,21 +1038,78 @@ class RoundSliderThumbShape extends SliderComponentShape {
|
||||
end: sliderTheme.thumbColor,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
thumbCenter,
|
||||
center,
|
||||
radiusTween.evaluate(enableAnimation),
|
||||
Paint()..color = colorTween.evaluate(enableAnimation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the default shape to a [Slider]'s value indicator if no
|
||||
/// other shape is specified.
|
||||
/// This is the default shape of a [Slider]'s thumb overlay.
|
||||
///
|
||||
/// 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:
|
||||
///
|
||||
/// * [Slider] for the component that this is meant to display this shape.
|
||||
/// * [SliderThemeData] where an instance of this class is set to inform the
|
||||
/// slider of the shape of the its value indicator.
|
||||
/// * [Slider], which includes an overlay defined by this shape.
|
||||
/// * [SliderTheme], which can be used to configure the overlay shape of all
|
||||
/// 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 {
|
||||
/// Create a slider value indicator in the shape of an upside-down pear.
|
||||
const PaddleSliderValueIndicatorShape();
|
||||
@@ -881,7 +1391,7 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset thumbCenter, {
|
||||
Offset center, {
|
||||
Animation<double> activationAnimation,
|
||||
Animation<double> enableAnimation,
|
||||
bool isDiscrete,
|
||||
@@ -898,10 +1408,10 @@ class PaddleSliderValueIndicatorShape extends SliderComponentShape {
|
||||
_drawValueIndicator(
|
||||
parentBox,
|
||||
context.canvas,
|
||||
thumbCenter,
|
||||
center,
|
||||
Paint()..color = enableColor.evaluate(enableAnimation),
|
||||
activationAnimation.value,
|
||||
labelPainter,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,19 @@ class LoggingThumbShape extends SliderComponentShape {
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset thumbCenter, {
|
||||
Animation<double> activationAnimation,
|
||||
Animation<double> enableAnimation,
|
||||
bool isDiscrete,
|
||||
TextPainter labelPainter,
|
||||
RenderBox parentBox,
|
||||
SliderThemeData sliderTheme,
|
||||
TextDirection textDirection,
|
||||
double value,
|
||||
}) {
|
||||
PaintingContext context,
|
||||
Offset thumbCenter, {
|
||||
Animation<double> activationAnimation,
|
||||
Animation<double> enableAnimation,
|
||||
bool isEnabled,
|
||||
bool isDiscrete,
|
||||
bool onActiveTrack,
|
||||
TextPainter labelPainter,
|
||||
RenderBox parentBox,
|
||||
SliderThemeData sliderTheme,
|
||||
TextDirection textDirection,
|
||||
double value,
|
||||
}) {
|
||||
log.add(thumbCenter);
|
||||
final Paint thumbPaint = Paint()..color = Colors.red;
|
||||
context.canvas.drawCircle(thumbCenter, 5.0, thumbPaint);
|
||||
@@ -720,14 +722,15 @@ void main() {
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..rect(color: customColor1)
|
||||
..rect(color: customColor2)
|
||||
..circle(color: customColor1.withAlpha(0x29))
|
||||
..circle(color: customColor2)
|
||||
..circle(color: customColor2)
|
||||
..circle(color: customColor1)
|
||||
..path(color: customColor1)
|
||||
..circle(color: customColor1),
|
||||
..rect(color: customColor1) // active track
|
||||
..rect(color: customColor2) // inactive track
|
||||
..circle(color: customColor1.withAlpha(0x29)) // overlay
|
||||
..circle(color: customColor2) // 1st tick mark
|
||||
..circle(color: customColor2) // 2nd tick mark
|
||||
..circle(color: customColor2) // 3rd tick mark
|
||||
..circle(color: customColor1) // 4th tick mark
|
||||
..path(color: customColor1) // indicator
|
||||
..circle(color: customColor1), // thumb
|
||||
);
|
||||
await gesture.up();
|
||||
});
|
||||
@@ -896,8 +899,8 @@ void main() {
|
||||
child: Material(
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
sliderTheme: Theme.of(context).sliderTheme.copyWith(showValueIndicator: show),
|
||||
),
|
||||
sliderTheme: Theme.of(context).sliderTheme.copyWith(showValueIndicator: show),
|
||||
),
|
||||
child: Center(
|
||||
child: OverflowBox(
|
||||
maxWidth: double.infinity,
|
||||
@@ -1025,6 +1028,7 @@ void main() {
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..circle(x: 17.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 208.5, y: 16.0, radius: 1.0)
|
||||
..circle(x: 400.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 591.5, y: 16.0, radius: 1.0)
|
||||
@@ -1077,6 +1081,7 @@ void main() {
|
||||
..circle(x: 400.0, y: 16.0, radius: 16.0)
|
||||
..circle(x: 17.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 208.5, y: 16.0, radius: 1.0)
|
||||
..circle(x: 400.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 591.5, y: 16.0, radius: 1.0)
|
||||
..circle(x: 783.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 400.0, y: 16.0, radius: 6.0),
|
||||
@@ -1089,6 +1094,7 @@ void main() {
|
||||
paints
|
||||
..circle(x: 17.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 208.5, y: 16.0, radius: 1.0)
|
||||
..circle(x: 400.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 591.5, y: 16.0, radius: 1.0)
|
||||
..circle(x: 783.0, y: 16.0, radius: 1.0)
|
||||
..circle(x: 400.0, y: 16.0, radius: 6.0),
|
||||
@@ -1367,4 +1373,4 @@ void main() {
|
||||
expect(await tester.pumpAndSettle(const Duration(milliseconds: 100)), equals(1));
|
||||
await gesture.up();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,20 @@ void main() {
|
||||
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 {
|
||||
const Color customColor1 = Color(0xcafefeed);
|
||||
const Color customColor2 = Color(0xdeadbeef);
|
||||
@@ -125,9 +139,6 @@ void main() {
|
||||
expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
|
||||
expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29)));
|
||||
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));
|
||||
});
|
||||
|
||||
@@ -137,15 +148,17 @@ void main() {
|
||||
primaryColorDark: Colors.black,
|
||||
primaryColorLight: Colors.black,
|
||||
valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black),
|
||||
);
|
||||
).copyWith(trackHeight: 2.0);
|
||||
final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
|
||||
primaryColor: Colors.white,
|
||||
primaryColorDark: Colors.white,
|
||||
primaryColorLight: Colors.white,
|
||||
valueIndicatorTextStyle: ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white),
|
||||
);
|
||||
).copyWith(trackHeight: 6.0);
|
||||
final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
|
||||
const Color middleGrey = Color(0xff7f7f7f);
|
||||
|
||||
expect(lerp.trackHeight, equals(4.0));
|
||||
expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff)));
|
||||
expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d)));
|
||||
expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52)));
|
||||
@@ -161,7 +174,142 @@ void main() {
|
||||
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(
|
||||
platform: TargetPlatform.android,
|
||||
primarySwatch: Colors.blue,
|
||||
@@ -207,23 +355,26 @@ void main() {
|
||||
await tester.pumpWidget(buildApp(divisions: 3));
|
||||
await tester.pumpAndSettle(); // wait for disable animation
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..circle(color: sliderTheme.activeTickMarkColor)
|
||||
..circle(color: sliderTheme.activeTickMarkColor)
|
||||
..circle(color: sliderTheme.inactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.inactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.thumbColor, radius: 6.0));
|
||||
sliderBox,
|
||||
paints
|
||||
..circle(color: sliderTheme.activeTickMarkColor)
|
||||
..circle(color: sliderTheme.activeTickMarkColor)
|
||||
..circle(color: sliderTheme.inactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.inactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.thumbColor, radius: 6.0)
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
|
||||
await tester.pumpAndSettle(); // wait for disable animation
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..circle(color: sliderTheme.disabledActiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledThumbColor, radius: 4.0));
|
||||
sliderBox,
|
||||
paints
|
||||
..circle(color: sliderTheme.disabledActiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
||||
..circle(color: sliderTheme.disabledThumbColor, radius: 4.0)
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
|
||||
@@ -267,17 +418,18 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
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)],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
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)],
|
||||
)
|
||||
);
|
||||
|
||||
await gesture.up();
|
||||
|
||||
@@ -288,17 +440,18 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
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)],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
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)],
|
||||
)
|
||||
);
|
||||
await gesture.up();
|
||||
|
||||
// Test that it avoids the left edge of the screen.
|
||||
@@ -308,17 +461,18 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
const Offset(98.0, -40.0),
|
||||
const Offset(-16.0, -40.0),
|
||||
],
|
||||
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
const Offset(98.0, -40.0),
|
||||
const Offset(-16.0, -40.0),
|
||||
],
|
||||
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-16.1, -40.0)],
|
||||
)
|
||||
);
|
||||
await gesture.up();
|
||||
|
||||
// Test that it avoids the right edge of the screen.
|
||||
@@ -328,17 +482,18 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
const Offset(16.0, -40.0),
|
||||
const Offset(-98.0, -40.0),
|
||||
],
|
||||
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -40.0),
|
||||
const Offset(16.0, -40.0),
|
||||
const Offset(-98.0, -40.0),
|
||||
],
|
||||
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-98.1, -40.0)],
|
||||
)
|
||||
);
|
||||
await gesture.up();
|
||||
|
||||
// Test that the neck stretches when the text scale gets smaller.
|
||||
@@ -348,22 +503,23 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -49.0),
|
||||
const Offset(90.0, -49.0),
|
||||
const Offset(-24.0, -49.0),
|
||||
],
|
||||
excludes: <Offset>[
|
||||
const Offset(98.0, -32.0), // inside full size, outside small
|
||||
const Offset(-16.0, -32.0), // inside full size, outside small
|
||||
const Offset(90.1, -49.0),
|
||||
const Offset(-24.1, -49.0),
|
||||
],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -49.0),
|
||||
const Offset(90.0, -49.0),
|
||||
const Offset(-24.0, -49.0),
|
||||
],
|
||||
excludes: <Offset>[
|
||||
const Offset(98.0, -32.0), // inside full size, outside small
|
||||
const Offset(-16.0, -32.0), // inside full size, outside small
|
||||
const Offset(90.1, -49.0),
|
||||
const Offset(-24.1, -49.0),
|
||||
],
|
||||
)
|
||||
);
|
||||
await gesture.up();
|
||||
|
||||
// Test that the neck shrinks when the text scale gets larger.
|
||||
@@ -373,22 +529,23 @@ void main() {
|
||||
// Wait for value indicator animation to finish.
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -38.8),
|
||||
const Offset(98.0, -38.8),
|
||||
const Offset(-16.0, -38.8),
|
||||
const Offset(10.0, -23.0), // Inside large, outside scale=1.0
|
||||
const Offset(-4.0, -23.0), // Inside large, outside scale=1.0
|
||||
],
|
||||
excludes: <Offset>[
|
||||
const Offset(98.5, -38.8),
|
||||
const Offset(-16.1, -38.8),
|
||||
],
|
||||
));
|
||||
sliderBox,
|
||||
paints
|
||||
..path(
|
||||
color: sliderTheme.valueIndicatorColor,
|
||||
includes: <Offset>[
|
||||
const Offset(0.0, -38.8),
|
||||
const Offset(98.0, -38.8),
|
||||
const Offset(-16.0, -38.8),
|
||||
const Offset(10.0, -23.0), // Inside large, outside scale=1.0
|
||||
const Offset(-4.0, -23.0), // Inside large, outside scale=1.0
|
||||
],
|
||||
excludes: <Offset>[
|
||||
const Offset(98.5, -38.8),
|
||||
const Offset(-16.1, -38.8),
|
||||
],
|
||||
)
|
||||
);
|
||||
await gesture.up();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user