Skip to content

Commit 06b87fb

Browse files
authored
Update Radio button to material 3 (#111774)
1 parent ce7789e commit 06b87fb

File tree

5 files changed

+568
-139
lines changed

5 files changed

+568
-139
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:gen_defaults/input_chip_template.dart';
3131
import 'package:gen_defaults/input_decorator_template.dart';
3232
import 'package:gen_defaults/navigation_bar_template.dart';
3333
import 'package:gen_defaults/navigation_rail_template.dart';
34+
import 'package:gen_defaults/radio_template.dart';
3435
import 'package:gen_defaults/surface_tint.dart';
3536
import 'package:gen_defaults/switch_template.dart';
3637
import 'package:gen_defaults/text_field_template.dart';
@@ -79,6 +80,7 @@ Future<void> main(List<String> args) async {
7980
'navigation_drawer.json',
8081
'navigation_rail.json',
8182
'palette.json',
83+
'radio_button.json',
8284
'segmented_button_outlined.json',
8385
'shape.json',
8486
'slider.json',
@@ -124,6 +126,7 @@ Future<void> main(List<String> args) async {
124126
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
125127
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
126128
NavigationRailTemplate('NavigationRail', '$materialLib/navigation_rail.dart', tokens).updateFile();
129+
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
127130
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
128131
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
129132
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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 'template.dart';
6+
7+
class RadioTemplate extends TokenTemplate {
8+
const RadioTemplate(super.blockName, super.fileName, super.tokens, {
9+
super.colorSchemePrefix = '_colors.',
10+
});
11+
12+
@override
13+
String generate() => '''
14+
class _RadioDefaultsM3 extends RadioThemeData {
15+
_RadioDefaultsM3(this.context);
16+
17+
final BuildContext context;
18+
late final ThemeData _theme = Theme.of(context);
19+
late final ColorScheme _colors = _theme.colorScheme;
20+
21+
@override
22+
MaterialStateProperty<Color> get fillColor {
23+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
24+
if (states.contains(MaterialState.selected)) {
25+
if (states.contains(MaterialState.disabled)) {
26+
return ${componentColor('md.comp.radio-button.disabled.selected.icon')};
27+
}
28+
if (states.contains(MaterialState.pressed)) {
29+
return ${componentColor('md.comp.radio-button.selected.pressed.icon')};
30+
}
31+
if (states.contains(MaterialState.hovered)) {
32+
return ${componentColor('md.comp.radio-button.selected.hover.icon')};
33+
}
34+
if (states.contains(MaterialState.focused)) {
35+
return ${componentColor('md.comp.radio-button.selected.focus.icon')};
36+
}
37+
return ${componentColor('md.comp.radio-button.selected.icon')};
38+
}
39+
if (states.contains(MaterialState.disabled)) {
40+
return ${componentColor('md.comp.radio-button.disabled.unselected.icon')};
41+
}
42+
if (states.contains(MaterialState.pressed)) {
43+
return ${componentColor('md.comp.radio-button.unselected.pressed.icon')};
44+
}
45+
if (states.contains(MaterialState.hovered)) {
46+
return ${componentColor('md.comp.radio-button.unselected.hover.icon')};
47+
}
48+
if (states.contains(MaterialState.focused)) {
49+
return ${componentColor('md.comp.radio-button.unselected.focus.icon')};
50+
}
51+
return ${componentColor('md.comp.radio-button.unselected.icon')};
52+
});
53+
}
54+
55+
@override
56+
MaterialStateProperty<Color> get overlayColor {
57+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
58+
if (states.contains(MaterialState.selected)) {
59+
if (states.contains(MaterialState.pressed)) {
60+
return ${componentColor('md.comp.radio-button.selected.pressed.state-layer')};
61+
}
62+
if (states.contains(MaterialState.hovered)) {
63+
return ${componentColor('md.comp.radio-button.selected.hover.state-layer')};
64+
}
65+
if (states.contains(MaterialState.focused)) {
66+
return ${componentColor('md.comp.radio-button.selected.focus.state-layer')};
67+
}
68+
return Colors.transparent;
69+
}
70+
if (states.contains(MaterialState.pressed)) {
71+
return ${componentColor('md.comp.radio-button.unselected.pressed.state-layer')};
72+
}
73+
if (states.contains(MaterialState.hovered)) {
74+
return ${componentColor('md.comp.radio-button.unselected.hover.state-layer')};
75+
}
76+
if (states.contains(MaterialState.focused)) {
77+
return ${componentColor('md.comp.radio-button.unselected.focus.state-layer')};
78+
}
79+
return Colors.transparent;
80+
});
81+
}
82+
83+
@override
84+
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
85+
86+
@override
87+
VisualDensity get visualDensity => _theme.visualDensity;
88+
}
89+
''';
90+
}

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

Lines changed: 163 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'package:flutter/widgets.dart';
66

7+
import 'color_scheme.dart';
8+
import 'colors.dart';
79
import 'constants.dart';
810
import 'debug.dart';
911
import 'material_state.dart';
@@ -360,30 +362,17 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
360362
});
361363
}
362364

363-
MaterialStateProperty<Color> get _defaultFillColor {
364-
final ThemeData themeData = Theme.of(context);
365-
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
366-
if (states.contains(MaterialState.disabled)) {
367-
return themeData.disabledColor;
368-
}
369-
if (states.contains(MaterialState.selected)) {
370-
return themeData.colorScheme.secondary;
371-
}
372-
return themeData.unselectedWidgetColor;
373-
});
374-
}
375-
376365
@override
377366
Widget build(BuildContext context) {
378367
assert(debugCheckHasMaterial(context));
379-
final ThemeData themeData = Theme.of(context);
380368
final RadioThemeData radioTheme = RadioTheme.of(context);
369+
final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
381370
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
382371
?? radioTheme.materialTapTargetSize
383-
?? themeData.materialTapTargetSize;
372+
?? defaults.materialTapTargetSize!;
384373
final VisualDensity effectiveVisualDensity = widget.visualDensity
385374
?? radioTheme.visualDensity
386-
?? themeData.visualDensity;
375+
?? defaults.visualDensity!;
387376
Size size;
388377
switch (effectiveMaterialTapTargetSize) {
389378
case MaterialTapTargetSize.padded:
@@ -405,36 +394,47 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
405394
// so that they can be lerped between.
406395
final Set<MaterialState> activeStates = states..add(MaterialState.selected);
407396
final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected);
408-
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
397+
final Color? activeColor = widget.fillColor?.resolve(activeStates)
409398
?? _widgetFillColor.resolve(activeStates)
410-
?? radioTheme.fillColor?.resolve(activeStates)
411-
?? _defaultFillColor.resolve(activeStates);
412-
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
399+
?? radioTheme.fillColor?.resolve(activeStates);
400+
final Color effectiveActiveColor = activeColor ?? defaults.fillColor!.resolve(activeStates)!;
401+
final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates)
413402
?? _widgetFillColor.resolve(inactiveStates)
414-
?? radioTheme.fillColor?.resolve(inactiveStates)
415-
?? _defaultFillColor.resolve(inactiveStates);
403+
?? radioTheme.fillColor?.resolve(inactiveStates);
404+
final Color effectiveInactiveColor = inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
416405

417406
final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
418-
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
407+
Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
419408
?? widget.focusColor
420409
?? radioTheme.overlayColor?.resolve(focusedStates)
421-
?? themeData.focusColor;
410+
?? defaults.overlayColor!.resolve(focusedStates)!;
422411

423412
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
424-
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
425-
?? widget.hoverColor
426-
?? radioTheme.overlayColor?.resolve(hoveredStates)
427-
?? themeData.hoverColor;
413+
Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
414+
?? widget.hoverColor
415+
?? radioTheme.overlayColor?.resolve(hoveredStates)
416+
?? defaults.overlayColor!.resolve(hoveredStates)!;
428417

429418
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
430419
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
431-
?? radioTheme.overlayColor?.resolve(activePressedStates)
432-
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
420+
?? radioTheme.overlayColor?.resolve(activePressedStates)
421+
?? activeColor?.withAlpha(kRadialReactionAlpha)
422+
?? defaults.overlayColor!.resolve(activePressedStates)!;
433423

434424
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
435425
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
436-
?? radioTheme.overlayColor?.resolve(inactivePressedStates)
437-
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
426+
?? radioTheme.overlayColor?.resolve(inactivePressedStates)
427+
?? inactiveColor?.withAlpha(kRadialReactionAlpha)
428+
?? defaults.overlayColor!.resolve(inactivePressedStates)!;
429+
430+
if (downPosition != null) {
431+
effectiveHoverOverlayColor = states.contains(MaterialState.selected)
432+
? effectiveActivePressedOverlayColor
433+
: effectiveInactivePressedOverlayColor;
434+
effectiveFocusOverlayColor = states.contains(MaterialState.selected)
435+
? effectiveActivePressedOverlayColor
436+
: effectiveInactivePressedOverlayColor;
437+
}
438438

439439
return Semantics(
440440
inMutuallyExclusiveGroup: true,
@@ -485,3 +485,134 @@ class _RadioPainter extends ToggleablePainter {
485485
}
486486
}
487487
}
488+
489+
// Hand coded defaults based on Material Design 2.
490+
class _RadioDefaultsM2 extends RadioThemeData {
491+
_RadioDefaultsM2(this.context);
492+
493+
final BuildContext context;
494+
late final ThemeData _theme = Theme.of(context);
495+
late final ColorScheme _colors = _theme.colorScheme;
496+
497+
@override
498+
MaterialStateProperty<Color> get fillColor {
499+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
500+
if (states.contains(MaterialState.disabled)) {
501+
return _theme.disabledColor;
502+
}
503+
if (states.contains(MaterialState.selected)) {
504+
return _colors.secondary;
505+
}
506+
return _theme.unselectedWidgetColor;
507+
});
508+
}
509+
510+
@override
511+
MaterialStateProperty<Color> get overlayColor {
512+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
513+
if (states.contains(MaterialState.pressed)) {
514+
return fillColor.resolve(states).withAlpha(kRadialReactionAlpha);
515+
}
516+
if (states.contains(MaterialState.focused)) {
517+
return _theme.focusColor;
518+
}
519+
if (states.contains(MaterialState.hovered)) {
520+
return _theme.hoverColor;
521+
}
522+
return Colors.transparent;
523+
});
524+
}
525+
526+
@override
527+
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
528+
529+
@override
530+
VisualDensity get visualDensity => _theme.visualDensity;
531+
}
532+
533+
// BEGIN GENERATED TOKEN PROPERTIES - Radio<T>
534+
535+
// Do not edit by hand. The code between the "BEGIN GENERATED" and
536+
// "END GENERATED" comments are generated from data in the Material
537+
// Design token database by the script:
538+
// dev/tools/gen_defaults/bin/gen_defaults.dart.
539+
540+
// Token database version: v0_132
541+
542+
class _RadioDefaultsM3 extends RadioThemeData {
543+
_RadioDefaultsM3(this.context);
544+
545+
final BuildContext context;
546+
late final ThemeData _theme = Theme.of(context);
547+
late final ColorScheme _colors = _theme.colorScheme;
548+
549+
@override
550+
MaterialStateProperty<Color> get fillColor {
551+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
552+
if (states.contains(MaterialState.selected)) {
553+
if (states.contains(MaterialState.disabled)) {
554+
return _colors.onSurface.withOpacity(0.38);
555+
}
556+
if (states.contains(MaterialState.pressed)) {
557+
return _colors.primary;
558+
}
559+
if (states.contains(MaterialState.hovered)) {
560+
return _colors.primary;
561+
}
562+
if (states.contains(MaterialState.focused)) {
563+
return _colors.primary;
564+
}
565+
return _colors.primary;
566+
}
567+
if (states.contains(MaterialState.disabled)) {
568+
return _colors.onSurface.withOpacity(0.38);
569+
}
570+
if (states.contains(MaterialState.pressed)) {
571+
return _colors.onSurface;
572+
}
573+
if (states.contains(MaterialState.hovered)) {
574+
return _colors.onSurface;
575+
}
576+
if (states.contains(MaterialState.focused)) {
577+
return _colors.onSurface;
578+
}
579+
return _colors.onSurfaceVariant;
580+
});
581+
}
582+
583+
@override
584+
MaterialStateProperty<Color> get overlayColor {
585+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
586+
if (states.contains(MaterialState.selected)) {
587+
if (states.contains(MaterialState.pressed)) {
588+
return _colors.onSurface.withOpacity(0.12);
589+
}
590+
if (states.contains(MaterialState.hovered)) {
591+
return _colors.primary.withOpacity(0.08);
592+
}
593+
if (states.contains(MaterialState.focused)) {
594+
return _colors.primary.withOpacity(0.12);
595+
}
596+
return Colors.transparent;
597+
}
598+
if (states.contains(MaterialState.pressed)) {
599+
return _colors.primary.withOpacity(0.12);
600+
}
601+
if (states.contains(MaterialState.hovered)) {
602+
return _colors.onSurface.withOpacity(0.08);
603+
}
604+
if (states.contains(MaterialState.focused)) {
605+
return _colors.onSurface.withOpacity(0.12);
606+
}
607+
return Colors.transparent;
608+
});
609+
}
610+
611+
@override
612+
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
613+
614+
@override
615+
VisualDensity get visualDensity => _theme.visualDensity;
616+
}
617+
618+
// END GENERATED TOKEN PROPERTIES - Radio<T>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,7 @@ class ThemeData with Diagnosticable {
12661266
/// * Lists: [ListTile]
12671267
/// * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar])
12681268
/// * [Navigation rail](https://m3.material.io/components/navigation-rail): [NavigationRail]
1269+
/// * Radio button: [Radio]
12691270
/// * Switch: [Switch]
12701271
/// * Top app bar: [AppBar]
12711272
///

0 commit comments

Comments
 (0)