"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/slider.dart" (13 Nov 2020, 54267 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:async';
    8 import 'dart:math' as math;
    9 
   10 import 'package:flutter/cupertino.dart';
   11 import 'package:flutter/foundation.dart';
   12 import 'package:flutter/gestures.dart';
   13 import 'package:flutter/rendering.dart';
   14 import 'package:flutter/scheduler.dart' show timeDilation;
   15 import 'package:flutter/services.dart';
   16 import 'package:flutter/widgets.dart';
   17 
   18 import 'constants.dart';
   19 import 'debug.dart';
   20 import 'material.dart';
   21 import 'material_state.dart';
   22 import 'slider_theme.dart';
   23 import 'theme.dart';
   24 
   25 // Examples can assume:
   26 // int _dollars = 0;
   27 // int _duelCommandment = 1;
   28 // void setState(VoidCallback fn) { }
   29 
   30 /// [Slider] uses this callback to paint the value indicator on the overlay.
   31 ///
   32 /// Since the value indicator is painted on the Overlay; this method paints the
   33 /// value indicator in a [RenderBox] that appears in the [Overlay].
   34 typedef PaintValueIndicator = void Function(PaintingContext context, Offset offset);
   35 
   36 enum _SliderType { material, adaptive }
   37 
   38 /// A Material Design slider.
   39 ///
   40 /// Used to select from a range of values.
   41 ///
   42 /// {@tool dartpad --template=stateful_widget_scaffold}
   43 ///
   44 /// ![A slider widget, consisting of 5 divisions and showing the default value
   45 /// indicator.](https://flutter.github.io/assets-for-api-docs/assets/material/slider.png)
   46 ///
   47 /// The Sliders value is part of the Stateful widget subclass to change the value
   48 /// setState was called.
   49 ///
   50 /// ```dart
   51 /// double _currentSliderValue = 20;
   52 ///
   53 /// @override
   54 /// Widget build(BuildContext context) {
   55 ///   return Slider(
   56 ///     value: _currentSliderValue,
   57 ///     min: 0,
   58 ///     max: 100,
   59 ///     divisions: 5,
   60 ///     label: _currentSliderValue.round().toString(),
   61 ///     onChanged: (double value) {
   62 ///       setState(() {
   63 ///         _currentSliderValue = value;
   64 ///       });
   65 ///     },
   66 ///   );
   67 /// }
   68 /// ```
   69 /// {@end-tool}
   70 ///
   71 /// A slider can be used to select from either a continuous or a discrete set of
   72 /// values. The default is to use a continuous range of values from [min] to
   73 /// [max]. To use discrete values, use a non-null value for [divisions], which
   74 /// indicates the number of discrete intervals. For example, if [min] is 0.0 and
   75 /// [max] is 50.0 and [divisions] is 5, then the slider can take on the
   76 /// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
   77 ///
   78 /// The terms for the parts of a slider are:
   79 ///
   80 ///  * The "thumb", which is a shape that slides horizontally when the user
   81 ///    drags it.
   82 ///  * The "track", which is the line that the slider thumb slides along.
   83 ///  * The "value indicator", which is a shape that pops up when the user
   84 ///    is dragging the thumb to indicate the value being selected.
   85 ///  * The "active" side of the slider is the side between the thumb and the
   86 ///    minimum value.
   87 ///  * The "inactive" side of the slider is the side between the thumb and the
   88 ///    maximum value.
   89 ///
   90 /// The slider will be disabled if [onChanged] is null or if the range given by
   91 /// [min]..[max] is empty (i.e. if [min] is equal to [max]).
   92 ///
   93 /// The slider widget itself does not maintain any state. Instead, when the state
   94 /// of the slider changes, the widget calls the [onChanged] callback. Most
   95 /// widgets that use a slider will listen for the [onChanged] callback and
   96 /// rebuild the slider with a new [value] to update the visual appearance of the
   97 /// slider. To know when the value starts to change, or when it is done
   98 /// changing, set the optional callbacks [onChangeStart] and/or [onChangeEnd].
   99 ///
  100 /// By default, a slider will be as wide as possible, centered vertically. When
  101 /// given unbounded constraints, it will attempt to make the track 144 pixels
  102 /// wide (with margins on each side) and will shrink-wrap vertically.
  103 ///
  104 /// Requires one of its ancestors to be a [Material] widget.
  105 ///
  106 /// Requires one of its ancestors to be a [MediaQuery] widget. Typically, these
  107 /// are introduced by the [MaterialApp] or [WidgetsApp] widget at the top of
  108 /// your application widget tree.
  109 ///
  110 /// To determine how it should be displayed (e.g. colors, thumb shape, etc.),
  111 /// a slider uses the [SliderThemeData] available from either a [SliderTheme]
  112 /// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the
  113 /// widget tree. You can also override some of the colors with the [activeColor]
  114 /// and [inactiveColor] properties, although more fine-grained control of the
  115 /// look is achieved using a [SliderThemeData].
  116 ///
  117 /// See also:
  118 ///
  119 ///  * [SliderTheme] and [SliderThemeData] for information about controlling
  120 ///    the visual appearance of the slider.
  121 ///  * [Radio], for selecting among a set of explicit values.
  122 ///  * [Checkbox] and [Switch], for toggling a particular value on or off.
  123 ///  * <https://material.io/design/components/sliders.html>
  124 ///  * [MediaQuery], from which the text scale factor is obtained.
  125 class Slider extends StatefulWidget {
  126   /// Creates a Material Design slider.
  127   ///
  128   /// The slider itself does not maintain any state. Instead, when the state of
  129   /// the slider changes, the widget calls the [onChanged] callback. Most
  130   /// widgets that use a slider will listen for the [onChanged] callback and
  131   /// rebuild the slider with a new [value] to update the visual appearance of
  132   /// the slider.
  133   ///
  134   /// * [value] determines currently selected value for this slider.
  135   /// * [onChanged] is called while the user is selecting a new value for the
  136   ///   slider.
  137   /// * [onChangeStart] is called when the user starts to select a new value for
  138   ///   the slider.
  139   /// * [onChangeEnd] is called when the user is done selecting a new value for
  140   ///   the slider.
  141   ///
  142   /// You can override some of the colors with the [activeColor] and
  143   /// [inactiveColor] properties, although more fine-grained control of the
  144   /// appearance is achieved using a [SliderThemeData].
  145   const Slider({
  146     Key key,
  147     @required this.value,
  148     @required this.onChanged,
  149     this.onChangeStart,
  150     this.onChangeEnd,
  151     this.min = 0.0,
  152     this.max = 1.0,
  153     this.divisions,
  154     this.label,
  155     this.activeColor,
  156     this.inactiveColor,
  157     this.mouseCursor,
  158     this.semanticFormatterCallback,
  159     this.focusNode,
  160     this.autofocus = false,
  161   }) : _sliderType = _SliderType.material,
  162        assert(value != null),
  163        assert(min != null),
  164        assert(max != null),
  165        assert(min <= max),
  166        assert(value >= min && value <= max),
  167        assert(divisions == null || divisions > 0),
  168        super(key: key);
  169 
  170   /// Creates a [CupertinoSlider] if the target platform is iOS, creates a
  171   /// Material Design slider otherwise.
  172   ///
  173   /// If a [CupertinoSlider] is created, the following parameters are
  174   /// ignored: [label], [inactiveColor], [semanticFormatterCallback].
  175   ///
  176   /// The target platform is based on the current [Theme]: [ThemeData.platform].
  177   const Slider.adaptive({
  178     Key key,
  179     @required this.value,
  180     @required this.onChanged,
  181     this.onChangeStart,
  182     this.onChangeEnd,
  183     this.min = 0.0,
  184     this.max = 1.0,
  185     this.divisions,
  186     this.label,
  187     this.mouseCursor,
  188     this.activeColor,
  189     this.inactiveColor,
  190     this.semanticFormatterCallback,
  191     this.focusNode,
  192     this.autofocus = false,
  193   }) : _sliderType = _SliderType.adaptive,
  194        assert(value != null),
  195        assert(min != null),
  196        assert(max != null),
  197        assert(min <= max),
  198        assert(value >= min && value <= max),
  199        assert(divisions == null || divisions > 0),
  200        super(key: key);
  201 
  202   /// The currently selected value for this slider.
  203   ///
  204   /// The slider's thumb is drawn at a position that corresponds to this value.
  205   final double value;
  206 
  207   /// Called during a drag when the user is selecting a new value for the slider
  208   /// by dragging.
  209   ///
  210   /// The slider passes the new value to the callback but does not actually
  211   /// change state until the parent widget rebuilds the slider with the new
  212   /// value.
  213   ///
  214   /// If null, the slider will be displayed as disabled.
  215   ///
  216   /// The callback provided to onChanged should update the state of the parent
  217   /// [StatefulWidget] using the [State.setState] method, so that the parent
  218   /// gets rebuilt; for example:
  219   ///
  220   /// {@tool snippet}
  221   ///
  222   /// ```dart
  223   /// Slider(
  224   ///   value: _duelCommandment.toDouble(),
  225   ///   min: 1.0,
  226   ///   max: 10.0,
  227   ///   divisions: 10,
  228   ///   label: '$_duelCommandment',
  229   ///   onChanged: (double newValue) {
  230   ///     setState(() {
  231   ///       _duelCommandment = newValue.round();
  232   ///     });
  233   ///   },
  234   /// )
  235   /// ```
  236   /// {@end-tool}
  237   ///
  238   /// See also:
  239   ///
  240   ///  * [onChangeStart] for a callback that is called when the user starts
  241   ///    changing the value.
  242   ///  * [onChangeEnd] for a callback that is called when the user stops
  243   ///    changing the value.
  244   final ValueChanged<double> onChanged;
  245 
  246   /// Called when the user starts selecting a new value for the slider.
  247   ///
  248   /// This callback shouldn't be used to update the slider [value] (use
  249   /// [onChanged] for that), but rather to be notified when the user has started
  250   /// selecting a new value by starting a drag or with a tap.
  251   ///
  252   /// The value passed will be the last [value] that the slider had before the
  253   /// change began.
  254   ///
  255   /// {@tool snippet}
  256   ///
  257   /// ```dart
  258   /// Slider(
  259   ///   value: _duelCommandment.toDouble(),
  260   ///   min: 1.0,
  261   ///   max: 10.0,
  262   ///   divisions: 10,
  263   ///   label: '$_duelCommandment',
  264   ///   onChanged: (double newValue) {
  265   ///     setState(() {
  266   ///       _duelCommandment = newValue.round();
  267   ///     });
  268   ///   },
  269   ///   onChangeStart: (double startValue) {
  270   ///     print('Started change at $startValue');
  271   ///   },
  272   /// )
  273   /// ```
  274   /// {@end-tool}
  275   ///
  276   /// See also:
  277   ///
  278   ///  * [onChangeEnd] for a callback that is called when the value change is
  279   ///    complete.
  280   final ValueChanged<double> onChangeStart;
  281 
  282   /// Called when the user is done selecting a new value for the slider.
  283   ///
  284   /// This callback shouldn't be used to update the slider [value] (use
  285   /// [onChanged] for that), but rather to know when the user has completed
  286   /// selecting a new [value] by ending a drag or a click.
  287   ///
  288   /// {@tool snippet}
  289   ///
  290   /// ```dart
  291   /// Slider(
  292   ///   value: _duelCommandment.toDouble(),
  293   ///   min: 1.0,
  294   ///   max: 10.0,
  295   ///   divisions: 10,
  296   ///   label: '$_duelCommandment',
  297   ///   onChanged: (double newValue) {
  298   ///     setState(() {
  299   ///       _duelCommandment = newValue.round();
  300   ///     });
  301   ///   },
  302   ///   onChangeEnd: (double newValue) {
  303   ///     print('Ended change on $newValue');
  304   ///   },
  305   /// )
  306   /// ```
  307   /// {@end-tool}
  308   ///
  309   /// See also:
  310   ///
  311   ///  * [onChangeStart] for a callback that is called when a value change
  312   ///    begins.
  313   final ValueChanged<double> onChangeEnd;
  314 
  315   /// The minimum value the user can select.
  316   ///
  317   /// Defaults to 0.0. Must be less than or equal to [max].
  318   ///
  319   /// If the [max] is equal to the [min], then the slider is disabled.
  320   final double min;
  321 
  322   /// The maximum value the user can select.
  323   ///
  324   /// Defaults to 1.0. Must be greater than or equal to [min].
  325   ///
  326   /// If the [max] is equal to the [min], then the slider is disabled.
  327   final double max;
  328 
  329   /// The number of discrete divisions.
  330   ///
  331   /// Typically used with [label] to show the current discrete value.
  332   ///
  333   /// If null, the slider is continuous.
  334   final int divisions;
  335 
  336   /// A label to show above the slider when the slider is active.
  337   ///
  338   /// It is used to display the value of a discrete slider, and it is displayed
  339   /// as part of the value indicator shape.
  340   ///
  341   /// The label is rendered using the active [ThemeData]'s [TextTheme.bodyText1]
  342   /// text style, with the theme data's [ColorScheme.onPrimary] color. The
  343   /// label's text style can be overridden with
  344   /// [SliderThemeData.valueIndicatorTextStyle].
  345   ///
  346   /// If null, then the value indicator will not be displayed.
  347   ///
  348   /// Ignored if this slider is created with [Slider.adaptive].
  349   ///
  350   /// See also:
  351   ///
  352   ///  * [SliderComponentShape] for how to create a custom value indicator
  353   ///    shape.
  354   final String label;
  355 
  356   /// The color to use for the portion of the slider track that is active.
  357   ///
  358   /// The "active" side of the slider is the side between the thumb and the
  359   /// minimum value.
  360   ///
  361   /// Defaults to [SliderThemeData.activeTrackColor] of the current
  362   /// [SliderTheme].
  363   ///
  364   /// Using a [SliderTheme] gives much more fine-grained control over the
  365   /// appearance of various components of the slider.
  366   final Color activeColor;
  367 
  368   /// The color for the inactive portion of the slider track.
  369   ///
  370   /// The "inactive" side of the slider is the side between the thumb and the
  371   /// maximum value.
  372   ///
  373   /// Defaults to the [SliderThemeData.inactiveTrackColor] of the current
  374   /// [SliderTheme].
  375   ///
  376   /// Using a [SliderTheme] gives much more fine-grained control over the
  377   /// appearance of various components of the slider.
  378   ///
  379   /// Ignored if this slider is created with [Slider.adaptive].
  380   final Color inactiveColor;
  381 
  382   /// The cursor for a mouse pointer when it enters or is hovering over the
  383   /// widget.
  384   ///
  385   /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  386   /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  387   ///
  388   ///  * [MaterialState.hovered].
  389   ///  * [MaterialState.focused].
  390   ///  * [MaterialState.disabled].
  391   ///
  392   /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
  393   final MouseCursor mouseCursor;
  394 
  395   /// The callback used to create a semantic value from a slider value.
  396   ///
  397   /// Defaults to formatting values as a percentage.
  398   ///
  399   /// This is used by accessibility frameworks like TalkBack on Android to
  400   /// inform users what the currently selected value is with more context.
  401   ///
  402   /// {@tool snippet}
  403   ///
  404   /// In the example below, a slider for currency values is configured to
  405   /// announce a value with a currency label.
  406   ///
  407   /// ```dart
  408   /// Slider(
  409   ///   value: _dollars.toDouble(),
  410   ///   min: 20.0,
  411   ///   max: 330.0,
  412   ///   label: '$_dollars dollars',
  413   ///   onChanged: (double newValue) {
  414   ///     setState(() {
  415   ///       _dollars = newValue.round();
  416   ///     });
  417   ///   },
  418   ///   semanticFormatterCallback: (double newValue) {
  419   ///     return '${newValue.round()} dollars';
  420   ///   }
  421   ///  )
  422   /// ```
  423   /// {@end-tool}
  424   ///
  425   /// Ignored if this slider is created with [Slider.adaptive]
  426   final SemanticFormatterCallback semanticFormatterCallback;
  427 
  428   /// {@macro flutter.widgets.Focus.focusNode}
  429   final FocusNode focusNode;
  430 
  431   /// {@macro flutter.widgets.Focus.autofocus}
  432   final bool autofocus;
  433 
  434   final _SliderType _sliderType ;
  435 
  436   @override
  437   _SliderState createState() => _SliderState();
  438 
  439   @override
  440   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  441     super.debugFillProperties(properties);
  442     properties.add(DoubleProperty('value', value));
  443     properties.add(ObjectFlagProperty<ValueChanged<double>>('onChanged', onChanged, ifNull: 'disabled'));
  444     properties.add(ObjectFlagProperty<ValueChanged<double>>.has('onChangeStart', onChangeStart));
  445     properties.add(ObjectFlagProperty<ValueChanged<double>>.has('onChangeEnd', onChangeEnd));
  446     properties.add(DoubleProperty('min', min));
  447     properties.add(DoubleProperty('max', max));
  448     properties.add(IntProperty('divisions', divisions));
  449     properties.add(StringProperty('label', label));
  450     properties.add(ColorProperty('activeColor', activeColor));
  451     properties.add(ColorProperty('inactiveColor', inactiveColor));
  452     properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
  453     properties.add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode));
  454     properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus'));
  455   }
  456 }
  457 
  458 class _SliderState extends State<Slider> with TickerProviderStateMixin {
  459   static const Duration enableAnimationDuration = Duration(milliseconds: 75);
  460   static const Duration valueIndicatorAnimationDuration = Duration(milliseconds: 100);
  461 
  462   // Animation controller that is run when the overlay (a.k.a radial reaction)
  463   // is shown in response to user interaction.
  464   AnimationController overlayController;
  465   // Animation controller that is run when the value indicator is being shown
  466   // or hidden.
  467   AnimationController valueIndicatorController;
  468   // Animation controller that is run when enabling/disabling the slider.
  469   AnimationController enableController;
  470   // Animation controller that is run when transitioning between one value
  471   // and the next on a discrete slider.
  472   AnimationController positionController;
  473   Timer interactionTimer;
  474 
  475   final GlobalKey _renderObjectKey = GlobalKey();
  476   // Keyboard mapping for a focused slider.
  477   Map<LogicalKeySet, Intent> _shortcutMap;
  478   // Action mapping for a focused slider.
  479   Map<Type, Action<Intent>> _actionMap;
  480 
  481   bool get _enabled => widget.onChanged != null;
  482   // Value Indicator Animation that appears on the Overlay.
  483   PaintValueIndicator paintValueIndicator;
  484 
  485   @override
  486   void initState() {
  487     super.initState();
  488     overlayController = AnimationController(
  489       duration: kRadialReactionDuration,
  490       vsync: this,
  491     );
  492     valueIndicatorController = AnimationController(
  493       duration: valueIndicatorAnimationDuration,
  494       vsync: this,
  495     );
  496     enableController = AnimationController(
  497       duration: enableAnimationDuration,
  498       vsync: this,
  499     );
  500     positionController = AnimationController(
  501       duration: Duration.zero,
  502       vsync: this,
  503     );
  504     enableController.value = widget.onChanged != null ? 1.0 : 0.0;
  505     positionController.value = _unlerp(widget.value);
  506     _shortcutMap = <LogicalKeySet, Intent>{
  507       LogicalKeySet(LogicalKeyboardKey.arrowUp): const _AdjustSliderIntent.up(),
  508       LogicalKeySet(LogicalKeyboardKey.arrowDown): const _AdjustSliderIntent.down(),
  509       LogicalKeySet(LogicalKeyboardKey.arrowLeft): const _AdjustSliderIntent.left(),
  510       LogicalKeySet(LogicalKeyboardKey.arrowRight): const _AdjustSliderIntent.right(),
  511     };
  512     _actionMap = <Type, Action<Intent>>{
  513       _AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>(
  514         onInvoke: _actionHandler,
  515       ),
  516     };
  517   }
  518 
  519   @override
  520   void dispose() {
  521     interactionTimer?.cancel();
  522     overlayController.dispose();
  523     valueIndicatorController.dispose();
  524     enableController.dispose();
  525     positionController.dispose();
  526     if (overlayEntry != null) {
  527       overlayEntry.remove();
  528       overlayEntry = null;
  529     }
  530     super.dispose();
  531   }
  532 
  533   void _handleChanged(double value) {
  534     assert(widget.onChanged != null);
  535     final double lerpValue = _lerp(value);
  536     if (lerpValue != widget.value) {
  537       widget.onChanged(lerpValue);
  538     }
  539   }
  540 
  541   void _handleDragStart(double value) {
  542     assert(widget.onChangeStart != null);
  543     widget.onChangeStart(_lerp(value));
  544   }
  545 
  546   void _handleDragEnd(double value) {
  547     assert(widget.onChangeEnd != null);
  548     widget.onChangeEnd(_lerp(value));
  549   }
  550 
  551   void _actionHandler(_AdjustSliderIntent intent) {
  552     final _RenderSlider renderSlider = _renderObjectKey.currentContext.findRenderObject() as _RenderSlider;
  553     final TextDirection textDirection = Directionality.of(_renderObjectKey.currentContext);
  554     switch (intent.type) {
  555       case _SliderAdjustmentType.right:
  556         switch (textDirection) {
  557           case TextDirection.rtl:
  558             renderSlider.decreaseAction();
  559             break;
  560           case TextDirection.ltr:
  561             renderSlider.increaseAction();
  562             break;
  563         }
  564         break;
  565       case _SliderAdjustmentType.left:
  566         switch (textDirection) {
  567           case TextDirection.rtl:
  568             renderSlider.increaseAction();
  569             break;
  570           case TextDirection.ltr:
  571             renderSlider.decreaseAction();
  572             break;
  573         }
  574         break;
  575       case _SliderAdjustmentType.up:
  576         renderSlider.increaseAction();
  577         break;
  578       case _SliderAdjustmentType.down:
  579         renderSlider.decreaseAction();
  580         break;
  581     }
  582   }
  583 
  584   bool _focused = false;
  585   void _handleFocusHighlightChanged(bool focused) {
  586     if (focused != _focused) {
  587       setState(() { _focused = focused; });
  588     }
  589   }
  590 
  591   bool _hovering = false;
  592   void _handleHoverChanged(bool hovering) {
  593     if (hovering != _hovering) {
  594       setState(() { _hovering = hovering; });
  595     }
  596   }
  597 
  598   // Returns a number between min and max, proportional to value, which must
  599   // be between 0.0 and 1.0.
  600   double _lerp(double value) {
  601     assert(value >= 0.0);
  602     assert(value <= 1.0);
  603     return value * (widget.max - widget.min) + widget.min;
  604   }
  605 
  606   // Returns a number between 0.0 and 1.0, given a value between min and max.
  607   double _unlerp(double value) {
  608     assert(value <= widget.max);
  609     assert(value >= widget.min);
  610     return widget.max > widget.min ? (value - widget.min) / (widget.max - widget.min) : 0.0;
  611   }
  612 
  613   @override
  614   Widget build(BuildContext context) {
  615     assert(debugCheckHasMaterial(context));
  616     assert(debugCheckHasMediaQuery(context));
  617 
  618     switch (widget._sliderType) {
  619       case _SliderType.material:
  620         return _buildMaterialSlider(context);
  621 
  622       case _SliderType.adaptive: {
  623         final ThemeData theme = Theme.of(context);
  624         assert(theme.platform != null);
  625         switch (theme.platform) {
  626           case TargetPlatform.android:
  627           case TargetPlatform.fuchsia:
  628           case TargetPlatform.linux:
  629           case TargetPlatform.windows:
  630             return _buildMaterialSlider(context);
  631           case TargetPlatform.iOS:
  632           case TargetPlatform.macOS:
  633             return _buildCupertinoSlider(context);
  634         }
  635       }
  636     }
  637     assert(false);
  638     return null;
  639   }
  640 
  641   Widget _buildMaterialSlider(BuildContext context) {
  642     final ThemeData theme = Theme.of(context);
  643     SliderThemeData sliderTheme = SliderTheme.of(context);
  644 
  645     // If the widget has active or inactive colors specified, then we plug them
  646     // in to the slider theme as best we can. If the developer wants more
  647     // control than that, then they need to use a SliderTheme. The default
  648     // colors come from the ThemeData.colorScheme. These colors, along with
  649     // the default shapes and text styles are aligned to the Material
  650     // Guidelines.
  651 
  652     const double _defaultTrackHeight = 4;
  653     const SliderTrackShape _defaultTrackShape = RoundedRectSliderTrackShape();
  654     const SliderTickMarkShape _defaultTickMarkShape = RoundSliderTickMarkShape();
  655     const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
  656     const SliderComponentShape _defaultThumbShape = RoundSliderThumbShape();
  657     const SliderComponentShape _defaultValueIndicatorShape = RectangularSliderValueIndicatorShape();
  658     const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
  659 
  660     // The value indicator's color is not the same as the thumb and active track
  661     // (which can be defined by activeColor) if the
  662     // RectangularSliderValueIndicatorShape is used. In all other cases, the
  663     // value indicator is assumed to be the same as the active color.
  664     final SliderComponentShape valueIndicatorShape = sliderTheme.valueIndicatorShape ?? _defaultValueIndicatorShape;
  665     Color valueIndicatorColor;
  666     if (valueIndicatorShape is RectangularSliderValueIndicatorShape) {
  667       valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));
  668     } else {
  669       valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
  670     }
  671 
  672     sliderTheme = sliderTheme.copyWith(
  673       trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
  674       activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
  675       inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.24),
  676       disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.32),
  677       disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
  678       activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.54),
  679       inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? theme.colorScheme.primary.withOpacity(0.54),
  680       disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12),
  681       disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
  682       thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
  683       disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface),
  684       overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
  685       valueIndicatorColor: valueIndicatorColor,
  686       trackShape: sliderTheme.trackShape ?? _defaultTrackShape,
  687       tickMarkShape: sliderTheme.tickMarkShape ?? _defaultTickMarkShape,
  688       thumbShape: sliderTheme.thumbShape ?? _defaultThumbShape,
  689       overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
  690       valueIndicatorShape: valueIndicatorShape,
  691       showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
  692       valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1.copyWith(
  693         color: theme.colorScheme.onPrimary,
  694       ),
  695     );
  696     final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
  697       widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
  698       <MaterialState>{
  699         if (!_enabled) MaterialState.disabled,
  700         if (_hovering) MaterialState.hovered,
  701         if (_focused) MaterialState.focused,
  702       },
  703     );
  704 
  705     // This size is used as the max bounds for the painting of the value
  706     // indicators It must be kept in sync with the function with the same name
  707     // in range_slider.dart.
  708     Size _screenSize() => MediaQuery.of(context).size;
  709 
  710     return Semantics(
  711       container: true,
  712       child: FocusableActionDetector(
  713         actions: _actionMap,
  714         shortcuts: _shortcutMap,
  715         focusNode: widget.focusNode,
  716         autofocus: widget.autofocus,
  717         enabled: _enabled,
  718         onShowFocusHighlight: _handleFocusHighlightChanged,
  719         onShowHoverHighlight: _handleHoverChanged,
  720         mouseCursor: effectiveMouseCursor,
  721         child: CompositedTransformTarget(
  722           link: _layerLink,
  723           child: _SliderRenderObjectWidget(
  724             key: _renderObjectKey,
  725             value: _unlerp(widget.value),
  726             divisions: widget.divisions,
  727             label: widget.label,
  728             sliderTheme: sliderTheme,
  729             textScaleFactor: MediaQuery.of(context).textScaleFactor,
  730             screenSize: _screenSize(),
  731             onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
  732             onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
  733             onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
  734             state: this,
  735             semanticFormatterCallback: widget.semanticFormatterCallback,
  736             hasFocus: _focused,
  737             hovering: _hovering,
  738           ),
  739         ),
  740       ),
  741     );
  742   }
  743 
  744   Widget _buildCupertinoSlider(BuildContext context) {
  745     // The render box of a slider has a fixed height but takes up the available
  746     // width. Wrapping the [CupertinoSlider] in this manner will help maintain
  747     // the same size.
  748     return SizedBox(
  749       width: double.infinity,
  750       child: CupertinoSlider(
  751         value: widget.value,
  752         onChanged: widget.onChanged,
  753         onChangeStart: widget.onChangeStart,
  754         onChangeEnd: widget.onChangeEnd,
  755         min: widget.min,
  756         max: widget.max,
  757         divisions: widget.divisions,
  758         activeColor: widget.activeColor,
  759       ),
  760     );
  761   }
  762   final LayerLink _layerLink = LayerLink();
  763 
  764   OverlayEntry overlayEntry;
  765 
  766   void showValueIndicator() {
  767     if (overlayEntry == null) {
  768       overlayEntry = OverlayEntry(
  769         builder: (BuildContext context) {
  770           return CompositedTransformFollower(
  771             link: _layerLink,
  772             child: _ValueIndicatorRenderObjectWidget(
  773               state: this,
  774             ),
  775           );
  776         },
  777       );
  778       Overlay.of(context).insert(overlayEntry);
  779     }
  780   }
  781 }
  782 
  783 
  784 class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
  785   const _SliderRenderObjectWidget({
  786     Key key,
  787     this.value,
  788     this.divisions,
  789     this.label,
  790     this.sliderTheme,
  791     this.textScaleFactor,
  792     this.screenSize,
  793     this.onChanged,
  794     this.onChangeStart,
  795     this.onChangeEnd,
  796     this.state,
  797     this.semanticFormatterCallback,
  798     this.hasFocus,
  799     this.hovering,
  800   }) : super(key: key);
  801 
  802   final double value;
  803   final int divisions;
  804   final String label;
  805   final SliderThemeData sliderTheme;
  806   final double textScaleFactor;
  807   final Size screenSize;
  808   final ValueChanged<double> onChanged;
  809   final ValueChanged<double> onChangeStart;
  810   final ValueChanged<double> onChangeEnd;
  811   final SemanticFormatterCallback semanticFormatterCallback;
  812   final _SliderState state;
  813   final bool hasFocus;
  814   final bool hovering;
  815 
  816   @override
  817   _RenderSlider createRenderObject(BuildContext context) {
  818     return _RenderSlider(
  819       value: value,
  820       divisions: divisions,
  821       label: label,
  822       sliderTheme: sliderTheme,
  823       textScaleFactor: textScaleFactor,
  824       screenSize: screenSize,
  825       onChanged: onChanged,
  826       onChangeStart: onChangeStart,
  827       onChangeEnd: onChangeEnd,
  828       state: state,
  829       textDirection: Directionality.of(context),
  830       semanticFormatterCallback: semanticFormatterCallback,
  831       platform: Theme.of(context).platform,
  832       hasFocus: hasFocus,
  833       hovering: hovering,
  834     );
  835   }
  836 
  837   @override
  838   void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
  839     renderObject
  840       ..value = value
  841       ..divisions = divisions
  842       ..label = label
  843       ..sliderTheme = sliderTheme
  844       ..theme = Theme.of(context)
  845       ..textScaleFactor = textScaleFactor
  846       ..screenSize = screenSize
  847       ..onChanged = onChanged
  848       ..onChangeStart = onChangeStart
  849       ..onChangeEnd = onChangeEnd
  850       ..textDirection = Directionality.of(context)
  851       ..semanticFormatterCallback = semanticFormatterCallback
  852       ..platform = Theme.of(context).platform
  853       ..hasFocus = hasFocus
  854       ..hovering = hovering;
  855     // Ticker provider cannot change since there's a 1:1 relationship between
  856     // the _SliderRenderObjectWidget object and the _SliderState object.
  857   }
  858 }
  859 
  860 class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
  861   _RenderSlider({
  862     @required double value,
  863     int divisions,
  864     String label,
  865     SliderThemeData sliderTheme,
  866     double textScaleFactor,
  867     Size screenSize,
  868     TargetPlatform platform,
  869     ValueChanged<double> onChanged,
  870     SemanticFormatterCallback semanticFormatterCallback,
  871     this.onChangeStart,
  872     this.onChangeEnd,
  873     @required _SliderState state,
  874     @required TextDirection textDirection,
  875     bool hasFocus,
  876     bool hovering,
  877   }) : assert(value != null && value >= 0.0 && value <= 1.0),
  878         assert(state != null),
  879         assert(textDirection != null),
  880         _platform = platform,
  881         _semanticFormatterCallback = semanticFormatterCallback,
  882         _label = label,
  883         _value = value,
  884         _divisions = divisions,
  885         _sliderTheme = sliderTheme,
  886         _textScaleFactor = textScaleFactor,
  887         _screenSize = screenSize,
  888         _onChanged = onChanged,
  889         _state = state,
  890         _textDirection = textDirection,
  891         _hasFocus = hasFocus,
  892         _hovering = hovering {
  893     _updateLabelPainter();
  894     final GestureArenaTeam team = GestureArenaTeam();
  895     _drag = HorizontalDragGestureRecognizer()
  896       ..team = team
  897       ..onStart = _handleDragStart
  898       ..onUpdate = _handleDragUpdate
  899       ..onEnd = _handleDragEnd
  900       ..onCancel = _endInteraction;
  901     _tap = TapGestureRecognizer()
  902       ..team = team
  903       ..onTapDown = _handleTapDown
  904       ..onTapUp = _handleTapUp
  905       ..onTapCancel = _endInteraction;
  906     _overlayAnimation = CurvedAnimation(
  907       parent: _state.overlayController,
  908       curve: Curves.fastOutSlowIn,
  909     );
  910     _valueIndicatorAnimation = CurvedAnimation(
  911       parent: _state.valueIndicatorController,
  912       curve: Curves.fastOutSlowIn,
  913     )..addStatusListener((AnimationStatus status) {
  914       if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
  915         _state.overlayEntry.remove();
  916         _state.overlayEntry = null;
  917       }
  918     });
  919     _enableAnimation = CurvedAnimation(
  920       parent: _state.enableController,
  921       curve: Curves.easeInOut,
  922     );
  923   }
  924   static const Duration _positionAnimationDuration = Duration(milliseconds: 75);
  925   static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
  926 
  927   // This value is the touch target, 48, multiplied by 3.
  928   static const double _minPreferredTrackWidth = 144.0;
  929 
  930   // Compute the largest width and height needed to paint the slider shapes,
  931   // other than the track shape. It is assumed that these shapes are vertically
  932   // centered on the track.
  933   double get _maxSliderPartWidth => _sliderPartSizes.map((Size size) => size.width).reduce(math.max);
  934   double get _maxSliderPartHeight => _sliderPartSizes.map((Size size) => size.height).reduce(math.max);
  935   List<Size> get _sliderPartSizes => <Size>[
  936     _sliderTheme.overlayShape.getPreferredSize(isInteractive, isDiscrete),
  937     _sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete),
  938     _sliderTheme.tickMarkShape.getPreferredSize(isEnabled: isInteractive, sliderTheme: sliderTheme),
  939   ];
  940   double get _minPreferredTrackHeight => _sliderTheme.trackHeight;
  941 
  942   final _SliderState _state;
  943   Animation<double> _overlayAnimation;
  944   Animation<double> _valueIndicatorAnimation;
  945   Animation<double> _enableAnimation;
  946   final TextPainter _labelPainter = TextPainter();
  947   HorizontalDragGestureRecognizer _drag;
  948   TapGestureRecognizer _tap;
  949   bool _active = false;
  950   double _currentDragValue = 0.0;
  951 
  952   // This rect is used in gesture calculations, where the gesture coordinates
  953   // are relative to the sliders origin. Therefore, the offset is passed as
  954   // (0,0).
  955   Rect get _trackRect => _sliderTheme.trackShape.getPreferredRect(
  956     parentBox: this,
  957     offset: Offset.zero,
  958     sliderTheme: _sliderTheme,
  959     isDiscrete: false,
  960   );
  961 
  962   bool get isInteractive => onChanged != null;
  963 
  964   bool get isDiscrete => divisions != null && divisions > 0;
  965 
  966   double get value => _value;
  967   double _value;
  968   set value(double newValue) {
  969     assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
  970     final double convertedValue = isDiscrete ? _discretize(newValue) : newValue;
  971     if (convertedValue == _value) {
  972       return;
  973     }
  974     _value = convertedValue;
  975     if (isDiscrete) {
  976       // Reset the duration to match the distance that we're traveling, so that
  977       // whatever the distance, we still do it in _positionAnimationDuration,
  978       // and if we get re-targeted in the middle, it still takes that long to
  979       // get to the new location.
  980       final double distance = (_value - _state.positionController.value).abs();
  981       _state.positionController.duration = distance != 0.0
  982         ? _positionAnimationDuration * (1.0 / distance)
  983         : Duration.zero;
  984       _state.positionController.animateTo(convertedValue, curve: Curves.easeInOut);
  985     } else {
  986       _state.positionController.value = convertedValue;
  987     }
  988     markNeedsSemanticsUpdate();
  989   }
  990 
  991   TargetPlatform _platform;
  992   TargetPlatform get platform => _platform;
  993   set platform(TargetPlatform value) {
  994     if (_platform == value)
  995       return;
  996     _platform = value;
  997     markNeedsSemanticsUpdate();
  998   }
  999 
 1000   SemanticFormatterCallback _semanticFormatterCallback;
 1001   SemanticFormatterCallback get semanticFormatterCallback => _semanticFormatterCallback;
 1002   set semanticFormatterCallback(SemanticFormatterCallback value) {
 1003     if (_semanticFormatterCallback == value)
 1004       return;
 1005     _semanticFormatterCallback = value;
 1006     markNeedsSemanticsUpdate();
 1007   }
 1008 
 1009   int get divisions => _divisions;
 1010   int _divisions;
 1011   set divisions(int value) {
 1012     if (value == _divisions) {
 1013       return;
 1014     }
 1015     _divisions = value;
 1016     markNeedsPaint();
 1017   }
 1018 
 1019   String get label => _label;
 1020   String _label;
 1021   set label(String value) {
 1022     if (value == _label) {
 1023       return;
 1024     }
 1025     _label = value;
 1026     _updateLabelPainter();
 1027   }
 1028 
 1029   SliderThemeData get sliderTheme => _sliderTheme;
 1030   SliderThemeData _sliderTheme;
 1031   set sliderTheme(SliderThemeData value) {
 1032     if (value == _sliderTheme) {
 1033       return;
 1034     }
 1035     _sliderTheme = value;
 1036     markNeedsPaint();
 1037   }
 1038 
 1039   ThemeData get theme => _theme;
 1040   ThemeData _theme;
 1041   set theme(ThemeData value) {
 1042     if (value == _theme) {
 1043       return;
 1044     }
 1045     _theme = value;
 1046     markNeedsPaint();
 1047   }
 1048 
 1049   double get textScaleFactor => _textScaleFactor;
 1050   double _textScaleFactor;
 1051   set textScaleFactor(double value) {
 1052     if (value == _textScaleFactor) {
 1053       return;
 1054     }
 1055     _textScaleFactor = value;
 1056     _updateLabelPainter();
 1057   }
 1058 
 1059   Size get screenSize => _screenSize;
 1060   Size _screenSize;
 1061   set screenSize(Size value) {
 1062     if (value == _screenSize) {
 1063       return;
 1064     }
 1065     _screenSize = value;
 1066     markNeedsPaint();
 1067   }
 1068 
 1069   ValueChanged<double> get onChanged => _onChanged;
 1070   ValueChanged<double> _onChanged;
 1071   set onChanged(ValueChanged<double> value) {
 1072     if (value == _onChanged) {
 1073       return;
 1074     }
 1075     final bool wasInteractive = isInteractive;
 1076     _onChanged = value;
 1077     if (wasInteractive != isInteractive) {
 1078       if (isInteractive) {
 1079         _state.enableController.forward();
 1080       } else {
 1081         _state.enableController.reverse();
 1082       }
 1083       markNeedsPaint();
 1084       markNeedsSemanticsUpdate();
 1085     }
 1086   }
 1087 
 1088   ValueChanged<double> onChangeStart;
 1089   ValueChanged<double> onChangeEnd;
 1090 
 1091   TextDirection get textDirection => _textDirection;
 1092   TextDirection _textDirection;
 1093   set textDirection(TextDirection value) {
 1094     assert(value != null);
 1095     if (value == _textDirection) {
 1096       return;
 1097     }
 1098     _textDirection = value;
 1099     _updateLabelPainter();
 1100   }
 1101 
 1102   /// True if this slider has the input focus.
 1103   bool get hasFocus => _hasFocus;
 1104   bool _hasFocus;
 1105   set hasFocus(bool value) {
 1106     assert(value != null);
 1107     if (value == _hasFocus)
 1108       return;
 1109     _hasFocus = value;
 1110     _updateForFocusOrHover(_hasFocus);
 1111     markNeedsSemanticsUpdate();
 1112   }
 1113 
 1114   /// True if this slider is being hovered over by a pointer.
 1115   bool get hovering => _hovering;
 1116   bool _hovering;
 1117   set hovering(bool value) {
 1118     assert(value != null);
 1119     if (value == _hovering)
 1120       return;
 1121     _hovering = value;
 1122     _updateForFocusOrHover(_hovering);
 1123   }
 1124 
 1125   void _updateForFocusOrHover(bool hasFocusOrIsHovering) {
 1126     if (hasFocusOrIsHovering) {
 1127       _state.overlayController.forward();
 1128       if (showValueIndicator) {
 1129         _state.valueIndicatorController.forward();
 1130       }
 1131     } else {
 1132       _state.overlayController.reverse();
 1133       if (showValueIndicator) {
 1134         _state.valueIndicatorController.reverse();
 1135       }
 1136     }
 1137   }
 1138 
 1139   bool get showValueIndicator {
 1140     bool showValueIndicator;
 1141     switch (_sliderTheme.showValueIndicator) {
 1142       case ShowValueIndicator.onlyForDiscrete:
 1143         showValueIndicator = isDiscrete;
 1144         break;
 1145       case ShowValueIndicator.onlyForContinuous:
 1146         showValueIndicator = !isDiscrete;
 1147         break;
 1148       case ShowValueIndicator.always:
 1149         showValueIndicator = true;
 1150         break;
 1151       case ShowValueIndicator.never:
 1152         showValueIndicator = false;
 1153         break;
 1154     }
 1155     return showValueIndicator;
 1156   }
 1157 
 1158   double get _adjustmentUnit {
 1159     switch (_platform) {
 1160       case TargetPlatform.iOS:
 1161       case TargetPlatform.macOS:
 1162         // Matches iOS implementation of material slider.
 1163         return 0.1;
 1164       case TargetPlatform.android:
 1165       case TargetPlatform.fuchsia:
 1166       case TargetPlatform.linux:
 1167       case TargetPlatform.windows:
 1168         // Matches Android implementation of material slider.
 1169         return 0.05;
 1170     }
 1171     assert(false, 'Unhandled TargetPlatform $_platform');
 1172     return 0.0;
 1173   }
 1174 
 1175   void _updateLabelPainter() {
 1176     if (label != null) {
 1177       _labelPainter
 1178         ..text = TextSpan(
 1179           style: _sliderTheme.valueIndicatorTextStyle,
 1180           text: label,
 1181         )
 1182         ..textDirection = textDirection
 1183         ..textScaleFactor = textScaleFactor
 1184         ..layout();
 1185     } else {
 1186       _labelPainter.text = null;
 1187     }
 1188     // Changing the textDirection can result in the layout changing, because the
 1189     // bidi algorithm might line up the glyphs differently which can result in
 1190     // different ligatures, different shapes, etc. So we always markNeedsLayout.
 1191     markNeedsLayout();
 1192   }
 1193 
 1194   @override
 1195   void systemFontsDidChange() {
 1196     super.systemFontsDidChange();
 1197     _labelPainter.markNeedsLayout();
 1198     _updateLabelPainter();
 1199   }
 1200 
 1201   @override
 1202   void attach(PipelineOwner owner) {
 1203     super.attach(owner);
 1204     _overlayAnimation.addListener(markNeedsPaint);
 1205     _valueIndicatorAnimation.addListener(markNeedsPaint);
 1206     _enableAnimation.addListener(markNeedsPaint);
 1207     _state.positionController.addListener(markNeedsPaint);
 1208   }
 1209 
 1210   @override
 1211   void detach() {
 1212     _overlayAnimation.removeListener(markNeedsPaint);
 1213     _valueIndicatorAnimation.removeListener(markNeedsPaint);
 1214     _enableAnimation.removeListener(markNeedsPaint);
 1215     _state.positionController.removeListener(markNeedsPaint);
 1216     super.detach();
 1217   }
 1218 
 1219   double _getValueFromVisualPosition(double visualPosition) {
 1220     switch (textDirection) {
 1221       case TextDirection.rtl:
 1222         return 1.0 - visualPosition;
 1223       case TextDirection.ltr:
 1224         return visualPosition;
 1225     }
 1226     return null;
 1227   }
 1228 
 1229   double _getValueFromGlobalPosition(Offset globalPosition) {
 1230     final double visualPosition = (globalToLocal(globalPosition).dx - _trackRect.left) / _trackRect.width;
 1231     return _getValueFromVisualPosition(visualPosition);
 1232   }
 1233 
 1234   double _discretize(double value) {
 1235     double result = value.clamp(0.0, 1.0) as double;
 1236     if (isDiscrete) {
 1237       result = (result * divisions).round() / divisions;
 1238     }
 1239     return result;
 1240   }
 1241 
 1242   void _startInteraction(Offset globalPosition) {
 1243     _state.showValueIndicator();
 1244     if (isInteractive) {
 1245       _active = true;
 1246       // We supply the *current* value as the start location, so that if we have
 1247       // a tap, it consists of a call to onChangeStart with the previous value and
 1248       // a call to onChangeEnd with the new value.
 1249       if (onChangeStart != null) {
 1250         onChangeStart(_discretize(value));
 1251       }
 1252       _currentDragValue = _getValueFromGlobalPosition(globalPosition);
 1253       onChanged(_discretize(_currentDragValue));
 1254       _state.overlayController.forward();
 1255       if (showValueIndicator) {
 1256         _state.valueIndicatorController.forward();
 1257         _state.interactionTimer?.cancel();
 1258         _state.interactionTimer = Timer(_minimumInteractionTime * timeDilation, () {
 1259           _state.interactionTimer = null;
 1260           if (!_active &&
 1261               _state.valueIndicatorController.status == AnimationStatus.completed) {
 1262             _state.valueIndicatorController.reverse();
 1263           }
 1264         });
 1265       }
 1266     }
 1267   }
 1268 
 1269   void _endInteraction() {
 1270     if (!_state.mounted) {
 1271       return;
 1272     }
 1273 
 1274     if (_active && _state.mounted) {
 1275       if (onChangeEnd != null) {
 1276         onChangeEnd(_discretize(_currentDragValue));
 1277       }
 1278       _active = false;
 1279       _currentDragValue = 0.0;
 1280       _state.overlayController.reverse();
 1281 
 1282       if (showValueIndicator && _state.interactionTimer == null) {
 1283         _state.valueIndicatorController.reverse();
 1284       }
 1285     }
 1286   }
 1287 
 1288   void _handleDragStart(DragStartDetails details) {
 1289     _startInteraction(details.globalPosition);
 1290   }
 1291 
 1292   void _handleDragUpdate(DragUpdateDetails details) {
 1293     if (!_state.mounted) {
 1294       return;
 1295     }
 1296 
 1297     if (isInteractive) {
 1298       final double valueDelta = details.primaryDelta / _trackRect.width;
 1299       switch (textDirection) {
 1300         case TextDirection.rtl:
 1301           _currentDragValue -= valueDelta;
 1302           break;
 1303         case TextDirection.ltr:
 1304           _currentDragValue += valueDelta;
 1305           break;
 1306       }
 1307       onChanged(_discretize(_currentDragValue));
 1308     }
 1309   }
 1310 
 1311   void _handleDragEnd(DragEndDetails details) {
 1312     _endInteraction();
 1313   }
 1314 
 1315   void _handleTapDown(TapDownDetails details) {
 1316     _startInteraction(details.globalPosition);
 1317   }
 1318 
 1319   void _handleTapUp(TapUpDetails details) {
 1320     _endInteraction();
 1321   }
 1322 
 1323   @override
 1324   bool hitTestSelf(Offset position) => true;
 1325 
 1326   @override
 1327   void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
 1328     assert(debugHandleEvent(event, entry));
 1329     if (event is PointerDownEvent && isInteractive) {
 1330       // We need to add the drag first so that it has priority.
 1331       _drag.addPointer(event);
 1332       _tap.addPointer(event);
 1333     }
 1334   }
 1335 
 1336   @override
 1337   double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
 1338 
 1339   @override
 1340   double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
 1341 
 1342   @override
 1343   double computeMinIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight, _maxSliderPartHeight);
 1344 
 1345   @override
 1346   double computeMaxIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight, _maxSliderPartHeight);
 1347 
 1348   @override
 1349   bool get sizedByParent => true;
 1350 
 1351   @override
 1352   void performResize() {
 1353     size = Size(
 1354       constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
 1355       constraints.hasBoundedHeight ? constraints.maxHeight : math.max(_minPreferredTrackHeight, _maxSliderPartHeight),
 1356     );
 1357   }
 1358 
 1359   @override
 1360   void paint(PaintingContext context, Offset offset) {
 1361     final double value = _state.positionController.value;
 1362 
 1363     // The visual position is the position of the thumb from 0 to 1 from left
 1364     // to right. In left to right, this is the same as the value, but it is
 1365     // reversed for right to left text.
 1366     double visualPosition;
 1367     switch (textDirection) {
 1368       case TextDirection.rtl:
 1369         visualPosition = 1.0 - value;
 1370         break;
 1371       case TextDirection.ltr:
 1372         visualPosition = value;
 1373         break;
 1374     }
 1375 
 1376     final Rect trackRect = _sliderTheme.trackShape.getPreferredRect(
 1377       parentBox: this,
 1378       offset: offset,
 1379       sliderTheme: _sliderTheme,
 1380       isDiscrete: isDiscrete,
 1381     );
 1382     final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy);
 1383 
 1384     _sliderTheme.trackShape.paint(
 1385       context,
 1386       offset,
 1387       parentBox: this,
 1388       sliderTheme: _sliderTheme,
 1389       enableAnimation: _enableAnimation,
 1390       textDirection: _textDirection,
 1391       thumbCenter: thumbCenter,
 1392       isDiscrete: isDiscrete,
 1393       isEnabled: isInteractive,
 1394     );
 1395 
 1396     if (!_overlayAnimation.isDismissed) {
 1397       _sliderTheme.overlayShape.paint(
 1398         context,
 1399         thumbCenter,
 1400         activationAnimation: _overlayAnimation,
 1401         enableAnimation: _enableAnimation,
 1402         isDiscrete: isDiscrete,
 1403         labelPainter: _labelPainter,
 1404         parentBox: this,
 1405         sliderTheme: _sliderTheme,
 1406         textDirection: _textDirection,
 1407         value: _value,
 1408       );
 1409     }
 1410 
 1411     if (isDiscrete) {
 1412       final double tickMarkWidth = _sliderTheme.tickMarkShape.getPreferredSize(
 1413         isEnabled: isInteractive,
 1414         sliderTheme: _sliderTheme,
 1415       ).width;
 1416       final double padding = trackRect.height;
 1417       final double adjustedTrackWidth = trackRect.width - padding;
 1418       // If the tick marks would be too dense, don't bother painting them.
 1419       if (adjustedTrackWidth / divisions >= 3.0 * tickMarkWidth) {
 1420         final double dy = trackRect.center.dy;
 1421         for (int i = 0; i <= divisions; i++) {
 1422           final double value = i / divisions;
 1423           // The ticks are mapped to be within the track, so the tick mark width
 1424           // must be subtracted from the track width.
 1425           final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
 1426           final Offset tickMarkOffset = Offset(dx, dy);
 1427           _sliderTheme.tickMarkShape.paint(
 1428             context,
 1429             tickMarkOffset,
 1430             parentBox: this,
 1431             sliderTheme: _sliderTheme,
 1432             enableAnimation: _enableAnimation,
 1433             textDirection: _textDirection,
 1434             thumbCenter: thumbCenter,
 1435             isEnabled: isInteractive,
 1436           );
 1437         }
 1438       }
 1439     }
 1440 
 1441     if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
 1442       if (showValueIndicator) {
 1443         _state.paintValueIndicator = (PaintingContext context, Offset offset) {
 1444           if (attached) {
 1445             _sliderTheme.valueIndicatorShape.paint(
 1446               context,
 1447               offset + thumbCenter,
 1448               activationAnimation: _valueIndicatorAnimation,
 1449               enableAnimation: _enableAnimation,
 1450               isDiscrete: isDiscrete,
 1451               labelPainter: _labelPainter,
 1452               parentBox: this,
 1453               sliderTheme: _sliderTheme,
 1454               textDirection: _textDirection,
 1455               value: _value,
 1456               textScaleFactor: textScaleFactor,
 1457               sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
 1458             );
 1459           }
 1460         };
 1461       }
 1462     }
 1463 
 1464     _sliderTheme.thumbShape.paint(
 1465       context,
 1466       thumbCenter,
 1467       activationAnimation: _overlayAnimation,
 1468       enableAnimation: _enableAnimation,
 1469       isDiscrete: isDiscrete,
 1470       labelPainter: _labelPainter,
 1471       parentBox: this,
 1472       sliderTheme: _sliderTheme,
 1473       textDirection: _textDirection,
 1474       sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
 1475       value: _value,
 1476     );
 1477   }
 1478 
 1479   @override
 1480   void describeSemanticsConfiguration(SemanticsConfiguration config) {
 1481     super.describeSemanticsConfiguration(config);
 1482 
 1483     // The Slider widget has its own Focus widget with semantics information,
 1484     // and we want that semantics node to collect the semantics information here
 1485     // so that it's all in the same node: otherwise Talkback sees that the node
 1486     // has focusable children, and it won't focus the Slider's Focus widget
 1487     // because it thinks the Focus widget's node doesn't have anything to say
 1488     // (which it doesn't, but this child does). Aggregating the semantic
 1489     // information into one node means that Talkback will recognize that it has
 1490     // something to say and focus it when it receives keyboard focus.
 1491     // (See https://github.com/flutter/flutter/issues/57038 for context).
 1492     config.isSemanticBoundary = false;
 1493 
 1494     config.isEnabled = isInteractive;
 1495     config.textDirection = textDirection;
 1496     if (isInteractive) {
 1497       config.onIncrease = increaseAction;
 1498       config.onDecrease = decreaseAction;
 1499     }
 1500     config.label = _label ?? '';
 1501     if (semanticFormatterCallback != null) {
 1502       config.value = semanticFormatterCallback(_state._lerp(value));
 1503       config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double));
 1504       config.decreasedValue = semanticFormatterCallback(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0) as double));
 1505     } else {
 1506       config.value = '${(value * 100).round()}%';
 1507       config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
 1508       config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
 1509     }
 1510   }
 1511 
 1512   double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _adjustmentUnit;
 1513 
 1514   void increaseAction() {
 1515     if (isInteractive) {
 1516       onChanged((value + _semanticActionUnit).clamp(0.0, 1.0) as double);
 1517     }
 1518   }
 1519 
 1520   void decreaseAction() {
 1521     if (isInteractive) {
 1522       onChanged((value - _semanticActionUnit).clamp(0.0, 1.0) as double);
 1523     }
 1524   }
 1525 }
 1526 
 1527 class _AdjustSliderIntent extends Intent {
 1528   const _AdjustSliderIntent({
 1529     @required this.type
 1530   });
 1531 
 1532   const _AdjustSliderIntent.right() : type = _SliderAdjustmentType.right;
 1533 
 1534   const _AdjustSliderIntent.left() : type = _SliderAdjustmentType.left;
 1535 
 1536   const _AdjustSliderIntent.up() : type = _SliderAdjustmentType.up;
 1537 
 1538   const _AdjustSliderIntent.down() : type = _SliderAdjustmentType.down;
 1539 
 1540   final _SliderAdjustmentType type;
 1541 }
 1542 
 1543 enum _SliderAdjustmentType {
 1544   right,
 1545   left,
 1546   up,
 1547   down,
 1548 }
 1549 
 1550 class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
 1551   const _ValueIndicatorRenderObjectWidget({
 1552     this.state,
 1553   });
 1554 
 1555   final _SliderState state;
 1556 
 1557   @override
 1558   _RenderValueIndicator createRenderObject(BuildContext context) {
 1559     return _RenderValueIndicator(
 1560       state: state,
 1561     );
 1562   }
 1563   @override
 1564   void updateRenderObject(BuildContext context, _RenderValueIndicator renderObject) {
 1565     renderObject._state = state;
 1566   }
 1567 }
 1568 
 1569 class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
 1570   _RenderValueIndicator({
 1571     _SliderState state,
 1572   }) : _state = state {
 1573     _valueIndicatorAnimation = CurvedAnimation(
 1574       parent: _state.valueIndicatorController,
 1575       curve: Curves.fastOutSlowIn,
 1576     );
 1577   }
 1578   Animation<double> _valueIndicatorAnimation;
 1579   _SliderState _state;
 1580 
 1581   @override
 1582   bool get sizedByParent => true;
 1583 
 1584   @override
 1585   void attach(PipelineOwner owner) {
 1586     super.attach(owner);
 1587     _valueIndicatorAnimation.addListener(markNeedsPaint);
 1588     _state.positionController.addListener(markNeedsPaint);
 1589   }
 1590 
 1591   @override
 1592   void detach() {
 1593     _valueIndicatorAnimation.removeListener(markNeedsPaint);
 1594     _state.positionController.removeListener(markNeedsPaint);
 1595     super.detach();
 1596   }
 1597 
 1598   @override
 1599   void paint(PaintingContext context, Offset offset) {
 1600     if (_state.paintValueIndicator != null) {
 1601       _state.paintValueIndicator(context, offset);
 1602     }
 1603   }
 1604 }