Skip to content

Commit 2449907

Browse files
authored
Fixed the color curve issue (#115188)
1 parent 436fb4c commit 2449907

File tree

2 files changed

+146
-19
lines changed

2 files changed

+146
-19
lines changed

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

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,15 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
620620
// During a drag we may have modified the curve, reset it if its possible
621621
// to do without visual discontinuation.
622622
if (position.value == 0.0 || position.value == 1.0) {
623-
position
624-
..curve = Curves.easeIn
625-
..reverseCurve = Curves.easeOut;
623+
if (Theme.of(context).useMaterial3) {
624+
position
625+
..curve = Curves.easeOutBack
626+
..reverseCurve = Curves.easeOutBack.flipped;
627+
} else {
628+
position
629+
..curve = Curves.easeIn
630+
..reverseCurve = Curves.easeOut;
631+
}
626632
}
627633
animateToValue();
628634
}
@@ -693,7 +699,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
693699

694700
void _handleDragEnd(DragEndDetails details) {
695701
if (position.value >= 0.5 != widget.value) {
696-
widget.onChanged!(!widget.value);
702+
widget.onChanged?.call(!widget.value);
697703
// Wait with finishing the animation until widget.value has changed to
698704
// !widget.value as part of the widget.onChanged call above.
699705
setState(() {
@@ -709,7 +715,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
709715
void _handleChanged(bool? value) {
710716
assert(value != null);
711717
assert(widget.onChanged != null);
712-
widget.onChanged!(value!);
718+
widget.onChanged?.call(value!);
713719
}
714720

715721
@override
@@ -727,11 +733,6 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
727733
final SwitchThemeData defaults = theme.useMaterial3 ? _SwitchDefaultsM3(context) : _SwitchDefaultsM2(context);
728734

729735
positionController.duration = Duration(milliseconds: switchConfig.toggleDuration);
730-
if (theme.useMaterial3) {
731-
position
732-
..curve = Curves.easeOutBack
733-
..reverseCurve = Curves.easeOutBack.flipped;
734-
}
735736

736737
// Colors need to be resolved in selected and non selected states separately
737738
// so that they can be lerped between.
@@ -780,12 +781,20 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
780781
?? defaults.overlayColor!.resolve(hoveredStates)!;
781782

782783
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
784+
final Color effectiveActivePressedThumbColor = widget.thumbColor?.resolve(activePressedStates)
785+
?? _widgetThumbColor.resolve(activePressedStates)
786+
?? switchTheme.thumbColor?.resolve(activePressedStates)
787+
?? defaults.thumbColor!.resolve(activePressedStates)!;
783788
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
784789
?? switchTheme.overlayColor?.resolve(activePressedStates)
785790
?? activeThumbColor?.withAlpha(kRadialReactionAlpha)
786791
?? defaults.overlayColor!.resolve(activePressedStates)!;
787792

788793
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
794+
final Color effectiveInactivePressedThumbColor = widget.thumbColor?.resolve(inactivePressedStates)
795+
?? _widgetThumbColor.resolve(inactivePressedStates)
796+
?? switchTheme.thumbColor?.resolve(inactivePressedStates)
797+
?? defaults.thumbColor!.resolve(inactivePressedStates)!;
789798
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
790799
?? switchTheme.overlayColor?.resolve(inactivePressedStates)
791800
?? inactiveThumbColor?.withAlpha(kRadialReactionAlpha)
@@ -830,6 +839,8 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
830839
..isHovered = states.contains(MaterialState.hovered)
831840
..activeColor = effectiveActiveThumbColor
832841
..inactiveColor = effectiveInactiveThumbColor
842+
..activePressedColor = effectiveActivePressedThumbColor
843+
..inactivePressedColor = effectiveInactivePressedThumbColor
833844
..activeThumbImage = widget.activeThumbImage
834845
..onActiveThumbImageError = widget.onActiveThumbImageError
835846
..inactiveThumbImage = widget.inactiveThumbImage
@@ -926,6 +937,28 @@ class _SwitchPainter extends ToggleablePainter {
926937
notifyListeners();
927938
}
928939

940+
Color get activePressedColor => _activePressedColor!;
941+
Color? _activePressedColor;
942+
set activePressedColor(Color? value) {
943+
assert(value != null);
944+
if (value == _activePressedColor) {
945+
return;
946+
}
947+
_activePressedColor = value;
948+
notifyListeners();
949+
}
950+
951+
Color get inactivePressedColor => _inactivePressedColor!;
952+
Color? _inactivePressedColor;
953+
set inactivePressedColor(Color? value) {
954+
assert(value != null);
955+
if (value == _inactivePressedColor) {
956+
return;
957+
}
958+
_inactivePressedColor = value;
959+
notifyListeners();
960+
}
961+
929962
double get activeThumbRadius => _activeThumbRadius!;
930963
double? _activeThumbRadius;
931964
set activeThumbRadius(double value) {
@@ -1180,7 +1213,7 @@ class _SwitchPainter extends ToggleablePainter {
11801213
visualPosition = currentValue;
11811214
break;
11821215
}
1183-
if (reaction.status == AnimationStatus.reverse && _stopPressAnimation == false) {
1216+
if (reaction.status == AnimationStatus.reverse && !_stopPressAnimation) {
11841217
_stopPressAnimation = true;
11851218
} else {
11861219
_stopPressAnimation = false;
@@ -1189,7 +1222,7 @@ class _SwitchPainter extends ToggleablePainter {
11891222
// To get the thumb radius when the press ends, the value can be any number
11901223
// between activeThumbRadius/inactiveThumbRadius and pressedThumbRadius.
11911224
if (!_stopPressAnimation) {
1192-
if (reaction.status == AnimationStatus.completed) {
1225+
if (reaction.isCompleted) {
11931226
// This happens when the thumb is dragged instead of being tapped.
11941227
_pressedInactiveThumbRadius = lerpDouble(inactiveThumbRadius, pressedThumbRadius, reaction.value);
11951228
_pressedActiveThumbRadius = lerpDouble(activeThumbRadius, pressedThumbRadius, reaction.value);
@@ -1248,10 +1281,10 @@ class _SwitchPainter extends ToggleablePainter {
12481281
}
12491282

12501283
Size thumbSize;
1251-
if (reaction.status == AnimationStatus.completed) {
1284+
if (reaction.isCompleted) {
12521285
thumbSize = Size.fromRadius(pressedThumbRadius);
12531286
} else {
1254-
if (position.status == AnimationStatus.dismissed || position.status == AnimationStatus.forward) {
1287+
if (position.isDismissed || position.status == AnimationStatus.forward) {
12551288
thumbSize = thumbSizeAnimation(true).value;
12561289
} else {
12571290
thumbSize = thumbSizeAnimation(false).value;
@@ -1262,10 +1295,21 @@ class _SwitchPainter extends ToggleablePainter {
12621295
final double inset = thumbOffset == null ? 0 : 1.0 - (currentValue - thumbOffset!).abs() * 2.0;
12631296
thumbSize = Size(thumbSize.width - inset, thumbSize.height - inset);
12641297

1265-
final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, currentValue)!;
1298+
final double colorValue = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn).value;
1299+
final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, colorValue)!;
12661300
final Color? trackOutlineColor = inactiveTrackOutlineColor == null ? null
1267-
: Color.lerp(inactiveTrackOutlineColor, Colors.transparent, currentValue);
1268-
final Color lerpedThumbColor = Color.lerp(inactiveColor, activeColor, currentValue)!;
1301+
: Color.lerp(inactiveTrackOutlineColor, Colors.transparent, colorValue);
1302+
Color lerpedThumbColor;
1303+
if (!reaction.isDismissed) {
1304+
lerpedThumbColor = Color.lerp(inactivePressedColor, activePressedColor, colorValue)!;
1305+
} else if (positionController.status == AnimationStatus.forward) {
1306+
lerpedThumbColor = Color.lerp(inactivePressedColor, activeColor, colorValue)!;
1307+
} else if (positionController.status == AnimationStatus.reverse) {
1308+
lerpedThumbColor = Color.lerp(inactiveColor, activePressedColor, colorValue)!;
1309+
} else {
1310+
lerpedThumbColor = Color.lerp(inactiveColor, activeColor, colorValue)!;
1311+
}
1312+
12691313
// Blend the thumb color against a `surfaceColor` background in case the
12701314
// thumbColor is not opaque. This way we do not see through the thumb to the
12711315
// track underneath.
@@ -1289,7 +1333,7 @@ class _SwitchPainter extends ToggleablePainter {
12891333
_paintThumbWith(
12901334
thumbPaintOffset,
12911335
canvas,
1292-
currentValue,
1336+
colorValue,
12931337
thumbColor,
12941338
thumbImage,
12951339
thumbErrorListener,
@@ -1381,7 +1425,7 @@ class _SwitchPainter extends ToggleablePainter {
13811425

13821426
thumbPainter.paint(
13831427
canvas,
1384-
thumbPaintOffset + Offset(0, inset),
1428+
thumbPaintOffset,
13851429
configuration.copyWith(size: thumbSize),
13861430
);
13871431

packages/flutter/test/material/switch_test.dart

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,6 +2438,89 @@ void main() {
24382438
);
24392439
});
24402440

2441+
testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async {
2442+
final ThemeData themeData = ThemeData(useMaterial3: true);
2443+
final ColorScheme colors = themeData.colorScheme;
2444+
Widget buildApp({bool enabled = true, bool value = true}) {
2445+
return MaterialApp(
2446+
theme: themeData,
2447+
home: Material(
2448+
child: Center(
2449+
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
2450+
return Switch(
2451+
value: value,
2452+
onChanged: enabled ? (bool newValue) {
2453+
setState(() {
2454+
value = newValue;
2455+
});
2456+
} : null,
2457+
);
2458+
}),
2459+
),
2460+
),
2461+
);
2462+
}
2463+
2464+
await tester.pumpWidget(buildApp());
2465+
await tester.press(find.byType(Switch));
2466+
await tester.pumpAndSettle();
2467+
2468+
expect(Material.of(tester.element(find.byType(Switch))),
2469+
paints..rrect(
2470+
color: colors.primary, // track color
2471+
style: PaintingStyle.fill,
2472+
)..rrect(
2473+
color: Colors.transparent, // track outline color
2474+
style: PaintingStyle.stroke,
2475+
)..rrect(color: colors.primaryContainer, rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))),
2476+
);
2477+
2478+
await tester.pumpWidget(Container());
2479+
await tester.pumpWidget(buildApp(value: false));
2480+
await tester.press(find.byType(Switch));
2481+
await tester.pumpAndSettle();
2482+
2483+
expect(Material.of(tester.element(find.byType(Switch))),
2484+
paints..rrect(
2485+
color: colors.surfaceVariant, // track color
2486+
style: PaintingStyle.fill
2487+
)..rrect(
2488+
color: colors.outline, // track outline color
2489+
style: PaintingStyle.stroke,
2490+
)..rrect(color: colors.onSurfaceVariant),
2491+
);
2492+
2493+
await tester.pumpWidget(Container());
2494+
await tester.pumpWidget(buildApp(enabled: false));
2495+
await tester.press(find.byType(Switch));
2496+
await tester.pumpAndSettle();
2497+
2498+
expect(Material.of(tester.element(find.byType(Switch))),
2499+
paints..rrect(
2500+
color: colors.onSurface.withOpacity(0.12), // track color
2501+
style: PaintingStyle.fill,
2502+
)..rrect(
2503+
color: Colors.transparent, // track outline color
2504+
style: PaintingStyle.stroke,
2505+
)..rrect(color: colors.surface.withOpacity(1.0)),
2506+
);
2507+
2508+
await tester.pumpWidget(Container());
2509+
await tester.pumpWidget(buildApp(enabled: false, value: false));
2510+
await tester.press(find.byType(Switch));
2511+
await tester.pumpAndSettle();
2512+
2513+
expect(Material.of(tester.element(find.byType(Switch))),
2514+
paints..rrect(
2515+
color: colors.surfaceVariant.withOpacity(0.12), // track color
2516+
style: PaintingStyle.fill,
2517+
)..rrect(
2518+
color: colors.onSurface.withOpacity(0.12), // track outline color
2519+
style: PaintingStyle.stroke,
2520+
)..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
2521+
);
2522+
}, variant: TargetPlatformVariant.mobile());
2523+
24412524
testWidgets('Switch thumb color resolves in active/enabled states - M3', (WidgetTester tester) async {
24422525
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
24432526
final ColorScheme colors = themeData.colorScheme;

0 commit comments

Comments
 (0)