// 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 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'constants.dart'; import 'debug.dart'; import 'material_state.dart'; import 'shadows.dart'; import 'switch_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; import 'toggleable.dart'; // Examples can assume: // bool _giveVerse = true; // late StateSetter setState; const double _kSwitchMinSize = kMinInteractiveDimension - 8.0; enum _SwitchType { material, adaptive } /// A Material Design switch. /// /// Used to toggle the on/off state of a single setting. /// /// The switch itself does not maintain any state. Instead, when the state of /// the switch changes, the widget calls the [onChanged] callback. Most widgets /// that use a switch will listen for the [onChanged] callback and rebuild the /// switch with a new [value] to update the visual appearance of the switch. /// /// If the [onChanged] callback is null, then the switch will be disabled (it /// will not respond to input). A disabled switch's thumb and track are rendered /// in shades of grey by default. The default appearance of a disabled switch /// can be overridden with [inactiveThumbColor] and [inactiveTrackColor]. /// /// Requires one of its ancestors to be a [Material] widget. /// /// Material Design 3 provides the option to add icons on the thumb of the [Switch]. /// If [ThemeData.useMaterial3] is set to true, users can use [Switch.thumbIcon] /// to add optional Icons based on the different [MaterialState]s of the [Switch]. /// /// {@tool dartpad} /// This example shows a toggleable [Switch]. When the thumb slides to the other /// side of the track, the switch is toggled between on/off. /// /// ** See code in examples/api/lib/material/switch/switch.0.dart ** /// {@end-tool} /// /// {@tool dartpad} /// This example shows how to customize [Switch] using [MaterialStateProperty] /// switch properties. /// /// ** See code in examples/api/lib/material/switch/switch.1.dart ** /// {@end-tool} /// /// {@tool dartpad} /// This example shows how to add icons on the thumb of the [Switch] using the /// [Switch.thumbIcon] property. /// /// ** See code in examples/api/lib/material/switch/switch.2.dart ** /// {@end-tool} /// /// See also: /// /// * [SwitchListTile], which combines this widget with a [ListTile] so that /// you can give the switch a label. /// * [Checkbox], another widget with similar semantics. /// * [Radio], for selecting among a set of explicit values. /// * [Slider], for selecting a value in a range. /// * [MaterialStateProperty], an interface for objects that "resolve" to /// different values depending on a widget's material state. /// * <https://material.io/design/components/selection-controls.html#switches> class Switch extends StatelessWidget { /// Creates a Material Design switch. /// /// The switch itself does not maintain any state. Instead, when the state of /// the switch changes, the widget calls the [onChanged] callback. Most widgets /// that use a switch will listen for the [onChanged] callback and rebuild the /// switch with a new [value] to update the visual appearance of the switch. /// /// The following arguments are required: /// /// * [value] determines whether this switch is on or off. /// * [onChanged] is called when the user toggles the switch on or off. const Switch({ super.key, required this.value, required this.onChanged, this.activeColor, this.activeTrackColor, this.inactiveThumbColor, this.inactiveTrackColor, this.activeThumbImage, this.onActiveThumbImageError, this.inactiveThumbImage, this.onInactiveThumbImageError, this.thumbColor, this.trackColor, this.thumbIcon, this.materialTapTargetSize, this.dragStartBehavior = DragStartBehavior.start, this.mouseCursor, this.focusColor, this.hoverColor, this.overlayColor, this.splashRadius, this.focusNode, this.onFocusChange, this.autofocus = false, }) : _switchType = _SwitchType.material, applyCupertinoTheme = false, assert(dragStartBehavior != null), assert(activeThumbImage != null || onActiveThumbImageError == null), assert(inactiveThumbImage != null || onInactiveThumbImageError == null); /// Creates an adaptive [Switch] based on whether the target platform is iOS /// or macOS, following Material design's /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html). /// /// On iOS and macOS, this constructor creates a [CupertinoSwitch], which has /// matching functionality and presentation as Material switches, and are the /// graphics expected on iOS. On other platforms, this creates a Material /// design [Switch]. /// /// If a [CupertinoSwitch] is created, the following parameters are ignored: /// [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor], /// [activeThumbImage], [onActiveThumbImageError], [inactiveThumbImage], /// [onInactiveThumbImageError], [materialTapTargetSize]. /// /// The target platform is based on the current [Theme]: [ThemeData.platform]. const Switch.adaptive({ super.key, required this.value, required this.onChanged, this.activeColor, this.activeTrackColor, this.inactiveThumbColor, this.inactiveTrackColor, this.activeThumbImage, this.onActiveThumbImageError, this.inactiveThumbImage, this.onInactiveThumbImageError, this.materialTapTargetSize, this.thumbColor, this.trackColor, this.thumbIcon, this.dragStartBehavior = DragStartBehavior.start, this.mouseCursor, this.focusColor, this.hoverColor, this.overlayColor, this.splashRadius, this.focusNode, this.onFocusChange, this.autofocus = false, this.applyCupertinoTheme, }) : assert(autofocus != null), assert(activeThumbImage != null || onActiveThumbImageError == null), assert(inactiveThumbImage != null || onInactiveThumbImageError == null), _switchType = _SwitchType.adaptive; /// Whether this switch is on or off. /// /// This property must not be null. final bool value; /// Called when the user toggles the 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. /// /// 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 /// Switch( /// value: _giveVerse, /// onChanged: (bool newValue) { /// setState(() { /// _giveVerse = newValue; /// }); /// }, /// ) /// ``` final ValueChanged<bool>? onChanged; /// The color to use when this switch is on. /// /// Defaults to [ColorScheme.secondary]. /// /// If [thumbColor] returns a non-null color in the [MaterialState.selected] /// state, it will be used instead of this color. final Color? activeColor; /// The color to use on the track when this switch is on. /// /// Defaults to [ColorScheme.secondary] with the opacity set at 50%. /// /// Ignored if this switch is created with [Switch.adaptive]. /// /// If [trackColor] returns a non-null color in the [MaterialState.selected] /// state, it will be used instead of this color. final Color? activeTrackColor; /// The color to use on the thumb when this switch is off. /// /// Defaults to the colors described in the Material design specification. /// /// Ignored if this switch is created with [Switch.adaptive]. /// /// If [thumbColor] returns a non-null color in the default state, it will be /// used instead of this color. final Color? inactiveThumbColor; /// The color to use on the track when this switch is off. /// /// Defaults to the colors described in the Material design specification. /// /// Ignored if this switch is created with [Switch.adaptive]. /// /// If [trackColor] returns a non-null color in the default state, it will be /// used instead of this color. final Color? inactiveTrackColor; /// An image to use on the thumb of this switch when the switch is on. /// /// Ignored if this switch is created with [Switch.adaptive]. final ImageProvider? activeThumbImage; /// An optional error callback for errors emitted when loading /// [activeThumbImage]. final ImageErrorListener? onActiveThumbImageError; /// An image to use on the thumb of this switch when the switch is off. /// /// Ignored if this switch is created with [Switch.adaptive]. final ImageProvider? inactiveThumbImage; /// An optional error callback for errors emitted when loading /// [inactiveThumbImage]. final ImageErrorListener? onInactiveThumbImageError; /// {@template flutter.material.switch.thumbColor} /// The color of this [Switch]'s thumb. /// /// Resolved in the following states: /// * [MaterialState.selected]. /// * [MaterialState.hovered]. /// * [MaterialState.focused]. /// * [MaterialState.disabled]. /// /// {@tool snippet} /// This example resolves the [thumbColor] based on the current /// [MaterialState] of the [Switch], providing a different [Color] when it is /// [MaterialState.disabled]. /// /// ```dart /// Switch( /// value: true, /// onChanged: (_) => true, /// thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { /// if (states.contains(MaterialState.disabled)) { /// return Colors.orange.withOpacity(.48); /// } /// return Colors.orange; /// }), /// ) /// ``` /// {@end-tool} /// {@endtemplate} /// /// If null, then the value of [activeColor] is used in the selected /// state and [inactiveThumbColor] in the default state. If that is also null, /// then the value of [SwitchThemeData.thumbColor] is used. If that is also /// null, then the following colors are used: /// /// | State | Light theme | Dark theme | /// |----------|-----------------------------------|-----------------------------------| /// | Default | `Colors.grey.shade50` | `Colors.grey.shade400` | /// | Selected | [ColorScheme.secondary] | [ColorScheme.secondary] | /// | Disabled | `Colors.grey.shade400` | `Colors.grey.shade800` | final MaterialStateProperty<Color?>? thumbColor; /// {@template flutter.material.switch.trackColor} /// The color of this [Switch]'s track. /// /// Resolved in the following states: /// * [MaterialState.selected]. /// * [MaterialState.hovered]. /// * [MaterialState.focused]. /// * [MaterialState.disabled]. /// /// {@tool snippet} /// This example resolves the [trackColor] based on the current /// [MaterialState] of the [Switch], providing a different [Color] when it is /// [MaterialState.disabled]. /// /// ```dart /// Switch( /// value: true, /// onChanged: (_) => true, /// thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { /// if (states.contains(MaterialState.disabled)) { /// return Colors.orange.withOpacity(.48); /// } /// return Colors.orange; /// }), /// ) /// ``` /// {@end-tool} /// {@endtemplate} /// /// If null, then the value of [activeTrackColor] is used in the selected /// state and [inactiveTrackColor] in the default state. If that is also null, /// then the value of [SwitchThemeData.trackColor] is used. If that is also /// null, then the following colors are used: /// /// | State | Light theme | Dark theme | /// |----------|---------------------------------|---------------------------------| /// | Default | `Color(0x52000000)` | `Colors.white30` | /// | Selected | [activeColor] with alpha `0x80` | [activeColor] with alpha `0x80` | /// | Disabled | `Colors.black12` | `Colors.white10` | final MaterialStateProperty<Color?>? trackColor; /// {@template flutter.material.switch.thumbIcon} /// The icon to use on the thumb of this switch /// /// Resolved in the following states: /// * [MaterialState.selected]. /// * [MaterialState.hovered]. /// * [MaterialState.focused]. /// * [MaterialState.disabled]. /// /// {@tool snippet} /// This example resolves the [thumbIcon] based on the current /// [MaterialState] of the [Switch], providing a different [Icon] when it is /// [MaterialState.disabled]. /// /// ```dart /// Switch( /// value: true, /// onChanged: (_) => true, /// thumbIcon: MaterialStateProperty.resolveWith<Icon?>((Set<MaterialState> states) { /// if (states.contains(MaterialState.disabled)) { /// return const Icon(Icons.close); /// } /// return null; // All other states will use the default thumbIcon. /// }), /// ) /// ``` /// {@end-tool} /// {@endtemplate} /// /// If null, then the value of [SwitchThemeData.thumbIcon] is used. If this is also null, /// then the [Switch] does not have any icons on the thumb. final MaterialStateProperty<Icon?>? thumbIcon; /// {@template flutter.material.switch.materialTapTargetSize} /// Configures the minimum size of the tap target. /// {@endtemplate} /// /// If null, then the value of [SwitchThemeData.materialTapTargetSize] is /// used. If that is also null, then the value of /// [ThemeData.materialTapTargetSize] is used. /// /// See also: /// /// * [MaterialTapTargetSize], for a description of how this affects tap targets. final MaterialTapTargetSize? materialTapTargetSize; final _SwitchType _switchType; /// {@macro flutter.cupertino.CupertinoSwitch.applyTheme} final bool? applyCupertinoTheme; /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior} final DragStartBehavior dragStartBehavior; /// {@template flutter.material.switch.mouseCursor} /// The cursor for a mouse pointer when it enters or is hovering over the /// widget. /// /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>], /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s: /// /// * [MaterialState.selected]. /// * [MaterialState.hovered]. /// * [MaterialState.focused]. /// * [MaterialState.disabled]. /// {@endtemplate} /// /// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that /// is also null, then [MaterialStateMouseCursor.clickable] is used. /// /// See also: /// /// * [MaterialStateMouseCursor], a [MouseCursor] that implements /// `MaterialStateProperty` which is used in APIs that need to accept /// either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>]. final MouseCursor? mouseCursor; /// The color for the button's [Material] when it has the input focus. /// /// If [overlayColor] returns a non-null color in the [MaterialState.focused] /// state, it will be used instead. /// /// If null, then the value of [SwitchThemeData.overlayColor] is used in the /// focused state. If that is also null, then the value of /// [ThemeData.focusColor] is used. final Color? focusColor; /// The color for the button's [Material] when a pointer is hovering over it. /// /// If [overlayColor] returns a non-null color in the [MaterialState.hovered] /// state, it will be used instead. /// /// If null, then the value of [SwitchThemeData.overlayColor] is used in the /// hovered state. If that is also null, then the value of /// [ThemeData.hoverColor] is used. final Color? hoverColor; /// {@template flutter.material.switch.overlayColor} /// The color for the switch's [Material]. /// /// Resolves in the following states: /// * [MaterialState.pressed]. /// * [MaterialState.selected]. /// * [MaterialState.hovered]. /// * [MaterialState.focused]. /// {@endtemplate} /// /// If null, then the value of [activeColor] with alpha /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the /// pressed, focused and hovered state. If that is also null, /// the value of [SwitchThemeData.overlayColor] is used. If that is /// also null, then the value of [ColorScheme.secondary] with alpha /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor] /// is used in the pressed, focused and hovered state. final MaterialStateProperty<Color?>? overlayColor; /// {@template flutter.material.switch.splashRadius} /// The splash radius of the circular [Material] ink response. /// {@endtemplate} /// /// If null, then the value of [SwitchThemeData.splashRadius] is used. If that /// is also null, then [kRadialReactionRadius] is used. final double? splashRadius; /// {@macro flutter.widgets.Focus.focusNode} final FocusNode? focusNode; /// {@macro flutter.material.inkwell.onFocusChange} final ValueChanged<bool>? onFocusChange; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; Size _getSwitchSize(BuildContext context) { final ThemeData theme = Theme.of(context); final SwitchThemeData switchTheme = SwitchTheme.of(context); final _SwitchConfig switchConfig = theme.useMaterial3 ? _SwitchConfigM3(context) : _SwitchConfigM2(); final MaterialTapTargetSize effectiveMaterialTapTargetSize = materialTapTargetSize ?? switchTheme.materialTapTargetSize ?? theme.materialTapTargetSize; switch (effectiveMaterialTapTargetSize) { case MaterialTapTargetSize.padded: return Size(switchConfig.switchWidth, switchConfig.switchHeight); case MaterialTapTargetSize.shrinkWrap: return Size(switchConfig.switchWidth, switchConfig.switchHeightCollapsed); } } Widget _buildCupertinoSwitch(BuildContext context) { final Size size = _getSwitchSize(context); return Focus( focusNode: focusNode, onFocusChange: onFocusChange, autofocus: autofocus, child: Container( width: size.width, // Same size as the Material switch. height: size.height, alignment: Alignment.center, child: CupertinoSwitch( dragStartBehavior: dragStartBehavior, value: value, onChanged: onChanged, activeColor: activeColor, trackColor: inactiveTrackColor, applyTheme: applyCupertinoTheme, ), ), ); } Widget _buildMaterialSwitch(BuildContext context) { return _MaterialSwitch( value: value, onChanged: onChanged, size: _getSwitchSize(context), activeColor: activeColor, activeTrackColor: activeTrackColor, inactiveThumbColor: inactiveThumbColor, inactiveTrackColor: inactiveTrackColor, activeThumbImage: activeThumbImage, onActiveThumbImageError: onActiveThumbImageError, inactiveThumbImage: inactiveThumbImage, onInactiveThumbImageError: onInactiveThumbImageError, thumbColor: thumbColor, trackColor: trackColor, thumbIcon: thumbIcon, materialTapTargetSize: materialTapTargetSize, dragStartBehavior: dragStartBehavior, mouseCursor: mouseCursor, focusColor: focusColor, hoverColor: hoverColor, overlayColor: overlayColor, splashRadius: splashRadius, focusNode: focusNode, onFocusChange: onFocusChange, autofocus: autofocus, ); } @override Widget build(BuildContext context) { switch (_switchType) { case _SwitchType.material: return _buildMaterialSwitch(context); case _SwitchType.adaptive: { final ThemeData theme = Theme.of(context); assert(theme.platform != null); switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: return _buildMaterialSwitch(context); case TargetPlatform.iOS: case TargetPlatform.macOS: return _buildCupertinoSwitch(context); } } } } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(FlagProperty('value', value: value, ifTrue: 'on', ifFalse: 'off', showName: true)); properties.add(ObjectFlagProperty<ValueChanged<bool>>('onChanged', onChanged, ifNull: 'disabled')); } } class _MaterialSwitch extends StatefulWidget { const _MaterialSwitch({ required this.value, required this.onChanged, required this.size, this.activeColor, this.activeTrackColor, this.inactiveThumbColor, this.inactiveTrackColor, this.activeThumbImage, this.onActiveThumbImageError, this.inactiveThumbImage, this.onInactiveThumbImageError, this.thumbColor, this.trackColor, this.thumbIcon, this.materialTapTargetSize, this.dragStartBehavior = DragStartBehavior.start, this.mouseCursor, this.focusColor, this.hoverColor, this.overlayColor, this.splashRadius, this.focusNode, this.onFocusChange, this.autofocus = false, }) : assert(dragStartBehavior != null), assert(activeThumbImage != null || onActiveThumbImageError == null), assert(inactiveThumbImage != null || onInactiveThumbImageError == null); final bool value; final ValueChanged<bool>? onChanged; final Color? activeColor; final Color? activeTrackColor; final Color? inactiveThumbColor; final Color? inactiveTrackColor; final ImageProvider? activeThumbImage; final ImageErrorListener? onActiveThumbImageError; final ImageProvider? inactiveThumbImage; final ImageErrorListener? onInactiveThumbImageError; final MaterialStateProperty<Color?>? thumbColor; final MaterialStateProperty<Color?>? trackColor; final MaterialStateProperty<Icon?>? thumbIcon; final MaterialTapTargetSize? materialTapTargetSize; final DragStartBehavior dragStartBehavior; final MouseCursor? mouseCursor; final Color? focusColor; final Color? hoverColor; final MaterialStateProperty<Color?>? overlayColor; final double? splashRadius; final FocusNode? focusNode; final Function(bool)? onFocusChange; final bool autofocus; final Size size; @override State<StatefulWidget> createState() => _MaterialSwitchState(); } class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderStateMixin, ToggleableStateMixin { final _SwitchPainter _painter = _SwitchPainter(); @override void didUpdateWidget(_MaterialSwitch oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.value != widget.value) { // During a drag we may have modified the curve, reset it if its possible // to do without visual discontinuation. if (position.value == 0.0 || position.value == 1.0) { if (Theme.of(context).useMaterial3) { position ..curve = Curves.easeOutBack ..reverseCurve = Curves.easeOutBack.flipped; } else { position ..curve = Curves.easeIn ..reverseCurve = Curves.easeOut; } } animateToValue(); } } @override void dispose() { _painter.dispose(); super.dispose(); } @override ValueChanged<bool?>? get onChanged => widget.onChanged != null ? _handleChanged : null; @override bool get tristate => false; @override bool? get value => widget.value; MaterialStateProperty<Color?> get _widgetThumbColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return widget.inactiveThumbColor; } if (states.contains(MaterialState.selected)) { return widget.activeColor; } return widget.inactiveThumbColor; }); } MaterialStateProperty<Color?> get _widgetTrackColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { return widget.activeTrackColor; } return widget.inactiveTrackColor; }); } double get _trackInnerLength => widget.size.width - _kSwitchMinSize; void _handleDragStart(DragStartDetails details) { if (isInteractive) { reactionController.forward(); } } void _handleDragUpdate(DragUpdateDetails details) { if (isInteractive) { position ..curve = Curves.linear ..reverseCurve = null; final double delta = details.primaryDelta! / _trackInnerLength; switch (Directionality.of(context)) { case TextDirection.rtl: positionController.value -= delta; break; case TextDirection.ltr: positionController.value += delta; break; } } } bool _needsPositionAnimation = false; void _handleDragEnd(DragEndDetails details) { if (position.value >= 0.5 != widget.value) { widget.onChanged?.call(!widget.value); // Wait with finishing the animation until widget.value has changed to // !widget.value as part of the widget.onChanged call above. setState(() { _needsPositionAnimation = true; }); } else { animateToValue(); } reactionController.reverse(); } void _handleChanged(bool? value) { assert(value != null); assert(widget.onChanged != null); widget.onChanged?.call(value!); } @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); if (_needsPositionAnimation) { _needsPositionAnimation = false; animateToValue(); } final ThemeData theme = Theme.of(context); final SwitchThemeData switchTheme = SwitchTheme.of(context); final _SwitchConfig switchConfig = theme.useMaterial3 ? _SwitchConfigM3(context) : _SwitchConfigM2(); final SwitchThemeData defaults = theme.useMaterial3 ? _SwitchDefaultsM3(context) : _SwitchDefaultsM2(context); positionController.duration = Duration(milliseconds: switchConfig.toggleDuration); // Colors need to be resolved in selected and non selected states separately // so that they can be lerped between. final Set<MaterialState> activeStates = states..add(MaterialState.selected); final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected); final Color? activeThumbColor = widget.thumbColor?.resolve(activeStates) ?? _widgetThumbColor.resolve(activeStates) ?? switchTheme.thumbColor?.resolve(activeStates); final Color effectiveActiveThumbColor = activeThumbColor ?? defaults.thumbColor!.resolve(activeStates)!; final Color? inactiveThumbColor = widget.thumbColor?.resolve(inactiveStates) ?? _widgetThumbColor.resolve(inactiveStates) ?? switchTheme.thumbColor?.resolve(inactiveStates); final Color effectiveInactiveThumbColor = inactiveThumbColor ?? defaults.thumbColor!.resolve(inactiveStates)!; final Color effectiveActiveTrackColor = widget.trackColor?.resolve(activeStates) ?? _widgetTrackColor.resolve(activeStates) ?? switchTheme.trackColor?.resolve(activeStates) ?? _widgetThumbColor.resolve(activeStates)?.withAlpha(0x80) ?? defaults.trackColor!.resolve(activeStates)!; final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates) ?? _widgetTrackColor.resolve(inactiveStates) ?? switchTheme.trackColor?.resolve(inactiveStates) ?? defaults.trackColor!.resolve(inactiveStates)!; final Color? effectiveInactiveTrackOutlineColor = switchConfig.trackOutlineColor?.resolve(inactiveStates); final Icon? effectiveActiveIcon = widget.thumbIcon?.resolve(activeStates) ?? switchTheme.thumbIcon?.resolve(activeStates); final Icon? effectiveInactiveIcon = widget.thumbIcon?.resolve(inactiveStates) ?? switchTheme.thumbIcon?.resolve(inactiveStates); final Color effectiveActiveIconColor = effectiveActiveIcon?.color ?? switchConfig.iconColor.resolve(activeStates); final Color effectiveInactiveIconColor = effectiveInactiveIcon?.color ?? switchConfig.iconColor.resolve(inactiveStates); final Set<MaterialState> focusedStates = states..add(MaterialState.focused); final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates) ?? widget.focusColor ?? switchTheme.overlayColor?.resolve(focusedStates) ?? defaults.overlayColor!.resolve(focusedStates)!; final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered); final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates) ?? widget.hoverColor ?? switchTheme.overlayColor?.resolve(hoveredStates) ?? defaults.overlayColor!.resolve(hoveredStates)!; final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed); final Color effectiveActivePressedThumbColor = widget.thumbColor?.resolve(activePressedStates) ?? _widgetThumbColor.resolve(activePressedStates) ?? switchTheme.thumbColor?.resolve(activePressedStates) ?? defaults.thumbColor!.resolve(activePressedStates)!; final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates) ?? switchTheme.overlayColor?.resolve(activePressedStates) ?? activeThumbColor?.withAlpha(kRadialReactionAlpha) ?? defaults.overlayColor!.resolve(activePressedStates)!; final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed); final Color effectiveInactivePressedThumbColor = widget.thumbColor?.resolve(inactivePressedStates) ?? _widgetThumbColor.resolve(inactivePressedStates) ?? switchTheme.thumbColor?.resolve(inactivePressedStates) ?? defaults.thumbColor!.resolve(inactivePressedStates)!; final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates) ?? switchTheme.overlayColor?.resolve(inactivePressedStates) ?? inactiveThumbColor?.withAlpha(kRadialReactionAlpha) ?? defaults.overlayColor!.resolve(inactivePressedStates)!; final MaterialStateProperty<MouseCursor> effectiveMouseCursor = MaterialStateProperty.resolveWith<MouseCursor>((Set<MaterialState> states) { return MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states) ?? switchTheme.mouseCursor?.resolve(states) ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, states); }); final double effectiveActiveThumbRadius = effectiveActiveIcon == null ? switchConfig.activeThumbRadius : switchConfig.thumbRadiusWithIcon; final double effectiveInactiveThumbRadius = effectiveInactiveIcon == null && widget.inactiveThumbImage == null ? switchConfig.inactiveThumbRadius : switchConfig.thumbRadiusWithIcon; final double effectiveSplashRadius = widget.splashRadius ?? switchTheme.splashRadius ?? defaults.splashRadius!; return Semantics( toggled: widget.value, child: GestureDetector( excludeFromSemantics: true, onHorizontalDragStart: _handleDragStart, onHorizontalDragUpdate: _handleDragUpdate, onHorizontalDragEnd: _handleDragEnd, dragStartBehavior: widget.dragStartBehavior, child: buildToggleable( mouseCursor: effectiveMouseCursor, focusNode: widget.focusNode, onFocusChange: widget.onFocusChange, autofocus: widget.autofocus, size: widget.size, painter: _painter ..position = position ..reaction = reaction ..reactionFocusFade = reactionFocusFade ..reactionHoverFade = reactionHoverFade ..inactiveReactionColor = effectiveInactivePressedOverlayColor ..reactionColor = effectiveActivePressedOverlayColor ..hoverColor = effectiveHoverOverlayColor ..focusColor = effectiveFocusOverlayColor ..splashRadius = effectiveSplashRadius ..downPosition = downPosition ..isFocused = states.contains(MaterialState.focused) ..isHovered = states.contains(MaterialState.hovered) ..activeColor = effectiveActiveThumbColor ..inactiveColor = effectiveInactiveThumbColor ..activePressedColor = effectiveActivePressedThumbColor ..inactivePressedColor = effectiveInactivePressedThumbColor ..activeThumbImage = widget.activeThumbImage ..onActiveThumbImageError = widget.onActiveThumbImageError ..inactiveThumbImage = widget.inactiveThumbImage ..onInactiveThumbImageError = widget.onInactiveThumbImageError ..activeTrackColor = effectiveActiveTrackColor ..inactiveTrackColor = effectiveInactiveTrackColor ..inactiveTrackOutlineColor = effectiveInactiveTrackOutlineColor ..configuration = createLocalImageConfiguration(context) ..isInteractive = isInteractive ..trackInnerLength = _trackInnerLength ..textDirection = Directionality.of(context) ..surfaceColor = theme.colorScheme.surface ..inactiveThumbRadius = effectiveInactiveThumbRadius ..activeThumbRadius = effectiveActiveThumbRadius ..pressedThumbRadius = switchConfig.pressedThumbRadius ..thumbOffset = switchConfig.thumbOffset ..trackHeight = switchConfig.trackHeight ..trackWidth = switchConfig.trackWidth ..activeIconColor = effectiveActiveIconColor ..inactiveIconColor = effectiveInactiveIconColor ..activeIcon = effectiveActiveIcon ..inactiveIcon = effectiveInactiveIcon ..iconTheme = IconTheme.of(context) ..thumbShadow = switchConfig.thumbShadow ..transitionalThumbSize = switchConfig.transitionalThumbSize ..positionController = positionController, ), ), ); } } class _SwitchPainter extends ToggleablePainter { AnimationController get positionController => _positionController!; AnimationController? _positionController; set positionController(AnimationController? value) { assert(value != null); if (value == _positionController) { return; } _positionController = value; notifyListeners(); } Icon? get activeIcon => _activeIcon; Icon? _activeIcon; set activeIcon(Icon? value) { if (value == _activeIcon) { return; } _activeIcon = value; notifyListeners(); } Icon? get inactiveIcon => _inactiveIcon; Icon? _inactiveIcon; set inactiveIcon(Icon? value) { if (value == _inactiveIcon) { return; } _inactiveIcon = value; notifyListeners(); } IconThemeData? get iconTheme => _iconTheme; IconThemeData? _iconTheme; set iconTheme(IconThemeData? value) { if (value == _iconTheme) { return; } _iconTheme = value; notifyListeners(); } Color get activeIconColor => _activeIconColor!; Color? _activeIconColor; set activeIconColor(Color value) { assert(value != null); if (value == _activeIconColor) { return; } _activeIconColor = value; notifyListeners(); } Color get inactiveIconColor => _inactiveIconColor!; Color? _inactiveIconColor; set inactiveIconColor(Color value) { assert(value != null); if (value == _inactiveIconColor) { return; } _inactiveIconColor = value; notifyListeners(); } Color get activePressedColor => _activePressedColor!; Color? _activePressedColor; set activePressedColor(Color? value) { assert(value != null); if (value == _activePressedColor) { return; } _activePressedColor = value; notifyListeners(); } Color get inactivePressedColor => _inactivePressedColor!; Color? _inactivePressedColor; set inactivePressedColor(Color? value) { assert(value != null); if (value == _inactivePressedColor) { return; } _inactivePressedColor = value; notifyListeners(); } double get activeThumbRadius => _activeThumbRadius!; double? _activeThumbRadius; set activeThumbRadius(double value) { assert(value != null); if (value == _activeThumbRadius) { return; } _activeThumbRadius = value; notifyListeners(); } double get inactiveThumbRadius => _inactiveThumbRadius!; double? _inactiveThumbRadius; set inactiveThumbRadius(double value) { assert(value != null); if (value == _inactiveThumbRadius) { return; } _inactiveThumbRadius = value; notifyListeners(); } double get pressedThumbRadius => _pressedThumbRadius!; double? _pressedThumbRadius; set pressedThumbRadius(double value) { assert(value != null); if (value == _pressedThumbRadius) { return; } _pressedThumbRadius = value; notifyListeners(); } double? get thumbOffset => _thumbOffset; double? _thumbOffset; set thumbOffset(double? value) { if (value == _thumbOffset) { return; } _thumbOffset = value; notifyListeners(); } Size get transitionalThumbSize => _transitionalThumbSize!; Size? _transitionalThumbSize; set transitionalThumbSize(Size value) { assert(value != null); if (value == _transitionalThumbSize) { return; } _transitionalThumbSize = value; notifyListeners(); } double get trackHeight => _trackHeight!; double? _trackHeight; set trackHeight(double value) { assert(value != null); if (value == _trackHeight) { return; } _trackHeight = value; notifyListeners(); } double get trackWidth => _trackWidth!; double? _trackWidth; set trackWidth(double value) { assert(value != null); if (value == _trackWidth) { return; } _trackWidth = value; notifyListeners(); } ImageProvider? get activeThumbImage => _activeThumbImage; ImageProvider? _activeThumbImage; set activeThumbImage(ImageProvider? value) { if (value == _activeThumbImage) { return; } _activeThumbImage = value; notifyListeners(); } ImageErrorListener? get onActiveThumbImageError => _onActiveThumbImageError; ImageErrorListener? _onActiveThumbImageError; set onActiveThumbImageError(ImageErrorListener? value) { if (value == _onActiveThumbImageError) { return; } _onActiveThumbImageError = value; notifyListeners(); } ImageProvider? get inactiveThumbImage => _inactiveThumbImage; ImageProvider? _inactiveThumbImage; set inactiveThumbImage(ImageProvider? value) { if (value == _inactiveThumbImage) { return; } _inactiveThumbImage = value; notifyListeners(); } ImageErrorListener? get onInactiveThumbImageError => _onInactiveThumbImageError; ImageErrorListener? _onInactiveThumbImageError; set onInactiveThumbImageError(ImageErrorListener? value) { if (value == _onInactiveThumbImageError) { return; } _onInactiveThumbImageError = value; notifyListeners(); } Color get activeTrackColor => _activeTrackColor!; Color? _activeTrackColor; set activeTrackColor(Color value) { assert(value != null); if (value == _activeTrackColor) { return; } _activeTrackColor = value; notifyListeners(); } Color? get inactiveTrackOutlineColor => _inactiveTrackOutlineColor; Color? _inactiveTrackOutlineColor; set inactiveTrackOutlineColor(Color? value) { if (value == _inactiveTrackOutlineColor) { return; } _inactiveTrackOutlineColor = value; notifyListeners(); } Color get inactiveTrackColor => _inactiveTrackColor!; Color? _inactiveTrackColor; set inactiveTrackColor(Color value) { assert(value != null); if (value == _inactiveTrackColor) { return; } _inactiveTrackColor = value; notifyListeners(); } ImageConfiguration get configuration => _configuration!; ImageConfiguration? _configuration; set configuration(ImageConfiguration value) { assert(value != null); if (value == _configuration) { return; } _configuration = value; notifyListeners(); } TextDirection get textDirection => _textDirection!; TextDirection? _textDirection; set textDirection(TextDirection value) { assert(value != null); if (_textDirection == value) { return; } _textDirection = value; notifyListeners(); } Color get surfaceColor => _surfaceColor!; Color? _surfaceColor; set surfaceColor(Color value) { assert(value != null); if (value == _surfaceColor) { return; } _surfaceColor = value; notifyListeners(); } bool get isInteractive => _isInteractive!; bool? _isInteractive; set isInteractive(bool value) { if (value == _isInteractive) { return; } _isInteractive = value; notifyListeners(); } double get trackInnerLength => _trackInnerLength!; double? _trackInnerLength; set trackInnerLength(double value) { if (value == _trackInnerLength) { return; } _trackInnerLength = value; notifyListeners(); } List<BoxShadow>? get thumbShadow => _thumbShadow; List<BoxShadow>? _thumbShadow; set thumbShadow(List<BoxShadow>? value) { if (value == _thumbShadow) { return; } _thumbShadow = value; notifyListeners(); } Color? _cachedThumbColor; ImageProvider? _cachedThumbImage; ImageErrorListener? _cachedThumbErrorListener; BoxPainter? _cachedThumbPainter; ShapeDecoration _createDefaultThumbDecoration(Color color, ImageProvider? image, ImageErrorListener? errorListener) { return ShapeDecoration( color: color, image: image == null ? null : DecorationImage(image: image, onError: errorListener), shape: const StadiumBorder(), shadows: thumbShadow, ); } bool _isPainting = false; void _handleDecorationChanged() { // If the image decoration is available synchronously, we'll get called here // during paint. There's no reason to mark ourselves as needing paint if we // are already in the middle of painting. (In fact, doing so would trigger // an assert). if (!_isPainting) { notifyListeners(); } } bool _stopPressAnimation = false; double? _pressedInactiveThumbRadius; double? _pressedActiveThumbRadius; @override void paint(Canvas canvas, Size size) { final double currentValue = position.value; final double visualPosition; switch (textDirection) { case TextDirection.rtl: visualPosition = 1.0 - currentValue; break; case TextDirection.ltr: visualPosition = currentValue; break; } if (reaction.status == AnimationStatus.reverse && !_stopPressAnimation) { _stopPressAnimation = true; } else { _stopPressAnimation = false; } // To get the thumb radius when the press ends, the value can be any number // between activeThumbRadius/inactiveThumbRadius and pressedThumbRadius. if (!_stopPressAnimation) { if (reaction.isCompleted) { // This happens when the thumb is dragged instead of being tapped. _pressedInactiveThumbRadius = lerpDouble(inactiveThumbRadius, pressedThumbRadius, reaction.value); _pressedActiveThumbRadius = lerpDouble(activeThumbRadius, pressedThumbRadius, reaction.value); } if (currentValue == 0) { _pressedInactiveThumbRadius = lerpDouble(inactiveThumbRadius, pressedThumbRadius, reaction.value); _pressedActiveThumbRadius = activeThumbRadius; } if (currentValue == 1) { _pressedActiveThumbRadius = lerpDouble(activeThumbRadius, pressedThumbRadius, reaction.value); _pressedInactiveThumbRadius = inactiveThumbRadius; } } final Size inactiveThumbSize = Size.fromRadius(_pressedInactiveThumbRadius ?? inactiveThumbRadius); final Size activeThumbSize = Size.fromRadius(_pressedActiveThumbRadius ?? activeThumbRadius); Animation<Size> thumbSizeAnimation(bool isForward) { List<TweenSequenceItem<Size>> thumbSizeSequence; if (isForward) { thumbSizeSequence = <TweenSequenceItem<Size>>[ TweenSequenceItem<Size>( tween: Tween<Size>(begin: inactiveThumbSize, end: transitionalThumbSize) .chain(CurveTween(curve: const Cubic(0.31, 0.00, 0.56, 1.00))), weight: 11, ), TweenSequenceItem<Size>( tween: Tween<Size>(begin: transitionalThumbSize, end: activeThumbSize) .chain(CurveTween(curve: const Cubic(0.20, 0.00, 0.00, 1.00))), weight: 72, ), TweenSequenceItem<Size>( tween: ConstantTween<Size>(activeThumbSize), weight: 17, ) ]; } else { thumbSizeSequence = <TweenSequenceItem<Size>>[ TweenSequenceItem<Size>( tween: ConstantTween<Size>(inactiveThumbSize), weight: 17, ), TweenSequenceItem<Size>( tween: Tween<Size>(begin: inactiveThumbSize, end: transitionalThumbSize) .chain(CurveTween(curve: const Cubic(0.20, 0.00, 0.00, 1.00).flipped)), weight: 72, ), TweenSequenceItem<Size>( tween: Tween<Size>(begin: transitionalThumbSize, end: activeThumbSize) .chain(CurveTween(curve: const Cubic(0.31, 0.00, 0.56, 1.00).flipped)), weight: 11, ), ]; } return TweenSequence<Size>(thumbSizeSequence).animate(positionController); } Size thumbSize; if (reaction.isCompleted) { thumbSize = Size.fromRadius(pressedThumbRadius); } else { if (position.isDismissed || position.status == AnimationStatus.forward) { thumbSize = thumbSizeAnimation(true).value; } else { thumbSize = thumbSizeAnimation(false).value; } } // The thumb contracts slightly during the animation in Material 2. final double inset = thumbOffset == null ? 0 : 1.0 - (currentValue - thumbOffset!).abs() * 2.0; thumbSize = Size(thumbSize.width - inset, thumbSize.height - inset); final double colorValue = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn).value; final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, colorValue)!; final Color? trackOutlineColor = inactiveTrackOutlineColor == null ? null : Color.lerp(inactiveTrackOutlineColor, Colors.transparent, colorValue); Color lerpedThumbColor; if (!reaction.isDismissed) { lerpedThumbColor = Color.lerp(inactivePressedColor, activePressedColor, colorValue)!; } else if (positionController.status == AnimationStatus.forward) { lerpedThumbColor = Color.lerp(inactivePressedColor, activeColor, colorValue)!; } else if (positionController.status == AnimationStatus.reverse) { lerpedThumbColor = Color.lerp(inactiveColor, activePressedColor, colorValue)!; } else { lerpedThumbColor = Color.lerp(inactiveColor, activeColor, colorValue)!; } // Blend the thumb color against a `surfaceColor` background in case the // thumbColor is not opaque. This way we do not see through the thumb to the // track underneath. final Color thumbColor = Color.alphaBlend(lerpedThumbColor, surfaceColor); final Icon? thumbIcon = currentValue < 0.5 ? inactiveIcon : activeIcon; final ImageProvider? thumbImage = currentValue < 0.5 ? inactiveThumbImage : activeThumbImage; final ImageErrorListener? thumbErrorListener = currentValue < 0.5 ? onInactiveThumbImageError : onActiveThumbImageError; final Paint paint = Paint() ..color = trackColor; final Offset trackPaintOffset = _computeTrackPaintOffset(size, trackWidth, trackHeight); final Offset thumbPaintOffset = _computeThumbPaintOffset(trackPaintOffset, thumbSize, visualPosition); final Offset radialReactionOrigin = Offset(thumbPaintOffset.dx + thumbSize.height / 2, size.height / 2); _paintTrackWith(canvas, paint, trackPaintOffset, trackOutlineColor); paintRadialReaction(canvas: canvas, origin: radialReactionOrigin); _paintThumbWith( thumbPaintOffset, canvas, colorValue, thumbColor, thumbImage, thumbErrorListener, thumbIcon, thumbSize, inset, ); } /// Computes canvas offset for track's upper left corner Offset _computeTrackPaintOffset(Size canvasSize, double trackWidth, double trackHeight) { final double horizontalOffset = (canvasSize.width - trackWidth) / 2.0; final double verticalOffset = (canvasSize.height - trackHeight) / 2.0; return Offset(horizontalOffset, verticalOffset); } /// Computes canvas offset for thumb's upper left corner as if it were a /// square Offset _computeThumbPaintOffset(Offset trackPaintOffset, Size thumbSize, double visualPosition) { // How much thumb radius extends beyond the track final double trackRadius = trackHeight / 2; final double additionalThumbRadius = thumbSize.height / 2 - trackRadius; final double additionalRectWidth = (thumbSize.width - thumbSize.height) / 2; final double horizontalProgress = visualPosition * trackInnerLength; final double thumbHorizontalOffset = trackPaintOffset.dx - additionalThumbRadius - additionalRectWidth + horizontalProgress; final double thumbVerticalOffset = trackPaintOffset.dy - additionalThumbRadius; return Offset(thumbHorizontalOffset, thumbVerticalOffset); } void _paintTrackWith(Canvas canvas, Paint paint, Offset trackPaintOffset, Color? trackOutlineColor) { final Rect trackRect = Rect.fromLTWH( trackPaintOffset.dx, trackPaintOffset.dy, trackWidth, trackHeight, ); final double trackRadius = trackHeight / 2; final RRect trackRRect = RRect.fromRectAndRadius( trackRect, Radius.circular(trackRadius), ); canvas.drawRRect(trackRRect, paint); if (trackOutlineColor != null) { // paint track outline final Rect outlineTrackRect = Rect.fromLTWH( trackPaintOffset.dx + 1, trackPaintOffset.dy + 1, trackWidth - 2, trackHeight - 2, ); final RRect outlineTrackRRect = RRect.fromRectAndRadius( outlineTrackRect, Radius.circular(trackRadius), ); final Paint outlinePaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2 ..color = trackOutlineColor; canvas.drawRRect(outlineTrackRRect, outlinePaint); } } void _paintThumbWith( Offset thumbPaintOffset, Canvas canvas, double currentValue, Color thumbColor, ImageProvider? thumbImage, ImageErrorListener? thumbErrorListener, Icon? thumbIcon, Size thumbSize, double inset, ) { try { _isPainting = true; if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage || thumbErrorListener != _cachedThumbErrorListener) { _cachedThumbColor = thumbColor; _cachedThumbImage = thumbImage; _cachedThumbErrorListener = thumbErrorListener; _cachedThumbPainter?.dispose(); _cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage, thumbErrorListener).createBoxPainter(_handleDecorationChanged); } final BoxPainter thumbPainter = _cachedThumbPainter!; thumbPainter.paint( canvas, thumbPaintOffset, configuration.copyWith(size: thumbSize), ); if (thumbIcon != null && thumbIcon.icon != null) { final Color iconColor = Color.lerp(inactiveIconColor, activeIconColor, currentValue)!; final double iconSize = thumbIcon.size ?? _SwitchConfigM3.iconSize; final IconData iconData = thumbIcon.icon!; final double? iconWeight = thumbIcon.weight ?? iconTheme?.weight; final double? iconFill = thumbIcon.fill ?? iconTheme?.fill; final double? iconGrade = thumbIcon.grade ?? iconTheme?.grade; final double? iconOpticalSize = thumbIcon.opticalSize ?? iconTheme?.opticalSize; final List<Shadow>? iconShadows = thumbIcon.shadows ?? iconTheme?.shadows; final TextSpan textSpan = TextSpan( text: String.fromCharCode(iconData.codePoint), style: TextStyle( fontVariations: <FontVariation>[ if (iconFill != null) FontVariation('FILL', iconFill), if (iconWeight != null) FontVariation('wght', iconWeight), if (iconGrade != null) FontVariation('GRAD', iconGrade), if (iconOpticalSize != null) FontVariation('opsz', iconOpticalSize), ], color: iconColor, fontSize: iconSize, inherit: false, fontFamily: iconData.fontFamily, package: iconData.fontPackage, shadows: iconShadows, ), ); final TextPainter textPainter = TextPainter( textDirection: textDirection, text: textSpan, ); textPainter.layout(); final double additionalHorizontalOffset = (thumbSize.width - iconSize) / 2; final double additionalVerticalOffset = (thumbSize.height - iconSize) / 2; final Offset offset = thumbPaintOffset + Offset(additionalHorizontalOffset, additionalVerticalOffset); textPainter.paint(canvas, offset); } } finally { _isPainting = false; } } @override void dispose() { _cachedThumbPainter?.dispose(); _cachedThumbPainter = null; _cachedThumbColor = null; _cachedThumbImage = null; _cachedThumbErrorListener = null; super.dispose(); } } mixin _SwitchConfig { double get trackHeight; double get trackWidth; double get switchWidth; double get switchHeight; double get switchHeightCollapsed; double get activeThumbRadius; double get inactiveThumbRadius; double get pressedThumbRadius; double get thumbRadiusWithIcon; List<BoxShadow>? get thumbShadow; MaterialStateProperty<Color?>? get trackOutlineColor; MaterialStateProperty<Color> get iconColor; double? get thumbOffset; Size get transitionalThumbSize; int get toggleDuration; } // Hand coded defaults based on Material Design 2. class _SwitchConfigM2 with _SwitchConfig { _SwitchConfigM2(); @override double get activeThumbRadius => 10.0; @override MaterialStateProperty<Color> get iconColor => MaterialStateProperty.all<Color>(Colors.transparent); @override double get inactiveThumbRadius => 10.0; @override double get pressedThumbRadius => 10.0; @override double get switchHeight => _kSwitchMinSize + 8.0; @override double get switchHeightCollapsed => _kSwitchMinSize; @override double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize; @override double get thumbRadiusWithIcon => 10.0; @override List<BoxShadow>? get thumbShadow => kElevationToShadow[1]; @override double get trackHeight => 14.0; @override MaterialStateProperty<Color?>? get trackOutlineColor => null; @override double get trackWidth => 33.0; @override double get thumbOffset => 0.5; @override Size get transitionalThumbSize => const Size(20, 20); @override int get toggleDuration => 200; } class _SwitchDefaultsM2 extends SwitchThemeData { _SwitchDefaultsM2(BuildContext context) : _theme = Theme.of(context), _colors = Theme.of(context).colorScheme; final ThemeData _theme; final ColorScheme _colors; @override MaterialStateProperty<Color> get thumbColor { final bool isDark = _theme.brightness == Brightness.dark; return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return isDark ? Colors.grey.shade800 : Colors.grey.shade400; } if (states.contains(MaterialState.selected)) { return _colors.secondary; } return isDark ? Colors.grey.shade400 : Colors.grey.shade50; }); } @override MaterialStateProperty<Color> get trackColor { final bool isDark = _theme.brightness == Brightness.dark; const Color black32 = Color(0x52000000); // Black with 32% opacity return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return isDark ? Colors.white10 : Colors.black12; } if (states.contains(MaterialState.selected)) { final Color activeColor = _colors.secondary; return activeColor.withAlpha(0x80); } return isDark ? Colors.white30 : black32; }); } @override MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; @override MaterialStateProperty<MouseCursor> get mouseCursor => MaterialStateProperty.resolveWith((Set<MaterialState> states) => MaterialStateMouseCursor.clickable.resolve(states)); @override MaterialStateProperty<Color?> get overlayColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.pressed)) { return thumbColor.resolve(states).withAlpha(kRadialReactionAlpha); } if (states.contains(MaterialState.focused)) { return _theme.focusColor; } if (states.contains(MaterialState.hovered)) { return _theme.hoverColor; } return null; }); } @override double get splashRadius => kRadialReactionRadius; } // BEGIN GENERATED TOKEN PROPERTIES - Switch // Do not edit by hand. The code between the "BEGIN GENERATED" and // "END GENERATED" comments are generated from data in the Material // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. // Token database version: v0_143 class _SwitchDefaultsM3 extends SwitchThemeData { _SwitchDefaultsM3(BuildContext context) : _colors = Theme.of(context).colorScheme; final ColorScheme _colors; @override MaterialStateProperty<Color> get thumbColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.selected)) { return _colors.surface.withOpacity(1.0); } return _colors.onSurface.withOpacity(0.38); } if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { return _colors.primaryContainer; } if (states.contains(MaterialState.hovered)) { return _colors.primaryContainer; } if (states.contains(MaterialState.focused)) { return _colors.primaryContainer; } return _colors.onPrimary; } if (states.contains(MaterialState.pressed)) { return _colors.onSurfaceVariant; } if (states.contains(MaterialState.hovered)) { return _colors.onSurfaceVariant; } if (states.contains(MaterialState.focused)) { return _colors.onSurfaceVariant; } return _colors.outline; }); } @override MaterialStateProperty<Color> get trackColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.selected)) { return _colors.onSurface.withOpacity(0.12); } return _colors.surfaceVariant.withOpacity(0.12); } if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { return _colors.primary; } if (states.contains(MaterialState.hovered)) { return _colors.primary; } if (states.contains(MaterialState.focused)) { return _colors.primary; } return _colors.primary; } if (states.contains(MaterialState.pressed)) { return _colors.surfaceVariant; } if (states.contains(MaterialState.hovered)) { return _colors.surfaceVariant; } if (states.contains(MaterialState.focused)) { return _colors.surfaceVariant; } return _colors.surfaceVariant; }); } @override MaterialStateProperty<Color?> get overlayColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { return _colors.primary.withOpacity(0.12); } if (states.contains(MaterialState.hovered)) { return _colors.primary.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { return _colors.primary.withOpacity(0.12); } return null; } if (states.contains(MaterialState.pressed)) { return _colors.onSurface.withOpacity(0.12); } if (states.contains(MaterialState.hovered)) { return _colors.onSurface.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { return _colors.onSurface.withOpacity(0.12); } return null; }); } @override double get splashRadius => 40.0 / 2; } class _SwitchConfigM3 with _SwitchConfig { _SwitchConfigM3(this.context) : _colors = Theme.of(context).colorScheme; BuildContext context; final ColorScheme _colors; static const double iconSize = 16.0; @override double get activeThumbRadius => 24.0 / 2; @override MaterialStateProperty<Color> get iconColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.selected)) { return _colors.onSurface.withOpacity(0.38); } return _colors.surfaceVariant.withOpacity(0.38); } if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { return _colors.onPrimaryContainer; } if (states.contains(MaterialState.hovered)) { return _colors.onPrimaryContainer; } if (states.contains(MaterialState.focused)) { return _colors.onPrimaryContainer; } return _colors.onPrimaryContainer; } if (states.contains(MaterialState.pressed)) { return _colors.surfaceVariant; } if (states.contains(MaterialState.hovered)) { return _colors.surfaceVariant; } if (states.contains(MaterialState.focused)) { return _colors.surfaceVariant; } return _colors.surfaceVariant; }); } @override double get inactiveThumbRadius => 16.0 / 2; @override double get pressedThumbRadius => 28.0 / 2; @override double get switchHeight => _kSwitchMinSize + 8.0; @override double get switchHeightCollapsed => _kSwitchMinSize; @override double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize; @override double get thumbRadiusWithIcon => 24.0 / 2; @override List<BoxShadow>? get thumbShadow => kElevationToShadow[0]; @override double get trackHeight => 32.0; @override MaterialStateProperty<Color?> get trackOutlineColor { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { return null; } if (states.contains(MaterialState.disabled)) { return _colors.onSurface.withOpacity(0.12); } return _colors.outline; }); } @override double get trackWidth => 52.0; // The thumb size at the middle of the track. Hand coded default based on the animation specs. @override Size get transitionalThumbSize => const Size(34, 22); // Hand coded default based on the animation specs. @override int get toggleDuration => 300; // Hand coded default based on the animation specs. @override double? get thumbOffset => null; } // END GENERATED TOKEN PROPERTIES - Switch