Skip to content

Commit 3f98c0f

Browse files
authored
Add trackOutlineColor for Switch and SwitchListTile (#120140)
* Add trackOutlineColor for Switch and SwitchListTile * Update tests * Update test * Clean up unnecessary StatefulBUilder in tests * Fix failed test --------- Co-authored-by: Qun Cheng <[email protected]>
1 parent 6e7f580 commit 3f98c0f

File tree

6 files changed

+536
-231
lines changed

6 files changed

+536
-231
lines changed

packages/flutter/lib/src/material/switch.dart

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class Switch extends StatelessWidget {
105105
this.onInactiveThumbImageError,
106106
this.thumbColor,
107107
this.trackColor,
108+
this.trackOutlineColor,
108109
this.thumbIcon,
109110
this.materialTapTargetSize,
110111
this.dragStartBehavior = DragStartBehavior.start,
@@ -151,6 +152,7 @@ class Switch extends StatelessWidget {
151152
this.materialTapTargetSize,
152153
this.thumbColor,
153154
this.trackColor,
155+
this.trackOutlineColor,
154156
this.thumbIcon,
155157
this.dragStartBehavior = DragStartBehavior.start,
156158
this.mouseCursor,
@@ -333,6 +335,40 @@ class Switch extends StatelessWidget {
333335
/// | Disabled | `Colors.black12` | `Colors.white10` |
334336
final MaterialStateProperty<Color?>? trackColor;
335337

338+
/// {@template flutter.material.switch.trackOutlineColor}
339+
/// The outline color of this [Switch]'s track.
340+
///
341+
/// Resolved in the following states:
342+
/// * [MaterialState.selected].
343+
/// * [MaterialState.hovered].
344+
/// * [MaterialState.focused].
345+
/// * [MaterialState.disabled].
346+
///
347+
/// {@tool snippet}
348+
/// This example resolves the [trackOutlineColor] based on the current
349+
/// [MaterialState] of the [Switch], providing a different [Color] when it is
350+
/// [MaterialState.disabled].
351+
///
352+
/// ```dart
353+
/// Switch(
354+
/// value: true,
355+
/// onChanged: (_) => true,
356+
/// trackOutlineColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
357+
/// if (states.contains(MaterialState.disabled)) {
358+
/// return Colors.orange.withOpacity(.48);
359+
/// }
360+
/// return null; // Use the default color.
361+
/// }),
362+
/// )
363+
/// ```
364+
/// {@end-tool}
365+
/// {@endtemplate}
366+
///
367+
/// In Material 3, the outline color defaults to transparent in the selected
368+
/// state and [ColorScheme.outline] in the unselected state. In Material 2,
369+
/// the [Switch] track has no outline by default.
370+
final MaterialStateProperty<Color?>? trackOutlineColor;
371+
336372
/// {@template flutter.material.switch.thumbIcon}
337373
/// The icon to use on the thumb of this switch
338374
///
@@ -519,6 +555,7 @@ class Switch extends StatelessWidget {
519555
onInactiveThumbImageError: onInactiveThumbImageError,
520556
thumbColor: thumbColor,
521557
trackColor: trackColor,
558+
trackOutlineColor: trackOutlineColor,
522559
thumbIcon: thumbIcon,
523560
materialTapTargetSize: materialTapTargetSize,
524561
dragStartBehavior: dragStartBehavior,
@@ -578,6 +615,7 @@ class _MaterialSwitch extends StatefulWidget {
578615
this.onInactiveThumbImageError,
579616
this.thumbColor,
580617
this.trackColor,
618+
this.trackOutlineColor,
581619
this.thumbIcon,
582620
this.materialTapTargetSize,
583621
this.dragStartBehavior = DragStartBehavior.start,
@@ -604,6 +642,7 @@ class _MaterialSwitch extends StatefulWidget {
604642
final ImageErrorListener? onInactiveThumbImageError;
605643
final MaterialStateProperty<Color?>? thumbColor;
606644
final MaterialStateProperty<Color?>? trackColor;
645+
final MaterialStateProperty<Color?>? trackOutlineColor;
607646
final MaterialStateProperty<Icon?>? thumbIcon;
608647
final MaterialTapTargetSize? materialTapTargetSize;
609648
final DragStartBehavior dragStartBehavior;
@@ -765,11 +804,17 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
765804
?? switchTheme.trackColor?.resolve(activeStates)
766805
?? _widgetThumbColor.resolve(activeStates)?.withAlpha(0x80)
767806
?? defaults.trackColor!.resolve(activeStates)!;
807+
final Color effectiveActiveTrackOutlineColor = widget.trackOutlineColor?.resolve(activeStates)
808+
?? switchTheme.trackOutlineColor?.resolve(activeStates)
809+
?? Colors.transparent;
810+
768811
final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates)
769812
?? _widgetTrackColor.resolve(inactiveStates)
770813
?? switchTheme.trackColor?.resolve(inactiveStates)
771814
?? defaults.trackColor!.resolve(inactiveStates)!;
772-
final Color? effectiveInactiveTrackOutlineColor = switchConfig.trackOutlineColor?.resolve(inactiveStates);
815+
final Color? effectiveInactiveTrackOutlineColor = widget.trackOutlineColor?.resolve(inactiveStates)
816+
?? switchTheme.trackOutlineColor?.resolve(inactiveStates)
817+
?? defaults.trackOutlineColor?.resolve(inactiveStates);
773818

774819
final Icon? effectiveActiveIcon = widget.thumbIcon?.resolve(activeStates)
775820
?? switchTheme.thumbIcon?.resolve(activeStates);
@@ -858,6 +903,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
858903
..inactiveThumbImage = widget.inactiveThumbImage
859904
..onInactiveThumbImageError = widget.onInactiveThumbImageError
860905
..activeTrackColor = effectiveActiveTrackColor
906+
..activeTrackOutlineColor = effectiveActiveTrackOutlineColor
861907
..inactiveTrackColor = effectiveInactiveTrackColor
862908
..inactiveTrackOutlineColor = effectiveInactiveTrackOutlineColor
863909
..configuration = createLocalImageConfiguration(context)
@@ -1089,6 +1135,16 @@ class _SwitchPainter extends ToggleablePainter {
10891135
notifyListeners();
10901136
}
10911137

1138+
Color? get activeTrackOutlineColor => _activeTrackOutlineColor;
1139+
Color? _activeTrackOutlineColor;
1140+
set activeTrackOutlineColor(Color? value) {
1141+
if (value == _activeTrackOutlineColor) {
1142+
return;
1143+
}
1144+
_activeTrackOutlineColor = value;
1145+
notifyListeners();
1146+
}
1147+
10921148
Color? get inactiveTrackOutlineColor => _inactiveTrackOutlineColor;
10931149
Color? _inactiveTrackOutlineColor;
10941150
set inactiveTrackOutlineColor(Color? value) {
@@ -1297,7 +1353,7 @@ class _SwitchPainter extends ToggleablePainter {
12971353
final double colorValue = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn).value;
12981354
final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, colorValue)!;
12991355
final Color? trackOutlineColor = inactiveTrackOutlineColor == null ? null
1300-
: Color.lerp(inactiveTrackOutlineColor, Colors.transparent, colorValue);
1356+
: Color.lerp(inactiveTrackOutlineColor, activeTrackOutlineColor, colorValue);
13011357
Color lerpedThumbColor;
13021358
if (!reaction.isDismissed) {
13031359
lerpedThumbColor = Color.lerp(inactivePressedColor, activePressedColor, colorValue)!;
@@ -1493,7 +1549,6 @@ mixin _SwitchConfig {
14931549
double get pressedThumbRadius;
14941550
double get thumbRadiusWithIcon;
14951551
List<BoxShadow>? get thumbShadow;
1496-
MaterialStateProperty<Color?>? get trackOutlineColor;
14971552
MaterialStateProperty<Color> get iconColor;
14981553
double? get thumbOffset;
14991554
Size get transitionalThumbSize;
@@ -1534,9 +1589,6 @@ class _SwitchConfigM2 with _SwitchConfig {
15341589
@override
15351590
double get trackHeight => 14.0;
15361591

1537-
@override
1538-
MaterialStateProperty<Color?>? get trackOutlineColor => null;
1539-
15401592
@override
15411593
double get trackWidth => 33.0;
15421594

@@ -1590,6 +1642,9 @@ class _SwitchDefaultsM2 extends SwitchThemeData {
15901642
});
15911643
}
15921644

1645+
@override
1646+
MaterialStateProperty<Color?>? get trackOutlineColor => null;
1647+
15931648
@override
15941649
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
15951650

@@ -1700,6 +1755,19 @@ class _SwitchDefaultsM3 extends SwitchThemeData {
17001755
});
17011756
}
17021757

1758+
@override
1759+
MaterialStateProperty<Color?> get trackOutlineColor {
1760+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
1761+
if (states.contains(MaterialState.selected)) {
1762+
return Colors.transparent;
1763+
}
1764+
if (states.contains(MaterialState.disabled)) {
1765+
return _colors.onSurface.withOpacity(0.12);
1766+
}
1767+
return _colors.outline;
1768+
});
1769+
}
1770+
17031771
@override
17041772
MaterialStateProperty<Color?> get overlayColor {
17051773
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
@@ -1802,19 +1870,6 @@ class _SwitchConfigM3 with _SwitchConfig {
18021870
@override
18031871
double get trackHeight => 32.0;
18041872

1805-
@override
1806-
MaterialStateProperty<Color?> get trackOutlineColor {
1807-
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
1808-
if (states.contains(MaterialState.selected)) {
1809-
return null;
1810-
}
1811-
if (states.contains(MaterialState.disabled)) {
1812-
return _colors.onSurface.withOpacity(0.12);
1813-
}
1814-
return _colors.outline;
1815-
});
1816-
}
1817-
18181873
@override
18191874
double get trackWidth => 52.0;
18201875

packages/flutter/lib/src/material/switch_list_tile.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class SwitchListTile extends StatelessWidget {
186186
this.onFocusChange,
187187
this.enableFeedback,
188188
this.hoverColor,
189+
this.trackOutlineColor,
189190
}) : _switchListTileType = _SwitchListTileType.material,
190191
assert(!isThreeLine || subtitle != null);
191192

@@ -228,6 +229,7 @@ class SwitchListTile extends StatelessWidget {
228229
this.onFocusChange,
229230
this.enableFeedback,
230231
this.hoverColor,
232+
this.trackOutlineColor,
231233
}) : _switchListTileType = _SwitchListTileType.adaptive,
232234
assert(!isThreeLine || subtitle != null);
233235

@@ -382,6 +384,16 @@ class SwitchListTile extends StatelessWidget {
382384
/// The color for the tile's [Material] when a pointer is hovering over it.
383385
final Color? hoverColor;
384386

387+
/// {@macro flutter.material.switch.trackOutlineColor}
388+
///
389+
/// The [ListTile] will be focused when this [SwitchListTile] requests focus,
390+
/// so the focused outline color of the switch will be ignored.
391+
///
392+
/// In Material 3, the outline color defaults to transparent in the selected
393+
/// state and [ColorScheme.outline] in the unselected state. In Material 2,
394+
/// the [Switch] track has no outline.
395+
final MaterialStateProperty<Color?>? trackOutlineColor;
396+
385397
@override
386398
Widget build(BuildContext context) {
387399
final Widget control;
@@ -399,6 +411,7 @@ class SwitchListTile extends StatelessWidget {
399411
inactiveThumbColor: inactiveThumbColor,
400412
autofocus: autofocus,
401413
onFocusChange: onFocusChange,
414+
trackOutlineColor: trackOutlineColor,
402415
);
403416
break;
404417

@@ -415,6 +428,7 @@ class SwitchListTile extends StatelessWidget {
415428
inactiveThumbColor: inactiveThumbColor,
416429
autofocus: autofocus,
417430
onFocusChange: onFocusChange,
431+
trackOutlineColor: trackOutlineColor,
418432
);
419433
}
420434

packages/flutter/lib/src/material/switch_theme.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class SwitchThemeData with Diagnosticable {
3939
const SwitchThemeData({
4040
this.thumbColor,
4141
this.trackColor,
42+
this.trackOutlineColor,
4243
this.materialTapTargetSize,
4344
this.mouseCursor,
4445
this.overlayColor,
@@ -56,6 +57,11 @@ class SwitchThemeData with Diagnosticable {
5657
/// If specified, overrides the default value of [Switch.trackColor].
5758
final MaterialStateProperty<Color?>? trackColor;
5859

60+
/// {@macro flutter.material.switch.trackOutlineColor}
61+
///
62+
/// If specified, overrides the default value of [Switch.trackOutlineColor].
63+
final MaterialStateProperty<Color?>? trackOutlineColor;
64+
5965
/// {@macro flutter.material.switch.materialTapTargetSize}
6066
///
6167
/// If specified, overrides the default value of
@@ -87,6 +93,7 @@ class SwitchThemeData with Diagnosticable {
8793
SwitchThemeData copyWith({
8894
MaterialStateProperty<Color?>? thumbColor,
8995
MaterialStateProperty<Color?>? trackColor,
96+
MaterialStateProperty<Color?>? trackOutlineColor,
9097
MaterialTapTargetSize? materialTapTargetSize,
9198
MaterialStateProperty<MouseCursor?>? mouseCursor,
9299
MaterialStateProperty<Color?>? overlayColor,
@@ -96,6 +103,7 @@ class SwitchThemeData with Diagnosticable {
96103
return SwitchThemeData(
97104
thumbColor: thumbColor ?? this.thumbColor,
98105
trackColor: trackColor ?? this.trackColor,
106+
trackOutlineColor: trackOutlineColor ?? this.trackOutlineColor,
99107
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
100108
mouseCursor: mouseCursor ?? this.mouseCursor,
101109
overlayColor: overlayColor ?? this.overlayColor,
@@ -111,6 +119,7 @@ class SwitchThemeData with Diagnosticable {
111119
return SwitchThemeData(
112120
thumbColor: MaterialStateProperty.lerp<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
113121
trackColor: MaterialStateProperty.lerp<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
122+
trackOutlineColor: MaterialStateProperty.lerp<Color?>(a?.trackOutlineColor, b?.trackOutlineColor, t, Color.lerp),
114123
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
115124
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
116125
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
@@ -123,6 +132,7 @@ class SwitchThemeData with Diagnosticable {
123132
int get hashCode => Object.hash(
124133
thumbColor,
125134
trackColor,
135+
trackOutlineColor,
126136
materialTapTargetSize,
127137
mouseCursor,
128138
overlayColor,
@@ -141,6 +151,7 @@ class SwitchThemeData with Diagnosticable {
141151
return other is SwitchThemeData
142152
&& other.thumbColor == thumbColor
143153
&& other.trackColor == trackColor
154+
&& other.trackOutlineColor == trackOutlineColor
144155
&& other.materialTapTargetSize == materialTapTargetSize
145156
&& other.mouseCursor == mouseCursor
146157
&& other.overlayColor == overlayColor
@@ -153,6 +164,7 @@ class SwitchThemeData with Diagnosticable {
153164
super.debugFillProperties(properties);
154165
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
155166
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
167+
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackOutlineColor', trackOutlineColor, defaultValue: null));
156168
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
157169
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
158170
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));

0 commit comments

Comments
 (0)