title: How to change CupertinoSwitch inactive state color in Flutter
body: <p>my question is, how I can change inactive color of CupertinoSwitch in Flutter, with normal Switch it's fairly easy, because we have property for that, but on CupertinoSwitch (iOS) we do not have these properties.</p>
  
  <p>EDIT:</p>
  
  <p>Here is custom class for now, to achieve that.</p>
  
  <pre><code>import 'dart:ui';
  
  import 'package:flutter/cupertino.dart';
  import 'package:flutter/foundation.dart';
  import 'package:flutter/gestures.dart';
  import 'package:flutter/rendering.dart';
  import 'package:flutter/services.dart';
  
  class MyCupertinoSwitch extends StatefulWidget {
    /// Creates an iOS-style switch.
    ///
    /// The [value] parameter must not be null.
    /// The [dragStartBehavior] parameter defaults to [DragStartBehavior.start] and must not be null.
    const MyCupertinoSwitch({
      Key key,
      @required this.value,
      @required this.onChanged,
      this.activeColor,
      this.trackColor,
      this.dragStartBehavior = DragStartBehavior.start,
    })  : assert(value != null),
          assert(dragStartBehavior != null),
          super(key: key);
  
    /// Whether this switch is on or off.
    ///
    /// Must not be null.
    final bool value;
  
    /// Called when the user toggles with switch on or off.
    ///
    /// The switch passes the new value to the callback but does not actually
    /// change state until the parent widget rebuilds the switch with the new
    /// value.
    ///
    /// If null, the switch will be displayed as disabled, which has a reduced opacity.
    ///
    /// The callback provided to onChanged should update the state of the parent
    /// [StatefulWidget] using the [State.setState] method, so that the parent
    /// gets rebuilt; for example:
    ///
    /// ```dart
    /// CupertinoSwitch(
    ///   value: _giveVerse,
    ///   onChanged: (bool newValue) {
    ///     setState(() {
    ///       _giveVerse = newValue;
    ///     });
    ///   },
    /// )
    /// ```
    final ValueChanged&lt;bool&gt; onChanged;
  
    /// The color to use when this switch is on.
    ///
    /// Defaults to [CupertinoColors.activeGreen] when null and ignores the
    /// [CupertinoTheme] in accordance to native iOS behavior.
    final Color activeColor;
  
    /// The color to use when this switch is off.
    ///
    /// Defaults to [CupertinoColors.lightBackgroundGray] when null and ignores the
    /// [CupertinoTheme] in accordance to native iOS behavior.
    final Color trackColor;
  
    /// {@template flutter.cupertino.switch.dragStartBehavior}
    /// Determines the way that drag start behavior is handled.
    ///
    /// If set to [DragStartBehavior.start], the drag behavior used to move the
    /// switch from on to off will begin upon the detection of a drag gesture. If
    /// set to [DragStartBehavior.down] it will begin when a down event is first
    /// detected.
    ///
    /// In general, setting this to [DragStartBehavior.start] will make drag
    /// animation smoother and setting it to [DragStartBehavior.down] will make
    /// drag behavior feel slightly more reactive.
    ///
    /// By default, the drag start behavior is [DragStartBehavior.start].
    ///
    /// See also:
    ///
    ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
    ///    the different behaviors.
    ///
    /// {@endtemplate}
    final DragStartBehavior dragStartBehavior;
  
    @override
    _MyCupertinoSwitchState createState() =&gt; _MyCupertinoSwitchState();
  
    @override
    void debugFillProperties(DiagnosticPropertiesBuilder properties) {
      super.debugFillProperties(properties);
      properties.add(FlagProperty('value',
          value: value, ifTrue: 'on', ifFalse: 'off', showName: true));
      properties.add(ObjectFlagProperty&lt;ValueChanged&lt;bool&gt;&gt;(
          'onChanged', onChanged,
          ifNull: 'disabled'));
    }
  }
  
  class _MyCupertinoSwitchState extends State&lt;MyCupertinoSwitch&gt;
      with TickerProviderStateMixin {
    @override
    Widget build(BuildContext context) {
      return Opacity(
        opacity:
            widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0,
        child: _CupertinoSwitchRenderObjectWidget(
          value: widget.value,
          activeColor: widget.activeColor ?? CupertinoColors.activeGreen,
          trackColor: widget.trackColor ?? CupertinoColors.lightBackgroundGray,
          onChanged: widget.onChanged,
          vsync: this,
          dragStartBehavior: widget.dragStartBehavior,
        ),
      );
    }
  }
  
  class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
    const _CupertinoSwitchRenderObjectWidget({
      Key key,
      this.value,
      this.activeColor,
      this.trackColor,
      this.onChanged,
      this.vsync,
      this.dragStartBehavior = DragStartBehavior.start,
    }) : super(key: key);
  
    final bool value;
    final Color activeColor;
    final Color trackColor;
    final ValueChanged&lt;bool&gt; onChanged;
    final TickerProvider vsync;
    final DragStartBehavior dragStartBehavior;
  
    @override
    _RenderCupertinoSwitch createRenderObject(BuildContext context) {
      return _RenderCupertinoSwitch(
        value: value,
        activeColor: activeColor,
        trackColor: trackColor,
        onChanged: onChanged,
        textDirection: Directionality.of(context),
        vsync: vsync,
        dragStartBehavior: dragStartBehavior,
      );
    }
  
    @override
    void updateRenderObject(
        BuildContext context, _RenderCupertinoSwitch renderObject) {
      renderObject
        ..value = value
        ..activeColor = activeColor
        ..trackColor = trackColor
        ..onChanged = onChanged
        ..textDirection = Directionality.of(context)
        ..vsync = vsync
        ..dragStartBehavior = dragStartBehavior;
    }
  }
  
  const double _kTrackWidth = 51.0;
  const double _kTrackHeight = 31.0;
  const double _kTrackRadius = _kTrackHeight / 2.0;
  const double _kTrackInnerStart = _kTrackHeight / 2.0;
  const double _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart;
  const double _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart;
  const double _kSwitchWidth = 59.0;
  const double _kSwitchHeight = 39.0;
  // Opacity of a disabled switch, as eye-balled from iOS Simulator on Mac.
  const double _kCupertinoSwitchDisabledOpacity = 0.5;
  
  const Duration _kReactionDuration = Duration(milliseconds: 300);
  const Duration _kToggleDuration = Duration(milliseconds: 200);
  
  class _RenderCupertinoSwitch extends RenderConstrainedBox {
    _RenderCupertinoSwitch({
      @required bool value,
      @required Color activeColor,
      @required Color trackColor,
      ValueChanged&lt;bool&gt; onChanged,
      @required TextDirection textDirection,
      @required TickerProvider vsync,
      DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    })  : assert(value != null),
          assert(activeColor != null),
          assert(vsync != null),
          _value = value,
          _activeColor = activeColor,
          _trackColor = trackColor,
          _onChanged = onChanged,
          _textDirection = textDirection,
          _vsync = vsync,
          super(
              additionalConstraints: const BoxConstraints.tightFor(
                  width: _kSwitchWidth, height: _kSwitchHeight)) {
      _tap = TapGestureRecognizer()
        ..onTapDown = _handleTapDown
        ..onTap = _handleTap
        ..onTapUp = _handleTapUp
        ..onTapCancel = _handleTapCancel;
      _drag = HorizontalDragGestureRecognizer()
        ..onStart = _handleDragStart
        ..onUpdate = _handleDragUpdate
        ..onEnd = _handleDragEnd
        ..dragStartBehavior = dragStartBehavior;
      _positionController = AnimationController(
        duration: _kToggleDuration,
        value: value ? 1.0 : 0.0,
        vsync: vsync,
      );
      _position = CurvedAnimation(
        parent: _positionController,
        curve: Curves.linear,
      )
        ..addListener(markNeedsPaint)
        ..addStatusListener(_handlePositionStateChanged);
      _reactionController = AnimationController(
        duration: _kReactionDuration,
        vsync: vsync,
      );
      _reaction = CurvedAnimation(
        parent: _reactionController,
        curve: Curves.ease,
      )..addListener(markNeedsPaint);
    }
  
    AnimationController _positionController;
    CurvedAnimation _position;
  
    AnimationController _reactionController;
    Animation&lt;double&gt; _reaction;
  
    bool get value =&gt; _value;
    bool _value;
  
    set value(bool value) {
      assert(value != null);
      if (value == _value) return;
      _value = value;
      markNeedsSemanticsUpdate();
      _position
        ..curve = Curves.ease
        ..reverseCurve = Curves.ease.flipped;
      if (value)
        _positionController.forward();
      else
        _positionController.reverse();
    }
  
    TickerProvider get vsync =&gt; _vsync;
    TickerProvider _vsync;
  
    set vsync(TickerProvider value) {
      assert(value != null);
      if (value == _vsync) return;
      _vsync = value;
      _positionController.resync(vsync);
      _reactionController.resync(vsync);
    }
  
    Color get activeColor =&gt; _activeColor;
  
    Color get trackColor =&gt; _trackColor;
    Color _activeColor;
    Color _trackColor;
  
    set activeColor(Color value) {
      assert(value != null);
      if (value == _activeColor) return;
      _activeColor = value;
      markNeedsPaint();
    }
  
    set trackColor(Color value) {
      assert(value != null);
      if (value == _trackColor) return;
      _trackColor = value;
      markNeedsPaint();
    }
  
    ValueChanged&lt;bool&gt; get onChanged =&gt; _onChanged;
    ValueChanged&lt;bool&gt; _onChanged;
  
    set onChanged(ValueChanged&lt;bool&gt; value) {
      if (value == _onChanged) return;
      final bool wasInteractive = isInteractive;
      _onChanged = value;
      if (wasInteractive != isInteractive) {
        markNeedsPaint();
        markNeedsSemanticsUpdate();
      }
    }
  
    TextDirection get textDirection =&gt; _textDirection;
    TextDirection _textDirection;
  
    set textDirection(TextDirection value) {
      assert(value != null);
      if (_textDirection == value) return;
      _textDirection = value;
      markNeedsPaint();
    }
  
    DragStartBehavior get dragStartBehavior =&gt; _drag.dragStartBehavior;
  
    set dragStartBehavior(DragStartBehavior value) {
      assert(value != null);
      if (_drag.dragStartBehavior == value) return;
      _drag.dragStartBehavior = value;
    }
  
    bool get isInteractive =&gt; onChanged != null;
  
    TapGestureRecognizer _tap;
    HorizontalDragGestureRecognizer _drag;
  
    @override
    void attach(PipelineOwner owner) {
      super.attach(owner);
      if (value)
        _positionController.forward();
      else
        _positionController.reverse();
      if (isInteractive) {
        switch (_reactionController.status) {
          case AnimationStatus.forward:
            _reactionController.forward();
            break;
          case AnimationStatus.reverse:
            _reactionController.reverse();
            break;
          case AnimationStatus.dismissed:
          case AnimationStatus.completed:
            // nothing to do
            break;
        }
      }
    }
  
    @override
    void detach() {
      _positionController.stop();
      _reactionController.stop();
      super.detach();
    }
  
    void _handlePositionStateChanged(AnimationStatus status) {
      if (isInteractive) {
        if (status == AnimationStatus.completed &amp;&amp; !_value)
          onChanged(true);
        else if (status == AnimationStatus.dismissed &amp;&amp; _value) onChanged(false);
      }
    }
  
    void _handleTapDown(TapDownDetails details) {
      if (isInteractive) _reactionController.forward();
    }
  
    void _handleTap() {
      if (isInteractive) {
        onChanged(!_value);
        _emitVibration();
      }
    }
  
    void _handleTapUp(TapUpDetails details) {
      if (isInteractive) _reactionController.reverse();
    }
  
    void _handleTapCancel() {
      if (isInteractive) _reactionController.reverse();
    }
  
    void _handleDragStart(DragStartDetails details) {
      if (isInteractive) {
        _reactionController.forward();
        _emitVibration();
      }
    }
  
    void _handleDragUpdate(DragUpdateDetails details) {
      if (isInteractive) {
        _position
          ..curve = null
          ..reverseCurve = null;
        final double delta = details.primaryDelta / _kTrackInnerLength;
        switch (textDirection) {
          case TextDirection.rtl:
            _positionController.value -= delta;
            break;
          case TextDirection.ltr:
            _positionController.value += delta;
            break;
        }
      }
    }
  
    void _handleDragEnd(DragEndDetails details) {
      if (_position.value &gt;= 0.5)
        _positionController.forward();
      else
        _positionController.reverse();
      _reactionController.reverse();
    }
  
    void _emitVibration() {
      switch (defaultTargetPlatform) {
        case TargetPlatform.iOS:
          HapticFeedback.lightImpact();
          break;
        case TargetPlatform.fuchsia:
        case TargetPlatform.android:
          break;
      }
    }
  
    @override
    bool hitTestSelf(Offset position) =&gt; true;
  
    @override
    void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
      assert(debugHandleEvent(event, entry));
      if (event is PointerDownEvent &amp;&amp; isInteractive) {
        _drag.addPointer(event);
        _tap.addPointer(event);
      }
    }
  
    @override
    void describeSemanticsConfiguration(SemanticsConfiguration config) {
      super.describeSemanticsConfiguration(config);
  
      if (isInteractive) config.onTap = _handleTap;
  
      config.isEnabled = isInteractive;
      config.isToggled = _value;
    }
  
    final CupertinoThumbPainter _thumbPainter = CupertinoThumbPainter();
  
    @override
    void paint(PaintingContext context, Offset offset) {
      final Canvas canvas = context.canvas;
  
      final double currentValue = _position.value;
      final double currentReactionValue = _reaction.value;
  
      double visualPosition;
      switch (textDirection) {
        case TextDirection.rtl:
          visualPosition = 1.0 - currentValue;
          break;
        case TextDirection.ltr:
          visualPosition = currentValue;
          break;
      }
  
      final Paint paint = Paint()
        ..color = Color.lerp(trackColor, activeColor, currentValue);
  
      final Rect trackRect = Rect.fromLTWH(
        offset.dx + (size.width - _kTrackWidth) / 2.0,
        offset.dy + (size.height - _kTrackHeight) / 2.0,
        _kTrackWidth,
        _kTrackHeight,
      );
      final RRect trackRRect = RRect.fromRectAndRadius(
          trackRect, const Radius.circular(_kTrackRadius));
      canvas.drawRRect(trackRRect, paint);
  
      final double currentThumbExtension =
          CupertinoThumbPainter.extension * currentReactionValue;
      final double thumbLeft = lerpDouble(
        trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
        trackRect.left +
            _kTrackInnerEnd -
            CupertinoThumbPainter.radius -
            currentThumbExtension,
        visualPosition,
      );
      final double thumbRight = lerpDouble(
        trackRect.left +
            _kTrackInnerStart +
            CupertinoThumbPainter.radius +
            currentThumbExtension,
        trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius,
        visualPosition,
      );
      final double thumbCenterY = offset.dy + size.height / 2.0;
      final Rect thumbBounds = Rect.fromLTRB(
        thumbLeft,
        thumbCenterY - CupertinoThumbPainter.radius,
        thumbRight,
        thumbCenterY + CupertinoThumbPainter.radius,
      );
  
      context
          .pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect,
              (PaintingContext innerContext, Offset offset) {
        _thumbPainter.paint(innerContext.canvas, thumbBounds);
      });
    }
  
    @override
    void debugFillProperties(DiagnosticPropertiesBuilder description) {
      super.debugFillProperties(description);
      description.add(FlagProperty('value',
          value: value, ifTrue: 'checked', ifFalse: 'unchecked', showName: true));
      description.add(FlagProperty('isInteractive',
          value: isInteractive,
          ifTrue: 'enabled',
          ifFalse: 'disabled',
          showName: true,
          defaultValue: true));
    }
  }
  </code></pre>
  
  <p>Let's see the response from flutter dev team on issue raised below.</p>