Skip to content

Commit 1e696d3

Browse files
authored
Support theming CupertinoSwitchs (#116510)
* Introduce flag to maximally apply CupertinoTheme * add missing docs * add tests * fix docs * fix test
1 parent 921f077 commit 1e696d3

File tree

9 files changed

+228
-6
lines changed

9 files changed

+228
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// Flutter code sample for [Switch].
6+
import 'package:flutter/cupertino.dart';
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const SwitchApp());
10+
11+
class SwitchApp extends StatelessWidget {
12+
const SwitchApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
theme: ThemeData.light(useMaterial3: true).copyWith(
18+
// Use the ambient [CupetinoThemeData] to style all widgets which would
19+
// otherwise use iOS defaults.
20+
cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true),
21+
),
22+
home: Scaffold(
23+
appBar: AppBar(title: const Text('Switch Sample')),
24+
body: const Center(
25+
child: SwitchExample(),
26+
),
27+
),
28+
);
29+
}
30+
}
31+
32+
class SwitchExample extends StatefulWidget {
33+
const SwitchExample({super.key});
34+
35+
@override
36+
State<SwitchExample> createState() => _SwitchExampleState();
37+
}
38+
39+
class _SwitchExampleState extends State<SwitchExample> {
40+
bool light = true;
41+
42+
@override
43+
Widget build(BuildContext context) {
44+
return Column(
45+
mainAxisAlignment: MainAxisAlignment.center,
46+
children: <Widget>[
47+
Switch.adaptive(
48+
value: light,
49+
onChanged: (bool value) {
50+
setState(() {
51+
light = value;
52+
});
53+
},
54+
),
55+
Switch.adaptive(
56+
// Don't use the ambient [CupetinoThemeData] to style this switch.
57+
applyCupertinoTheme: false,
58+
value: light,
59+
onChanged: (bool value) {
60+
setState(() {
61+
light = value;
62+
});
63+
},
64+
),
65+
],
66+
);
67+
}
68+
}

examples/api/test/material/switch/switch.1_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/material.dart';
6-
import 'package:flutter_api_samples/material/switch/switch.0.dart' as example;
6+
import 'package:flutter_api_samples/material/switch/switch.1.dart' as example;
77
import 'package:flutter_test/flutter_test.dart';
88

99
void main() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/switch/switch.3.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Can toggle switch', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const example.SwitchApp(),
13+
);
14+
15+
final Finder switchFinder = find.byType(Switch).first;
16+
Switch materialSwitch = tester.widget<Switch>(switchFinder);
17+
expect(materialSwitch.value, true);
18+
19+
await tester.tap(switchFinder);
20+
await tester.pumpAndSettle();
21+
materialSwitch = tester.widget<Switch>(switchFinder);
22+
expect(materialSwitch.value, false);
23+
});
24+
}

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

+22-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
1414
import 'package:flutter/widgets.dart';
1515

1616
import 'colors.dart';
17+
import 'theme.dart';
1718
import 'thumb_painter.dart';
1819

1920
// Examples can assume:
@@ -72,6 +73,7 @@ class CupertinoSwitch extends StatefulWidget {
7273
this.activeColor,
7374
this.trackColor,
7475
this.thumbColor,
76+
this.applyTheme,
7577
this.dragStartBehavior = DragStartBehavior.start,
7678
}) : assert(value != null),
7779
assert(dragStartBehavior != null);
@@ -105,13 +107,15 @@ class CupertinoSwitch extends StatefulWidget {
105107
/// ```
106108
final ValueChanged<bool>? onChanged;
107109

108-
/// The color to use when this switch is on.
110+
/// The color to use for the track when the switch is on.
109111
///
110-
/// Defaults to [CupertinoColors.systemGreen] when null and ignores
111-
/// the [CupertinoTheme] in accordance to native iOS behavior.
112+
/// If null and [applyTheme] is false, defaults to [CupertinoColors.systemGreen]
113+
/// in accordance to native iOS behavior. Otherwise, defaults to
114+
/// [CupertinoThemeData.primaryColor].
112115
final Color? activeColor;
113116

114-
/// The color to use for the background when the switch is off.
117+
118+
/// The color to use for the track when the switch is off.
115119
///
116120
/// Defaults to [CupertinoColors.secondarySystemFill] when null.
117121
final Color? trackColor;
@@ -121,6 +125,16 @@ class CupertinoSwitch extends StatefulWidget {
121125
/// Defaults to [CupertinoColors.white] when null.
122126
final Color? thumbColor;
123127

128+
/// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
129+
/// Whether to apply the ambient [CupertinoThemeData].
130+
///
131+
/// If true, the track uses [CupertinoThemeData.primaryColor] for the track
132+
/// when the switch is on.
133+
///
134+
/// Defaults to [CupertinoThemeData.applyThemeToAll].
135+
/// {@endtemplate}
136+
final bool? applyTheme;
137+
124138
/// {@template flutter.cupertino.CupertinoSwitch.dragStartBehavior}
125139
/// Determines the way that drag start behavior is handled.
126140
///
@@ -310,6 +324,7 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
310324

311325
@override
312326
Widget build(BuildContext context) {
327+
final CupertinoThemeData theme = CupertinoTheme.of(context);
313328
if (needsPositionAnimation) {
314329
_resumePositionAnimation();
315330
}
@@ -320,7 +335,9 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
320335
child: _CupertinoSwitchRenderObjectWidget(
321336
value: widget.value,
322337
activeColor: CupertinoDynamicColor.resolve(
323-
widget.activeColor ?? CupertinoColors.systemGreen,
338+
widget.activeColor
339+
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
340+
?? CupertinoColors.systemGreen,
324341
context,
325342
),
326343
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),

packages/flutter/lib/src/cupertino/theme.dart

+38
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults(
2222
// Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616.
2323
),
2424
CupertinoColors.systemBackground,
25+
false,
2526
_CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray),
2627
);
2728

@@ -172,13 +173,15 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
172173
CupertinoTextThemeData? textTheme,
173174
Color? barBackgroundColor,
174175
Color? scaffoldBackgroundColor,
176+
bool? applyThemeToAll,
175177
}) : this.raw(
176178
brightness,
177179
primaryColor,
178180
primaryContrastingColor,
179181
textTheme,
180182
barBackgroundColor,
181183
scaffoldBackgroundColor,
184+
applyThemeToAll,
182185
);
183186

184187
/// Same as the default constructor but with positional arguments to avoid
@@ -193,13 +196,15 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
193196
CupertinoTextThemeData? textTheme,
194197
Color? barBackgroundColor,
195198
Color? scaffoldBackgroundColor,
199+
bool? applyThemeToAll,
196200
) : this._rawWithDefaults(
197201
brightness,
198202
primaryColor,
199203
primaryContrastingColor,
200204
textTheme,
201205
barBackgroundColor,
202206
scaffoldBackgroundColor,
207+
applyThemeToAll,
203208
_kDefaultTheme,
204209
);
205210

@@ -210,6 +215,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
210215
CupertinoTextThemeData? textTheme,
211216
Color? barBackgroundColor,
212217
Color? scaffoldBackgroundColor,
218+
bool? applyThemeToAll,
213219
this._defaults,
214220
) : super(
215221
brightness: brightness,
@@ -218,6 +224,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
218224
textTheme: textTheme,
219225
barBackgroundColor: barBackgroundColor,
220226
scaffoldBackgroundColor: scaffoldBackgroundColor,
227+
applyThemeToAll: applyThemeToAll,
221228
);
222229

223230
final _CupertinoThemeDefaults _defaults;
@@ -239,6 +246,9 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
239246
@override
240247
Color get scaffoldBackgroundColor => super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
241248

249+
@override
250+
bool get applyThemeToAll => super.applyThemeToAll ?? _defaults.applyThemeToAll;
251+
242252
@override
243253
NoDefaultCupertinoThemeData noDefault() {
244254
return NoDefaultCupertinoThemeData(
@@ -248,6 +258,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
248258
textTheme: super.textTheme,
249259
barBackgroundColor: super.barBackgroundColor,
250260
scaffoldBackgroundColor: super.scaffoldBackgroundColor,
261+
applyThemeToAll: super.applyThemeToAll,
251262
);
252263
}
253264

@@ -262,6 +273,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
262273
super.textTheme?.resolveFrom(context),
263274
convertColor(super.barBackgroundColor),
264275
convertColor(super.scaffoldBackgroundColor),
276+
applyThemeToAll,
265277
_defaults.resolveFrom(context, super.textTheme == null),
266278
);
267279
}
@@ -274,6 +286,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
274286
CupertinoTextThemeData? textTheme,
275287
Color? barBackgroundColor,
276288
Color? scaffoldBackgroundColor,
289+
bool? applyThemeToAll,
277290
}) {
278291
return CupertinoThemeData._rawWithDefaults(
279292
brightness ?? super.brightness,
@@ -282,6 +295,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
282295
textTheme ?? super.textTheme,
283296
barBackgroundColor ?? super.barBackgroundColor,
284297
scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
298+
applyThemeToAll ?? super.applyThemeToAll,
285299
_defaults,
286300
);
287301
}
@@ -295,6 +309,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
295309
properties.add(createCupertinoColorProperty('primaryContrastingColor', primaryContrastingColor, defaultValue: defaultData.primaryContrastingColor));
296310
properties.add(createCupertinoColorProperty('barBackgroundColor', barBackgroundColor, defaultValue: defaultData.barBackgroundColor));
297311
properties.add(createCupertinoColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
312+
properties.add(DiagnosticsProperty<bool>('applyThemeToAll', applyThemeToAll, defaultValue: defaultData.applyThemeToAll));
298313
textTheme.debugFillProperties(properties);
299314
}
300315
}
@@ -322,6 +337,7 @@ class NoDefaultCupertinoThemeData {
322337
this.textTheme,
323338
this.barBackgroundColor,
324339
this.scaffoldBackgroundColor,
340+
this.applyThemeToAll,
325341
});
326342

327343
/// The brightness override for Cupertino descendants.
@@ -389,6 +405,22 @@ class NoDefaultCupertinoThemeData {
389405
/// Defaults to [CupertinoColors.systemBackground].
390406
final Color? scaffoldBackgroundColor;
391407

408+
/// Flag to apply this theme to all descendant Cupertino widgets.
409+
///
410+
/// Certain Cupertino widgets previously didn't use theming, matching past
411+
/// versions of iOS. For example, [CupertinoSwitch]s always used
412+
/// [CupertinoColors.systemGreen] when active.
413+
///
414+
/// Today, however, these widgets can indeed be themed on iOS. Moreover on
415+
/// macOS, the accent color is reflected in these widgets. Turning this flag
416+
/// on ensures that descendant Cupertino widgets will be themed accordingly.
417+
///
418+
/// This flag currently applies to the following widgets:
419+
/// - [CupertinoSwitch] & [Switch.adaptive]
420+
///
421+
/// Defaults to false.
422+
final bool? applyThemeToAll;
423+
392424
/// Returns an instance of the theme data whose property getters only return
393425
/// the construction time specifications with no derived values.
394426
///
@@ -412,6 +444,7 @@ class NoDefaultCupertinoThemeData {
412444
textTheme: textTheme?.resolveFrom(context),
413445
barBackgroundColor: convertColor(barBackgroundColor),
414446
scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor),
447+
applyThemeToAll: applyThemeToAll,
415448
);
416449
}
417450

@@ -428,6 +461,7 @@ class NoDefaultCupertinoThemeData {
428461
CupertinoTextThemeData? textTheme,
429462
Color? barBackgroundColor ,
430463
Color? scaffoldBackgroundColor,
464+
bool? applyThemeToAll,
431465
}) {
432466
return NoDefaultCupertinoThemeData(
433467
brightness: brightness ?? this.brightness,
@@ -436,6 +470,7 @@ class NoDefaultCupertinoThemeData {
436470
textTheme: textTheme ?? this.textTheme,
437471
barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor,
438472
scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
473+
applyThemeToAll: applyThemeToAll ?? this.applyThemeToAll,
439474
);
440475
}
441476
}
@@ -448,6 +483,7 @@ class _CupertinoThemeDefaults {
448483
this.primaryContrastingColor,
449484
this.barBackgroundColor,
450485
this.scaffoldBackgroundColor,
486+
this.applyThemeToAll,
451487
this.textThemeDefaults,
452488
);
453489

@@ -456,6 +492,7 @@ class _CupertinoThemeDefaults {
456492
final Color primaryContrastingColor;
457493
final Color barBackgroundColor;
458494
final Color scaffoldBackgroundColor;
495+
final bool applyThemeToAll;
459496
final _CupertinoTextThemeDefaults textThemeDefaults;
460497

461498
_CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) {
@@ -467,6 +504,7 @@ class _CupertinoThemeDefaults {
467504
convertColor(primaryContrastingColor),
468505
convertColor(barBackgroundColor),
469506
convertColor(scaffoldBackgroundColor),
507+
applyThemeToAll,
470508
resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults,
471509
);
472510
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class Switch extends StatelessWidget {
117117
this.onFocusChange,
118118
this.autofocus = false,
119119
}) : _switchType = _SwitchType.material,
120+
applyCupertinoTheme = false,
120121
assert(dragStartBehavior != null),
121122
assert(activeThumbImage != null || onActiveThumbImageError == null),
122123
assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
@@ -161,6 +162,7 @@ class Switch extends StatelessWidget {
161162
this.focusNode,
162163
this.onFocusChange,
163164
this.autofocus = false,
165+
this.applyCupertinoTheme,
164166
}) : assert(autofocus != null),
165167
assert(activeThumbImage != null || onActiveThumbImageError == null),
166168
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
@@ -381,6 +383,9 @@ class Switch extends StatelessWidget {
381383

382384
final _SwitchType _switchType;
383385

386+
/// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
387+
final bool? applyCupertinoTheme;
388+
384389
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
385390
final DragStartBehavior dragStartBehavior;
386391

@@ -495,6 +500,7 @@ class Switch extends StatelessWidget {
495500
onChanged: onChanged,
496501
activeColor: activeColor,
497502
trackColor: inactiveTrackColor,
503+
applyTheme: applyCupertinoTheme,
498504
),
499505
),
500506
);

0 commit comments

Comments
 (0)