| // 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} |
| /// |
| /// {@tool dartpad} |
| /// This example shows how to use the ambient [CupertinoThemeData] to style all |
| /// widgets which would otherwise use iOS defaults. |
| /// |
| /// ** See code in examples/api/lib/material/switch/switch.3.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.trackOutlineColor, |
| this.trackOutlineWidth, |
| 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(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], [trackOutlineWidth] |
| /// [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.trackOutlineColor, |
| this.trackOutlineWidth, |
| 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(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; |
| |
| /// {@template flutter.material.switch.activeColor} |
| /// The color to use when this switch is on. |
| /// {@endtemplate} |
| /// |
| /// 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; |
| |
| /// {@template flutter.material.switch.activeTrackColor} |
| /// The color to use on the track when this switch is on. |
| /// {@endtemplate} |
| /// |
| /// 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; |
| |
| /// {@template flutter.material.switch.inactiveThumbColor} |
| /// The color to use on the thumb when this switch is off. |
| /// {@endtemplate} |
| /// |
| /// 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; |
| |
| /// {@template flutter.material.switch.inactiveTrackColor} |
| /// The color to use on the track when this switch is off. |
| /// {@endtemplate} |
| /// |
| /// 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; |
| |
| /// {@template flutter.material.switch.activeThumbImage} |
| /// An image to use on the thumb of this switch when the switch is on. |
| /// {@endtemplate} |
| /// |
| /// Ignored if this switch is created with [Switch.adaptive]. |
| final ImageProvider? activeThumbImage; |
| |
| /// {@template flutter.material.switch.onActiveThumbImageError} |
| /// An optional error callback for errors emitted when loading |
| /// [activeThumbImage]. |
| /// {@endtemplate} |
| final ImageErrorListener? onActiveThumbImageError; |
| |
| /// {@template flutter.material.switch.inactiveThumbImage} |
| /// An image to use on the thumb of this switch when the switch is off. |
| /// {@endtemplate} |
| /// |
| /// Ignored if this switch is created with [Switch.adaptive]. |
| final ImageProvider? inactiveThumbImage; |
| |
| /// {@template flutter.material.switch.onInactiveThumbImageError} |
| /// An optional error callback for errors emitted when loading |
| /// [inactiveThumbImage]. |
| /// {@endtemplate} |
| 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: (bool value) { }, |
| /// 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: (bool value) { }, |
| /// 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.trackOutlineColor} |
| /// The outline 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 [trackOutlineColor] based on the current |
| /// [MaterialState] of the [Switch], providing a different [Color] when it is |
| /// [MaterialState.disabled]. |
| /// |
| /// ```dart |
| /// Switch( |
| /// value: true, |
| /// onChanged: (bool value) { }, |
| /// trackOutlineColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) { |
| /// if (states.contains(MaterialState.disabled)) { |
| /// return Colors.orange.withOpacity(.48); |
| /// } |
| /// return null; // Use the default color. |
| /// }), |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| /// {@endtemplate} |
| /// |
| /// In Material 3, the outline color defaults to transparent in the selected |
| /// state and [ColorScheme.outline] in the unselected state. In Material 2, |
| /// the [Switch] track has no outline by default. |
| final MaterialStateProperty<Color?>? trackOutlineColor; |
| |
| /// {@template flutter.material.switch.trackOutlineWidth} |
| /// The outline width of this [Switch]'s track. |
| /// |
| /// Resolved in the following states: |
| /// * [MaterialState.selected]. |
| /// * [MaterialState.hovered]. |
| /// * [MaterialState.focused]. |
| /// * [MaterialState.disabled]. |
| /// |
| /// {@tool snippet} |
| /// This example resolves the [trackOutlineWidth] based on the current |
| /// [MaterialState] of the [Switch], providing a different outline width when it is |
| /// [MaterialState.disabled]. |
| /// |
| /// ```dart |
| /// Switch( |
| /// value: true, |
| /// onChanged: (bool value) { }, |
| /// trackOutlineWidth: MaterialStateProperty.resolveWith<double?>((Set<MaterialState> states) { |
| /// if (states.contains(MaterialState.disabled)) { |
| /// return 5.0; |
| /// } |
| /// return null; // Use the default width. |
| /// }), |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| /// {@endtemplate} |
| /// |
| /// Defaults to 2.0. |
| final MaterialStateProperty<double?>? trackOutlineWidth; |
| |
| /// {@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: (bool value) { }, |
| /// 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 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, |
| thumbColor: thumbColor?.resolve(<MaterialState>{}), |
| applyTheme: applyCupertinoTheme, |
| focusColor: focusColor, |
| focusNode: focusNode, |
| onFocusChange: onFocusChange, |
| autofocus: autofocus, |
| ), |
| ); |
| } |
| |
| 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, |
| trackOutlineColor: trackOutlineColor, |
| trackOutlineWidth: trackOutlineWidth, |
| 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); |
| 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.trackOutlineColor, |
| this.trackOutlineWidth, |
| 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(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<Color?>? trackOutlineColor; |
| final MaterialStateProperty<double?>? trackOutlineWidth; |
| 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; |
| case TextDirection.ltr: |
| positionController.value += delta; |
| } |
| } |
| } |
| |
| 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 effectiveActiveTrackOutlineColor = widget.trackOutlineColor?.resolve(activeStates) |
| ?? switchTheme.trackOutlineColor?.resolve(activeStates) |
| ?? Colors.transparent; |
| final double? effectiveActiveTrackOutlineWidth = widget.trackOutlineWidth?.resolve(activeStates) |
| ?? switchTheme.trackOutlineWidth?.resolve(activeStates) |
| ?? defaults.trackOutlineWidth?.resolve(activeStates); |
| |
| final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates) |
| ?? _widgetTrackColor.resolve(inactiveStates) |
| ?? switchTheme.trackColor?.resolve(inactiveStates) |
| ?? defaults.trackColor!.resolve(inactiveStates)!; |
| final Color? effectiveInactiveTrackOutlineColor = widget.trackOutlineColor?.resolve(inactiveStates) |
| ?? switchTheme.trackOutlineColor?.resolve(inactiveStates) |
| ?? defaults.trackOutlineColor?.resolve(inactiveStates); |
| final double? effectiveInactiveTrackOutlineWidth = widget.trackOutlineWidth?.resolve(inactiveStates) |
| ?? switchTheme.trackOutlineWidth?.resolve(inactiveStates) |
| ?? defaults.trackOutlineWidth?.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 |
| ..activeTrackOutlineColor = effectiveActiveTrackOutlineColor |
| ..activeTrackOutlineWidth = effectiveActiveTrackOutlineWidth |
| ..inactiveTrackColor = effectiveInactiveTrackColor |
| ..inactiveTrackOutlineColor = effectiveInactiveTrackOutlineColor |
| ..inactiveTrackOutlineWidth = effectiveInactiveTrackOutlineWidth |
| ..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) { |
| if (value == _activeIconColor) { |
| return; |
| } |
| _activeIconColor = value; |
| notifyListeners(); |
| } |
| |
| Color get inactiveIconColor => _inactiveIconColor!; |
| Color? _inactiveIconColor; |
| set inactiveIconColor(Color value) { |
| 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) { |
| if (value == _activeThumbRadius) { |
| return; |
| } |
| _activeThumbRadius = value; |
| notifyListeners(); |
| } |
| |
| double get inactiveThumbRadius => _inactiveThumbRadius!; |
| double? _inactiveThumbRadius; |
| set inactiveThumbRadius(double value) { |
| if (value == _inactiveThumbRadius) { |
| return; |
| } |
| _inactiveThumbRadius = value; |
| notifyListeners(); |
| } |
| |
| double get pressedThumbRadius => _pressedThumbRadius!; |
| double? _pressedThumbRadius; |
| set pressedThumbRadius(double value) { |
| 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) { |
| if (value == _transitionalThumbSize) { |
| return; |
| } |
| _transitionalThumbSize = value; |
| notifyListeners(); |
| } |
| |
| double get trackHeight => _trackHeight!; |
| double? _trackHeight; |
| set trackHeight(double value) { |
| if (value == _trackHeight) { |
| return; |
| } |
| _trackHeight = value; |
| notifyListeners(); |
| } |
| |
| double get trackWidth => _trackWidth!; |
| double? _trackWidth; |
| set trackWidth(double value) { |
| 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) { |
| if (value == _activeTrackColor) { |
| return; |
| } |
| _activeTrackColor = value; |
| notifyListeners(); |
| } |
| |
| Color? get activeTrackOutlineColor => _activeTrackOutlineColor; |
| Color? _activeTrackOutlineColor; |
| set activeTrackOutlineColor(Color? value) { |
| if (value == _activeTrackOutlineColor) { |
| return; |
| } |
| _activeTrackOutlineColor = value; |
| notifyListeners(); |
| } |
| |
| Color? get inactiveTrackOutlineColor => _inactiveTrackOutlineColor; |
| Color? _inactiveTrackOutlineColor; |
| set inactiveTrackOutlineColor(Color? value) { |
| if (value == _inactiveTrackOutlineColor) { |
| return; |
| } |
| _inactiveTrackOutlineColor = value; |
| notifyListeners(); |
| } |
| |
| double? get activeTrackOutlineWidth => _activeTrackOutlineWidth; |
| double? _activeTrackOutlineWidth; |
| set activeTrackOutlineWidth(double? value) { |
| if (value == _activeTrackOutlineWidth) { |
| return; |
| } |
| _activeTrackOutlineWidth = value; |
| notifyListeners(); |
| } |
| |
| double? get inactiveTrackOutlineWidth => _inactiveTrackOutlineWidth; |
| double? _inactiveTrackOutlineWidth; |
| set inactiveTrackOutlineWidth(double? value) { |
| if (value == _inactiveTrackOutlineWidth) { |
| return; |
| } |
| _inactiveTrackOutlineWidth = value; |
| notifyListeners(); |
| } |
| |
| Color get inactiveTrackColor => _inactiveTrackColor!; |
| Color? _inactiveTrackColor; |
| set inactiveTrackColor(Color value) { |
| if (value == _inactiveTrackColor) { |
| return; |
| } |
| _inactiveTrackColor = value; |
| notifyListeners(); |
| } |
| |
| ImageConfiguration get configuration => _configuration!; |
| ImageConfiguration? _configuration; |
| set configuration(ImageConfiguration value) { |
| if (value == _configuration) { |
| return; |
| } |
| _configuration = value; |
| notifyListeners(); |
| } |
| |
| TextDirection get textDirection => _textDirection!; |
| TextDirection? _textDirection; |
| set textDirection(TextDirection value) { |
| if (_textDirection == value) { |
| return; |
| } |
| _textDirection = value; |
| notifyListeners(); |
| } |
| |
| Color get surfaceColor => _surfaceColor!; |
| Color? _surfaceColor; |
| set surfaceColor(Color value) { |
| 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; |
| case TextDirection.ltr: |
| visualPosition = currentValue; |
| } |
| 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, activeTrackOutlineColor, colorValue); |
| final double? trackOutlineWidth = lerpDouble(inactiveTrackOutlineWidth, activeTrackOutlineWidth, 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, trackOutlineWidth); |
| 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, double? trackOutlineWidth) { |
| 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 = trackOutlineWidth ?? 2.0 |
| ..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 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 |
| 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 |
| MaterialStateProperty<Color?>? get trackOutlineColor => null; |
| |
| @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.hovered)) { |
| return _theme.hoverColor; |
| } |
| if (states.contains(MaterialState.focused)) { |
| return _theme.focusColor; |
| } |
| 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. |
| |
| class _SwitchDefaultsM3 extends SwitchThemeData { |
| _SwitchDefaultsM3(this.context); |
| |
| final BuildContext context; |
| late final ColorScheme _colors = Theme.of(context).colorScheme; |
| |
| @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 trackOutlineColor { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.selected)) { |
| return Colors.transparent; |
| } |
| if (states.contains(MaterialState.disabled)) { |
| return _colors.onSurface.withOpacity(0.12); |
| } |
| return _colors.outline; |
| }); |
| } |
| |
| @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 |
| MaterialStatePropertyAll<double> get trackOutlineWidth => const MaterialStatePropertyAll<double>(2.0); |
| |
| @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 |
| 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 |