diff --git a/examples/api/lib/material/slider/slider.1.dart b/examples/api/lib/material/slider/slider.1.dart new file mode 100644 index 00000000000..2e0af68521e --- /dev/null +++ b/examples/api/lib/material/slider/slider.1.dart @@ -0,0 +1,66 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for Slider + +import 'package:flutter/material.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + static const String _title = 'Flutter Code Sample'; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: _title, + home: Scaffold( + appBar: AppBar(title: const Text(_title)), + body: const MyStatefulWidget(), + ), + ); + } +} + +class MyStatefulWidget extends StatefulWidget { + const MyStatefulWidget({super.key}); + + @override + State createState() => _MyStatefulWidgetState(); +} + +class _MyStatefulWidgetState extends State { + double _currentSliderPrimaryValue = 0.2; + double _currentSliderSecondaryValue = 0.5; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Slider( + value: _currentSliderPrimaryValue, + secondaryTrackValue: _currentSliderSecondaryValue, + label: _currentSliderPrimaryValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentSliderPrimaryValue = value; + }); + }, + ), + Slider( + value: _currentSliderSecondaryValue, + label: _currentSliderSecondaryValue.round().toString(), + onChanged: (double value) { + setState(() { + _currentSliderSecondaryValue = value; + }); + }, + ), + ], + ); + } +} diff --git a/examples/api/test/material/slider/slider.1_test.dart b/examples/api/test/material/slider/slider.1_test.dart new file mode 100644 index 00000000000..df884ccbf23 --- /dev/null +++ b/examples/api/test/material/slider/slider.1_test.dart @@ -0,0 +1,35 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/slider/slider.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Slider shows secondary track', (WidgetTester tester) async { + await tester.pumpWidget( + const example.MyApp(), + ); + + expect(find.byType(Slider), findsNWidgets(2)); + + final Finder slider1Finder = find.byType(Slider).at(0); + final Finder slider2Finder = find.byType(Slider).at(1); + + Slider slider1 = tester.widget(slider1Finder); + Slider slider2 = tester.widget(slider2Finder); + expect(slider1.secondaryTrackValue, slider2.value); + + const double targetValue = 0.8; + final Rect rect = tester.getRect(slider2Finder); + final Offset target = Offset(rect.left + (rect.right - rect.left) * targetValue, rect.top + (rect.bottom - rect.top) / 2); + await tester.tapAt(target); + await tester.pump(); + + slider1 = tester.widget(slider1Finder); + slider2 = tester.widget(slider2Finder); + expect(slider1.secondaryTrackValue, closeTo(targetValue, 0.05)); + expect(slider1.secondaryTrackValue, slider2.value); + }); +} diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 3301c8405bc..23cc03ad11d 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -48,6 +48,13 @@ enum _SliderType { material, adaptive } /// ** See code in examples/api/lib/material/slider/slider.0.dart ** /// {@end-tool} /// +/// {@tool dartpad} +/// This example shows a [Slider] widget using the [Slider.secondaryTrackValue] +/// to show a secondary track in the slider. +/// +/// ** See code in examples/api/lib/material/slider/slider.1.dart ** +/// {@end-tool} +/// /// A slider can be used to select from either a continuous or a discrete set of /// values. The default is to use a continuous range of values from [min] to /// [max]. To use discrete values, use a non-null value for [divisions], which @@ -125,6 +132,7 @@ class Slider extends StatefulWidget { const Slider({ super.key, required this.value, + this.secondaryTrackValue, required this.onChanged, this.onChangeStart, this.onChangeEnd, @@ -134,6 +142,7 @@ class Slider extends StatefulWidget { this.label, this.activeColor, this.inactiveColor, + this.secondaryActiveColor, this.thumbColor, this.mouseCursor, this.semanticFormatterCallback, @@ -146,6 +155,8 @@ class Slider extends StatefulWidget { assert(min <= max), assert(value >= min && value <= max, 'Value $value is not between minimum $min and maximum $max'), + assert(secondaryTrackValue == null || (secondaryTrackValue >= min && secondaryTrackValue <= max), + 'SecondaryValue $secondaryTrackValue is not between $min and $max'), assert(divisions == null || divisions > 0); /// Creates an adaptive [Slider] based on the target platform, following @@ -155,13 +166,15 @@ class Slider extends StatefulWidget { /// Creates a [CupertinoSlider] if the target platform is iOS or macOS, creates a /// Material Design slider otherwise. /// - /// If a [CupertinoSlider] is created, the following parameters are - /// ignored: [label], [inactiveColor], [semanticFormatterCallback]. + /// If a [CupertinoSlider] is created, the following parameters are ignored: + /// [secondaryTrackValue], [label], [inactiveColor], [secondaryActiveColor], + /// [semanticFormatterCallback]. /// /// The target platform is based on the current [Theme]: [ThemeData.platform]. const Slider.adaptive({ super.key, required this.value, + this.secondaryTrackValue, required this.onChanged, this.onChangeStart, this.onChangeEnd, @@ -172,6 +185,7 @@ class Slider extends StatefulWidget { this.mouseCursor, this.activeColor, this.inactiveColor, + this.secondaryActiveColor, this.thumbColor, this.semanticFormatterCallback, this.focusNode, @@ -181,7 +195,10 @@ class Slider extends StatefulWidget { assert(min != null), assert(max != null), assert(min <= max), - assert(value >= min && value <= max), + assert(value >= min && value <= max, + 'Value $value is not between minimum $min and maximum $max'), + assert(secondaryTrackValue == null || (secondaryTrackValue >= min && secondaryTrackValue <= max), + 'SecondaryValue $secondaryTrackValue is not between $min and $max'), assert(divisions == null || divisions > 0); /// The currently selected value for this slider. @@ -189,6 +206,17 @@ class Slider extends StatefulWidget { /// The slider's thumb is drawn at a position that corresponds to this value. final double value; + /// The secondary track value for this slider. + /// + /// If not null, a secondary track using [Slider.secondaryActiveColor] color + /// is drawn between the thumb and this value, over the inactive track. + /// + /// If less than [Slider.value], then the secondary track is not shown. + /// + /// It can be ideal for media scenarios such as showing the buffering progress + /// while the [Slider.value] shows the play progress. + final double? secondaryTrackValue; + /// Called during a drag when the user is selecting a new value for the slider /// by dragging. /// @@ -365,6 +393,18 @@ class Slider extends StatefulWidget { /// Ignored if this slider is created with [Slider.adaptive]. final Color? inactiveColor; + /// The color to use for the portion of the slider track between the thumb and + /// the [Slider.secondaryTrackValue]. + /// + /// Defaults to the [SliderThemeData.secondaryActiveTrackColor] of the current + /// [SliderTheme]. + /// + /// Using a [SliderTheme] gives much more fine-grained control over the + /// appearance of various components of the slider. + /// + /// Ignored if this slider is created with [Slider.adaptive]. + final Color? secondaryActiveColor; + /// The color of the thumb. /// /// If this color is null: @@ -443,6 +483,7 @@ class Slider extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DoubleProperty('value', value)); + properties.add(DoubleProperty('secondaryTrackValue', secondaryTrackValue)); properties.add(ObjectFlagProperty>('onChanged', onChanged, ifNull: 'disabled')); properties.add(ObjectFlagProperty>.has('onChangeStart', onChangeStart)); properties.add(ObjectFlagProperty>.has('onChangeEnd', onChangeEnd)); @@ -452,6 +493,7 @@ class Slider extends StatefulWidget { properties.add(StringProperty('label', label)); properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('inactiveColor', inactiveColor)); + properties.add(ColorProperty('secondaryActiveColor', secondaryActiveColor)); properties.add(ObjectFlagProperty>.has('semanticFormatterCallback', semanticFormatterCallback)); properties.add(ObjectFlagProperty.has('focusNode', focusNode)); properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus')); @@ -708,8 +750,10 @@ class _SliderState extends State with TickerProviderStateMixin { trackHeight: sliderTheme.trackHeight ?? defaultTrackHeight, activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary, inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.24), + secondaryActiveTrackColor: widget.secondaryActiveColor ?? sliderTheme.secondaryActiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.54), disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.32), disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12), + disabledSecondaryActiveTrackColor: sliderTheme.disabledSecondaryActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12), activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.54), inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? theme.colorScheme.primary.withOpacity(0.54), disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12), @@ -789,6 +833,7 @@ class _SliderState extends State with TickerProviderStateMixin { child: _SliderRenderObjectWidget( key: _renderObjectKey, value: _convert(widget.value), + secondaryTrackValue: (widget.secondaryTrackValue != null) ? _convert(widget.secondaryTrackValue!) : null, divisions: widget.divisions, label: widget.label, sliderTheme: sliderTheme, @@ -852,6 +897,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { const _SliderRenderObjectWidget({ super.key, required this.value, + required this.secondaryTrackValue, required this.divisions, required this.label, required this.sliderTheme, @@ -867,6 +913,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { }); final double value; + final double? secondaryTrackValue; final int? divisions; final String? label; final SliderThemeData sliderTheme; @@ -884,6 +931,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { _RenderSlider createRenderObject(BuildContext context) { return _RenderSlider( value: value, + secondaryTrackValue: secondaryTrackValue, divisions: divisions, label: label, sliderTheme: sliderTheme, @@ -909,6 +957,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { // setter dependent on the `divisions`. ..divisions = divisions ..value = value + ..secondaryTrackValue = secondaryTrackValue ..label = label ..sliderTheme = sliderTheme ..textScaleFactor = textScaleFactor @@ -930,6 +979,7 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { _RenderSlider({ required double value, + required double? secondaryTrackValue, required int? divisions, required String? label, required SliderThemeData sliderTheme, @@ -946,12 +996,14 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { required bool hovering, required DeviceGestureSettings gestureSettings, }) : assert(value != null && value >= 0.0 && value <= 1.0), + assert(secondaryTrackValue == null || (secondaryTrackValue >= 0.0 && secondaryTrackValue <= 1.0)), assert(state != null), assert(textDirection != null), _platform = platform, _semanticFormatterCallback = semanticFormatterCallback, _label = label, _value = value, + _secondaryTrackValue = secondaryTrackValue, _divisions = divisions, _sliderTheme = sliderTheme, _textScaleFactor = textScaleFactor, @@ -1059,6 +1111,17 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { markNeedsSemanticsUpdate(); } + double? get secondaryTrackValue => _secondaryTrackValue; + double? _secondaryTrackValue; + set secondaryTrackValue(double? newValue) { + assert(newValue == null || (newValue >= 0.0 && newValue <= 1.0)); + if (newValue == _secondaryTrackValue) { + return; + } + _secondaryTrackValue = newValue; + markNeedsSemanticsUpdate(); + } + DeviceGestureSettings? get gestureSettings => _drag.gestureSettings; set gestureSettings(DeviceGestureSettings? gestureSettings) { _drag.gestureSettings = gestureSettings; @@ -1417,17 +1480,21 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { @override void paint(PaintingContext context, Offset offset) { final double value = _state.positionController.value; + final double? secondaryValue = _secondaryTrackValue; // 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. final double visualPosition; + final double? secondaryVisualPosition; switch (textDirection) { case TextDirection.rtl: visualPosition = 1.0 - value; + secondaryVisualPosition = (secondaryValue != null) ? (1.0 - secondaryValue) : null; break; case TextDirection.ltr: visualPosition = value; + secondaryVisualPosition = (secondaryValue != null) ? secondaryValue : null; break; } @@ -1438,6 +1505,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { isDiscrete: isDiscrete, ); final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy); + final Offset? secondaryOffset = (secondaryVisualPosition != null) ? Offset(trackRect.left + secondaryVisualPosition * trackRect.width, trackRect.center.dy) : null; _sliderTheme.trackShape!.paint( context, @@ -1447,6 +1515,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { enableAnimation: _enableAnimation, textDirection: _textDirection, thumbCenter: thumbCenter, + secondaryOffset: secondaryOffset, isDiscrete: isDiscrete, isEnabled: isInteractive, ); diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index eeed8a28081..ba0464f5e61 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -266,8 +266,10 @@ class SliderThemeData with Diagnosticable { this.trackHeight, this.activeTrackColor, this.inactiveTrackColor, + this.secondaryActiveTrackColor, this.disabledActiveTrackColor, this.disabledInactiveTrackColor, + this.disabledSecondaryActiveTrackColor, this.activeTickMarkColor, this.inactiveTickMarkColor, this.disabledActiveTickMarkColor, @@ -317,8 +319,10 @@ class SliderThemeData with Diagnosticable { // component Colors (with opacity) from base colors. const int activeTrackAlpha = 0xff; const int inactiveTrackAlpha = 0x3d; // 24% opacity + const int secondaryActiveTrackAlpha = 0x8a; // 54% opacity const int disabledActiveTrackAlpha = 0x52; // 32% opacity const int disabledInactiveTrackAlpha = 0x1f; // 12% opacity + const int disabledSecondaryActiveTrackAlpha = 0x1f; // 12% opacity const int activeTickMarkAlpha = 0x8a; // 54% opacity const int inactiveTickMarkAlpha = 0x8a; // 54% opacity const int disabledActiveTickMarkAlpha = 0x1f; // 12% opacity @@ -332,8 +336,10 @@ class SliderThemeData with Diagnosticable { trackHeight: 2.0, activeTrackColor: primaryColor.withAlpha(activeTrackAlpha), inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha), + secondaryActiveTrackColor: primaryColor.withAlpha(secondaryActiveTrackAlpha), disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha), disabledInactiveTrackColor: primaryColorDark.withAlpha(disabledInactiveTrackAlpha), + disabledSecondaryActiveTrackColor: primaryColorDark.withAlpha(disabledSecondaryActiveTrackAlpha), activeTickMarkColor: primaryColorLight.withAlpha(activeTickMarkAlpha), inactiveTickMarkColor: primaryColor.withAlpha(inactiveTickMarkAlpha), disabledActiveTickMarkColor: primaryColorLight.withAlpha(disabledActiveTickMarkAlpha), @@ -368,10 +374,18 @@ class SliderThemeData with Diagnosticable { /// [Slider.max] position. final Color? inactiveTrackColor; + /// The color of the [Slider] track between the current thumb position and the + /// [Slider.secondaryTrackValue] position. + final Color? secondaryActiveTrackColor; + /// The color of the [Slider] track between the [Slider.min] position and the /// current thumb position when the [Slider] is disabled. final Color? disabledActiveTrackColor; + /// The color of the [Slider] track between the current thumb position and the + /// [Slider.secondaryTrackValue] position when the [Slider] is disabled. + final Color? disabledSecondaryActiveTrackColor; + /// The color of the [Slider] track between the current thumb position and the /// [Slider.max] position when the [Slider] is disabled. final Color? disabledInactiveTrackColor; @@ -573,8 +587,10 @@ class SliderThemeData with Diagnosticable { double? trackHeight, Color? activeTrackColor, Color? inactiveTrackColor, + Color? secondaryActiveTrackColor, Color? disabledActiveTrackColor, Color? disabledInactiveTrackColor, + Color? disabledSecondaryActiveTrackColor, Color? activeTickMarkColor, Color? inactiveTickMarkColor, Color? disabledActiveTickMarkColor, @@ -603,8 +619,10 @@ class SliderThemeData with Diagnosticable { trackHeight: trackHeight ?? this.trackHeight, activeTrackColor: activeTrackColor ?? this.activeTrackColor, inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor, + secondaryActiveTrackColor: secondaryActiveTrackColor ?? this.secondaryActiveTrackColor, disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor, disabledInactiveTrackColor: disabledInactiveTrackColor ?? this.disabledInactiveTrackColor, + disabledSecondaryActiveTrackColor: disabledSecondaryActiveTrackColor ?? this.disabledSecondaryActiveTrackColor, activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor, inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor, disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor, @@ -644,8 +662,10 @@ class SliderThemeData with Diagnosticable { trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t), activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t), + secondaryActiveTrackColor: Color.lerp(a.secondaryActiveTrackColor, b.secondaryActiveTrackColor, t), disabledActiveTrackColor: Color.lerp(a.disabledActiveTrackColor, b.disabledActiveTrackColor, t), disabledInactiveTrackColor: Color.lerp(a.disabledInactiveTrackColor, b.disabledInactiveTrackColor, t), + disabledSecondaryActiveTrackColor: Color.lerp(a.disabledSecondaryActiveTrackColor, b.disabledSecondaryActiveTrackColor, t), activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t), inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t), disabledActiveTickMarkColor: Color.lerp(a.disabledActiveTickMarkColor, b.disabledActiveTickMarkColor, t), @@ -677,8 +697,10 @@ class SliderThemeData with Diagnosticable { trackHeight, activeTrackColor, inactiveTrackColor, + secondaryActiveTrackColor, disabledActiveTrackColor, disabledInactiveTrackColor, + disabledSecondaryActiveTrackColor, activeTickMarkColor, inactiveTickMarkColor, disabledActiveTickMarkColor, @@ -691,9 +713,9 @@ class SliderThemeData with Diagnosticable { overlayShape, tickMarkShape, thumbShape, - trackShape, - valueIndicatorShape, Object.hash( + trackShape, + valueIndicatorShape, rangeTickMarkShape, rangeThumbShape, rangeTrackShape, @@ -718,8 +740,10 @@ class SliderThemeData with Diagnosticable { && other.trackHeight == trackHeight && other.activeTrackColor == activeTrackColor && other.inactiveTrackColor == inactiveTrackColor + && other.secondaryActiveTrackColor == secondaryActiveTrackColor && other.disabledActiveTrackColor == disabledActiveTrackColor && other.disabledInactiveTrackColor == disabledInactiveTrackColor + && other.disabledSecondaryActiveTrackColor == disabledSecondaryActiveTrackColor && other.activeTickMarkColor == activeTickMarkColor && other.inactiveTickMarkColor == inactiveTickMarkColor && other.disabledActiveTickMarkColor == disabledActiveTickMarkColor @@ -752,8 +776,10 @@ class SliderThemeData with Diagnosticable { properties.add(DoubleProperty('trackHeight', trackHeight, defaultValue: defaultData.trackHeight)); properties.add(ColorProperty('activeTrackColor', activeTrackColor, defaultValue: defaultData.activeTrackColor)); properties.add(ColorProperty('inactiveTrackColor', inactiveTrackColor, defaultValue: defaultData.inactiveTrackColor)); + properties.add(ColorProperty('secondaryActiveTrackColor', secondaryActiveTrackColor, defaultValue: defaultData.secondaryActiveTrackColor)); properties.add(ColorProperty('disabledActiveTrackColor', disabledActiveTrackColor, defaultValue: defaultData.disabledActiveTrackColor)); properties.add(ColorProperty('disabledInactiveTrackColor', disabledInactiveTrackColor, defaultValue: defaultData.disabledInactiveTrackColor)); + properties.add(ColorProperty('disabledSecondaryActiveTrackColor', disabledSecondaryActiveTrackColor, defaultValue: defaultData.disabledSecondaryActiveTrackColor)); properties.add(ColorProperty('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor)); properties.add(ColorProperty('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor)); properties.add(ColorProperty('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor)); @@ -1047,6 +1073,11 @@ abstract class SliderTrackShape { /// relative to the origin of the [PaintingContext.canvas]. It can be used as /// the point that divides the track into 2 segments. /// + /// The `secondaryOffset` argument is the offset of the secondary value + /// relative to the origin of the [PaintingContext.canvas]. + /// + /// If not null, the track is divided into 3 segments. + /// /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled} /// /// {@macro flutter.material.SliderComponentShape.paint.isDiscrete} @@ -1068,6 +1099,7 @@ abstract class SliderTrackShape { required SliderThemeData sliderTheme, required Animation enableAnimation, required Offset thumbCenter, + Offset? secondaryOffset, bool isEnabled, bool isDiscrete, required TextDirection textDirection, @@ -1534,6 +1566,7 @@ class RectangularSliderTrackShape extends SliderTrackShape with BaseSliderTrackS required Animation enableAnimation, required TextDirection textDirection, required Offset thumbCenter, + Offset? secondaryOffset, bool isDiscrete = false, bool isEnabled = false, }) { @@ -1593,6 +1626,25 @@ class RectangularSliderTrackShape extends SliderTrackShape with BaseSliderTrackS if (!rightTrackSegment.isEmpty) { context.canvas.drawRect(rightTrackSegment, rightTrackPaint); } + + final bool showSecondaryTrack = (secondaryOffset != null) && + ((textDirection == TextDirection.ltr) + ? (secondaryOffset.dx > thumbCenter.dx) + : (secondaryOffset.dx < thumbCenter.dx)); + + if (showSecondaryTrack) { + final ColorTween secondaryTrackColorTween = ColorTween(begin: sliderTheme.disabledSecondaryActiveTrackColor, end: sliderTheme.secondaryActiveTrackColor); + final Paint secondaryTrackPaint = Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!; + final Rect secondaryTrackSegment = Rect.fromLTRB( + (textDirection == TextDirection.ltr) ? thumbCenter.dx : secondaryOffset.dx, + trackRect.top, + (textDirection == TextDirection.ltr) ? secondaryOffset.dx : thumbCenter.dx, + trackRect.bottom, + ); + if (!secondaryTrackSegment.isEmpty) { + context.canvas.drawRect(secondaryTrackSegment, secondaryTrackPaint); + } + } } } @@ -1635,6 +1687,7 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS required Animation enableAnimation, required TextDirection textDirection, required Offset thumbCenter, + Offset? secondaryOffset, bool isDiscrete = false, bool isEnabled = false, double additionalActiveTrackHeight = 2, @@ -1709,6 +1762,41 @@ class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackS ), rightTrackPaint, ); + + final bool showSecondaryTrack = (secondaryOffset != null) && + ((textDirection == TextDirection.ltr) + ? (secondaryOffset.dx > thumbCenter.dx) + : (secondaryOffset.dx < thumbCenter.dx)); + + if (showSecondaryTrack) { + final ColorTween secondaryTrackColorTween = ColorTween(begin: sliderTheme.disabledSecondaryActiveTrackColor, end: sliderTheme.secondaryActiveTrackColor); + final Paint secondaryTrackPaint = Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!; + if (textDirection == TextDirection.ltr) { + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + thumbCenter.dx, + trackRect.top, + secondaryOffset.dx, + trackRect.bottom, + topRight: trackRadius, + bottomRight: trackRadius, + ), + secondaryTrackPaint, + ); + } else { + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + secondaryOffset.dx, + trackRect.top, + thumbCenter.dx, + trackRect.bottom, + topLeft: trackRadius, + bottomLeft: trackRadius, + ), + secondaryTrackPaint, + ); + } + } } } diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index ff39275a166..6e576840002 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -631,6 +631,7 @@ void main() { try { const Color customColor1 = Color(0xcafefeed); const Color customColor2 = Color(0xdeadbeef); + const Color customColor3 = Color(0xdecaface); final ThemeData theme = ThemeData( platform: TargetPlatform.android, primarySwatch: Colors.blue, @@ -647,6 +648,8 @@ void main() { overlayColor: Color(0xff000010), thumbColor: Color(0xff000011), valueIndicatorColor: Color(0xff000012), + disabledSecondaryActiveTrackColor: Color(0xff000013), + secondaryActiveTrackColor: Color(0xff000014), ), ); final SliderThemeData sliderTheme = theme.sliderTheme; @@ -654,6 +657,7 @@ void main() { Widget buildApp({ Color? activeColor, Color? inactiveColor, + Color? secondaryActiveColor, int? divisions, bool enabled = true, }) { @@ -671,10 +675,12 @@ void main() { data: theme, child: Slider( value: value, + secondaryTrackValue: 0.75, label: '$value', divisions: divisions, activeColor: activeColor, inactiveColor: inactiveColor, + secondaryActiveColor: secondaryActiveColor, onChanged: onChanged, ), ), @@ -690,47 +696,61 @@ void main() { final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay)); // Check default theme for enabled widget. - expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)); + expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor)); expect(material, paints..shadow(color: const Color(0xff000000))); expect(material, paints..circle(color: sliderTheme.thumbColor)); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); // Test setting only the activeColor. await tester.pumpWidget(buildApp(activeColor: customColor1)); - expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)); + expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor)); expect(material, paints..shadow(color: Colors.black)); expect(material, paints..circle(color: customColor1)); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); // Test setting only the inactiveColor. await tester.pumpWidget(buildApp(inactiveColor: customColor1)); - expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)); + expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor)); expect(material, paints..shadow(color: Colors.black)); expect(material, paints..circle(color: sliderTheme.thumbColor)); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); - // Test setting both activeColor and inactiveColor. - await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2)); - expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)); + // Test setting only the secondaryActiveColor. + await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1)); + expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1)); + expect(material, paints..shadow(color: Colors.black)); + expect(material, paints..circle(color: sliderTheme.thumbColor)); + expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); + + // Test setting both activeColor, inactiveColor, and secondaryActiveColor. + await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3)); + expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3)); expect(material, paints..shadow(color: Colors.black)); expect(material, paints..circle(color: customColor1)); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); // Test colors for discrete slider. await tester.pumpWidget(buildApp(divisions: 3)); - expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)); + expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor)); expect( material, paints @@ -744,14 +764,16 @@ void main() { expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); // Test colors for discrete slider with inactiveColor and activeColor set. await tester.pumpWidget(buildApp( activeColor: customColor1, inactiveColor: customColor2, + secondaryActiveColor: customColor3, divisions: 3, )); - expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)); + expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3)); expect( material, paints @@ -766,6 +788,7 @@ void main() { expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); @@ -776,25 +799,29 @@ void main() { material, paints ..rrect(color: sliderTheme.disabledActiveTrackColor) - ..rrect(color: sliderTheme.disabledInactiveTrackColor), + ..rrect(color: sliderTheme.disabledInactiveTrackColor) + ..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor), ); expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor)); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor))); - // Test setting the activeColor and inactiveColor for disabled widget. - await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false)); + // Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget. + await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false)); expect( material, paints ..rrect(color: sliderTheme.disabledActiveTrackColor) - ..rrect(color: sliderTheme.disabledInactiveTrackColor), + ..rrect(color: sliderTheme.disabledInactiveTrackColor) + ..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor), ); expect(material, paints..circle(color: sliderTheme.disabledThumbColor)); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor))); + expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor))); // Test that the default value indicator has the right colors. await tester.pumpWidget(buildApp(divisions: 3)); @@ -2813,10 +2840,12 @@ void main() { activeColor: Colors.blue, divisions: 10, inactiveColor: Colors.grey, + secondaryActiveColor: Colors.blueGrey, label: 'Set a value', max: 100.0, onChanged: null, value: 50.0, + secondaryTrackValue: 75.0, ).debugFillProperties(builder); final List description = builder.properties @@ -2825,6 +2854,7 @@ void main() { expect(description, [ 'value: 50.0', + 'secondaryTrackValue: 75.0', 'disabled', 'min: 0.0', 'max: 100.0', @@ -2832,6 +2862,7 @@ void main() { 'label: "Set a value"', 'activeColor: MaterialColor(primary value: Color(0xff2196f3))', 'inactiveColor: MaterialColor(primary value: Color(0xff9e9e9e))', + 'secondaryActiveColor: MaterialColor(primary value: Color(0xff607d8b))', ]); }); diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index 92ee74da015..e197fb61084 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -34,17 +34,19 @@ void main() { trackHeight: 7.0, activeTrackColor: Color(0xFF000001), inactiveTrackColor: Color(0xFF000002), - disabledActiveTrackColor: Color(0xFF000003), - disabledInactiveTrackColor: Color(0xFF000004), - activeTickMarkColor: Color(0xFF000005), - inactiveTickMarkColor: Color(0xFF000006), - disabledActiveTickMarkColor: Color(0xFF000007), - disabledInactiveTickMarkColor: Color(0xFF000008), - thumbColor: Color(0xFF000009), - overlappingShapeStrokeColor: Color(0xFF000010), - disabledThumbColor: Color(0xFF000011), - overlayColor: Color(0xFF000012), - valueIndicatorColor: Color(0xFF000013), + secondaryActiveTrackColor: Color(0xFF000003), + disabledActiveTrackColor: Color(0xFF000004), + disabledInactiveTrackColor: Color(0xFF000005), + disabledSecondaryActiveTrackColor: Color(0xFF000006), + activeTickMarkColor: Color(0xFF000007), + inactiveTickMarkColor: Color(0xFF000008), + disabledActiveTickMarkColor: Color(0xFF000009), + disabledInactiveTickMarkColor: Color(0xFF000010), + thumbColor: Color(0xFF000011), + overlappingShapeStrokeColor: Color(0xFF000012), + disabledThumbColor: Color(0xFF000013), + overlayColor: Color(0xFF000014), + valueIndicatorColor: Color(0xFF000015), overlayShape: RoundSliderOverlayShape(), tickMarkShape: RoundSliderTickMarkShape(), thumbShape: RoundSliderThumbShape(), @@ -68,17 +70,19 @@ void main() { 'trackHeight: 7.0', 'activeTrackColor: Color(0xff000001)', 'inactiveTrackColor: Color(0xff000002)', - 'disabledActiveTrackColor: Color(0xff000003)', - 'disabledInactiveTrackColor: Color(0xff000004)', - 'activeTickMarkColor: Color(0xff000005)', - 'inactiveTickMarkColor: Color(0xff000006)', - 'disabledActiveTickMarkColor: Color(0xff000007)', - 'disabledInactiveTickMarkColor: Color(0xff000008)', - 'thumbColor: Color(0xff000009)', - 'overlappingShapeStrokeColor: Color(0xff000010)', - 'disabledThumbColor: Color(0xff000011)', - 'overlayColor: Color(0xff000012)', - 'valueIndicatorColor: Color(0xff000013)', + 'secondaryActiveTrackColor: Color(0xff000003)', + 'disabledActiveTrackColor: Color(0xff000004)', + 'disabledInactiveTrackColor: Color(0xff000005)', + 'disabledSecondaryActiveTrackColor: Color(0xff000006)', + 'activeTickMarkColor: Color(0xff000007)', + 'inactiveTickMarkColor: Color(0xff000008)', + 'disabledActiveTickMarkColor: Color(0xff000009)', + 'disabledInactiveTickMarkColor: Color(0xff000010)', + 'thumbColor: Color(0xff000011)', + 'overlappingShapeStrokeColor: Color(0xff000012)', + 'disabledThumbColor: Color(0xff000013)', + 'overlayColor: Color(0xff000014)', + 'valueIndicatorColor: Color(0xff000015)', "overlayShape: Instance of 'RoundSliderOverlayShape'", "tickMarkShape: Instance of 'RoundSliderTickMarkShape'", "thumbShape: Instance of 'RoundSliderThumbShape'", @@ -103,16 +107,18 @@ void main() { final SliderThemeData customTheme = sliderTheme.copyWith( activeTrackColor: Colors.purple, inactiveTrackColor: Colors.purple.withAlpha(0x3d), + secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a), ); - await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false)); + await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; expect( material, paints ..rrect(color: customTheme.disabledActiveTrackColor) - ..rrect(color: customTheme.disabledInactiveTrackColor), + ..rrect(color: customTheme.disabledInactiveTrackColor) + ..rrect(color: customTheme.disabledSecondaryActiveTrackColor), ); }); @@ -125,16 +131,18 @@ void main() { final SliderThemeData customTheme = sliderTheme.copyWith( activeTrackColor: Colors.purple, inactiveTrackColor: Colors.purple.withAlpha(0x3d), + secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a), ); - await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false)); + await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; expect( material, paints ..rrect(color: customTheme.disabledActiveTrackColor) - ..rrect(color: customTheme.disabledInactiveTrackColor), + ..rrect(color: customTheme.disabledInactiveTrackColor) + ..rrect(color: customTheme.disabledSecondaryActiveTrackColor), ); }); @@ -153,8 +161,10 @@ void main() { expect(sliderTheme.activeTrackColor, equals(customColor1.withAlpha(0xff))); expect(sliderTheme.inactiveTrackColor, equals(customColor1.withAlpha(0x3d))); + expect(sliderTheme.secondaryActiveTrackColor, equals(customColor1.withAlpha(0x8a))); expect(sliderTheme.disabledActiveTrackColor, equals(customColor2.withAlpha(0x52))); expect(sliderTheme.disabledInactiveTrackColor, equals(customColor2.withAlpha(0x1f))); + expect(sliderTheme.disabledSecondaryActiveTrackColor, equals(customColor2.withAlpha(0x1f))); expect(sliderTheme.activeTickMarkColor, equals(customColor3.withAlpha(0x8a))); expect(sliderTheme.inactiveTickMarkColor, equals(customColor1.withAlpha(0x8a))); expect(sliderTheme.disabledActiveTickMarkColor, equals(customColor3.withAlpha(0x1f))); @@ -209,8 +219,10 @@ void main() { expect(lerp.trackHeight, equals(4.0)); expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff))); expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d))); + expect(lerp.secondaryActiveTrackColor, equals(middleGrey.withAlpha(0x8a))); expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52))); expect(lerp.disabledInactiveTrackColor, equals(middleGrey.withAlpha(0x1f))); + expect(lerp.disabledSecondaryActiveTrackColor, equals(middleGrey.withAlpha(0x1f))); expect(lerp.activeTickMarkColor, equals(middleGrey.withAlpha(0x8a))); expect(lerp.inactiveTickMarkColor, equals(middleGrey.withAlpha(0x8a))); expect(lerp.disabledActiveTickMarkColor, equals(middleGrey.withAlpha(0x1f))); @@ -229,7 +241,7 @@ void main() { ); final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); - await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); + await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!; const Radius radius = Radius.circular(2); @@ -241,10 +253,11 @@ void main() { material, paints ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor) - ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor), + ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor) + ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.secondaryActiveTrackColor), ); - await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false)); + await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5, enabled: false)); await tester.pumpAndSettle(); // wait for disable animation // The disabled slider thumb is the same size as the enabled thumb. @@ -252,7 +265,8 @@ void main() { material, paints ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor) - ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor), + ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor) + ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledSecondaryActiveTrackColor), ); }); @@ -1348,17 +1362,19 @@ class RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight extends R required Animation enableAnimation, required TextDirection textDirection, required Offset thumbCenter, + Offset? secondaryOffset, bool isDiscrete = false, bool isEnabled = false, double additionalActiveTrackHeight = 2.0, }) { - super.paint(context, offset, parentBox: parentBox, sliderTheme: sliderTheme, enableAnimation: enableAnimation, textDirection: textDirection, thumbCenter: thumbCenter, additionalActiveTrackHeight: this.additionalActiveTrackHeight); + super.paint(context, offset, parentBox: parentBox, sliderTheme: sliderTheme, enableAnimation: enableAnimation, textDirection: textDirection, thumbCenter: thumbCenter, secondaryOffset: secondaryOffset, additionalActiveTrackHeight: this.additionalActiveTrackHeight); } } Widget _buildApp( SliderThemeData sliderTheme, { double value = 0.0, + double? secondaryTrackValue, bool enabled = true, int? divisions, }) { @@ -1370,6 +1386,7 @@ Widget _buildApp( data: sliderTheme, child: Slider( value: value, + secondaryTrackValue: secondaryTrackValue, label: '$value', onChanged: onChanged, divisions: divisions,