diff --git a/dev/tools/gen_defaults/lib/radio_template.dart b/dev/tools/gen_defaults/lib/radio_template.dart index 7ddda05a41d..dc055e8371c 100644 --- a/dev/tools/gen_defaults/lib/radio_template.dart +++ b/dev/tools/gen_defaults/lib/radio_template.dart @@ -88,6 +88,10 @@ class _RadioDefaultsM3 extends RadioThemeData { @override VisualDensity get visualDensity => _theme.visualDensity; + + @override + WidgetStateProperty get backgroundColor => + WidgetStateProperty.all(Colors.transparent); } '''; } diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index 93d792672e4..dbe7f964963 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -418,6 +418,7 @@ class Radio extends StatefulWidget { /// {@endtemplate} final bool? enabled; + /// {@template flutter.material.Radio.backgroundColor} /// The color of the background of the radio button, in all [WidgetState]s. /// /// Resolves in the following states: @@ -427,6 +428,7 @@ class Radio extends StatefulWidget { /// * [WidgetState.disabled]. /// /// If null, then it is transparent in all states. + /// {@endtemplate} final WidgetStateProperty? backgroundColor; /// The side for the circular border of the radio button, in all @@ -673,11 +675,14 @@ class _RadioPaintState extends State<_RadioPaint> { radioTheme.fillColor?.resolve(inactiveStates); final Color effectiveInactiveColor = inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!; - // TODO(ValentinVignal): Add backgroundColor to RadioThemeData. final Color activeBackgroundColor = - widget.backgroundColor?.resolve(activeStates) ?? Colors.transparent; + widget.backgroundColor?.resolve(activeStates) ?? + radioTheme.backgroundColor?.resolve(activeStates) ?? + defaults.backgroundColor!.resolve(activeStates)!; final Color inactiveBackgroundColor = - widget.backgroundColor?.resolve(inactiveStates) ?? Colors.transparent; + widget.backgroundColor?.resolve(inactiveStates) ?? + radioTheme.backgroundColor?.resolve(inactiveStates) ?? + defaults.backgroundColor!.resolve(inactiveStates)!; final Set focusedStates = widget.toggleableState.states..add(MaterialState.focused); @@ -908,6 +913,10 @@ class _RadioDefaultsM2 extends RadioThemeData { @override VisualDensity get visualDensity => _theme.visualDensity; + + @override + WidgetStateProperty get backgroundColor => + WidgetStateProperty.all(Colors.transparent); } // BEGIN GENERATED TOKEN PROPERTIES - Radio @@ -992,6 +1001,10 @@ class _RadioDefaultsM3 extends RadioThemeData { @override VisualDensity get visualDensity => _theme.visualDensity; + + @override + WidgetStateProperty get backgroundColor => + WidgetStateProperty.all(Colors.transparent); } // dart format on diff --git a/packages/flutter/lib/src/material/radio_theme.dart b/packages/flutter/lib/src/material/radio_theme.dart index c8f522e3b43..4f44f3ade9d 100644 --- a/packages/flutter/lib/src/material/radio_theme.dart +++ b/packages/flutter/lib/src/material/radio_theme.dart @@ -49,6 +49,7 @@ class RadioThemeData with Diagnosticable { this.splashRadius, this.materialTapTargetSize, this.visualDensity, + this.backgroundColor, }); /// {@macro flutter.widget.RawRadio.mouseCursor} @@ -86,6 +87,9 @@ class RadioThemeData with Diagnosticable { /// default value is the value of [ThemeData.visualDensity]. final VisualDensity? visualDensity; + /// {@macro flutter.material.Radio.backgroundColor} + final WidgetStateProperty? backgroundColor; + /// Creates a copy of this object but with the given fields replaced with the /// new values. RadioThemeData copyWith({ @@ -95,6 +99,7 @@ class RadioThemeData with Diagnosticable { double? splashRadius, MaterialTapTargetSize? materialTapTargetSize, VisualDensity? visualDensity, + WidgetStateProperty? backgroundColor, }) { return RadioThemeData( mouseCursor: mouseCursor ?? this.mouseCursor, @@ -103,6 +108,7 @@ class RadioThemeData with Diagnosticable { splashRadius: splashRadius ?? this.splashRadius, materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize, visualDensity: visualDensity ?? this.visualDensity, + backgroundColor: backgroundColor ?? this.backgroundColor, ); } @@ -125,6 +131,12 @@ class RadioThemeData with Diagnosticable { ), splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t), visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity, + backgroundColor: WidgetStateProperty.lerp( + a?.backgroundColor, + b?.backgroundColor, + t, + Color.lerp, + ), ); } @@ -136,6 +148,7 @@ class RadioThemeData with Diagnosticable { splashRadius, materialTapTargetSize, visualDensity, + backgroundColor, ); @override @@ -152,7 +165,8 @@ class RadioThemeData with Diagnosticable { other.overlayColor == overlayColor && other.splashRadius == splashRadius && other.materialTapTargetSize == materialTapTargetSize && - other.visualDensity == visualDensity; + other.visualDensity == visualDensity && + other.backgroundColor == backgroundColor; } @override @@ -190,6 +204,13 @@ class RadioThemeData with Diagnosticable { properties.add( DiagnosticsProperty('visualDensity', visualDensity, defaultValue: null), ); + properties.add( + DiagnosticsProperty>( + 'backgroundColor', + backgroundColor, + defaultValue: null, + ), + ); } } diff --git a/packages/flutter/test/material/radio_theme_test.dart b/packages/flutter/test/material/radio_theme_test.dart index 8e6f4fec30d..9f2d3d64904 100644 --- a/packages/flutter/test/material/radio_theme_test.dart +++ b/packages/flutter/test/material/radio_theme_test.dart @@ -27,6 +27,7 @@ void main() { expect(themeData.splashRadius, null); expect(themeData.materialTapTargetSize, null); expect(themeData.visualDensity, null); + expect(themeData.backgroundColor, null); const RadioTheme theme = RadioTheme(data: RadioThemeData(), child: SizedBox()); expect(theme.data.mouseCursor, null); @@ -35,6 +36,7 @@ void main() { expect(theme.data.splashRadius, null); expect(theme.data.materialTapTargetSize, null); expect(theme.data.visualDensity, null); + expect(theme.data.backgroundColor, null); }); testWidgets('Default RadioThemeData debugFillProperties', (WidgetTester tester) async { @@ -47,18 +49,19 @@ void main() { .map((DiagnosticsNode node) => node.toString()) .toList(); - expect(description, []); + expect(description, const []); }); testWidgets('RadioThemeData implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const RadioThemeData( - mouseCursor: MaterialStatePropertyAll(SystemMouseCursors.click), - fillColor: MaterialStatePropertyAll(Color(0xfffffff0)), - overlayColor: MaterialStatePropertyAll(Color(0xfffffff1)), + mouseCursor: WidgetStatePropertyAll(SystemMouseCursors.click), + fillColor: WidgetStatePropertyAll(Color(0xfffffff0)), + overlayColor: WidgetStatePropertyAll(Color(0xfffffff1)), splashRadius: 1.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.standard, + backgroundColor: WidgetStatePropertyAll(Color(0xfffffff2)), ).debugFillProperties(builder); final List description = @@ -76,6 +79,7 @@ void main() { 'splashRadius: 1.0', 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap', 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)', + 'backgroundColor: WidgetStatePropertyAll(${const Color(0xfffffff2)})', ]), ); }); @@ -88,6 +92,8 @@ void main() { const Color selectedFillColor = Color(0xfffffff1); const Color focusOverlayColor = Color(0xfffffff2); const Color hoverOverlayColor = Color(0xfffffff3); + const Color defaultBackgroundColor = Color(0xfffffff4); + const Color selectedBackgroundColor = Color(0xfffffff5); const double splashRadius = 1.0; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const VisualDensity visualDensity = VisualDensity(horizontal: 1, vertical: 1); @@ -96,18 +102,18 @@ void main() { return MaterialApp( theme: ThemeData( radioTheme: RadioThemeData( - mouseCursor: const MaterialStatePropertyAll(mouseCursor), - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.selected)) { + mouseCursor: const WidgetStatePropertyAll(mouseCursor), + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { return selectedFillColor; } return defaultFillColor; }), - overlayColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.focused)) { + overlayColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.focused)) { return focusOverlayColor; } - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.hovered)) { return hoverOverlayColor; } return null; @@ -115,6 +121,12 @@ void main() { splashRadius: splashRadius, materialTapTargetSize: materialTapTargetSize, visualDensity: visualDensity, + backgroundColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return selectedBackgroundColor; + } + return defaultBackgroundColor; + }), ), ), home: Scaffold( @@ -134,7 +146,7 @@ void main() { expect( _getRadioMaterial(tester), paints - ..circle(color: Colors.transparent) + ..circle(color: defaultBackgroundColor) ..circle(color: defaultFillColor), ); // Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity. @@ -146,7 +158,7 @@ void main() { expect( _getRadioMaterial(tester), paints - ..circle(color: Colors.transparent) + ..circle(color: selectedBackgroundColor) ..circle(color: selectedFillColor), ); @@ -177,6 +189,8 @@ void main() { const Color themeSelectedFillColor = Color(0xfffffff1); const Color themeFocusOverlayColor = Color(0xfffffff2); const Color themeHoverOverlayColor = Color(0xfffffff3); + const Color themeDefaultBackgroundColor = Color(0xfffffff4); + const Color themeSelectedBackgroundColor = Color(0xfffffff5); const double themeSplashRadius = 1.0; const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; const VisualDensity themeVisualDensity = VisualDensity.standard; @@ -186,6 +200,8 @@ void main() { const Color selectedFillColor = Color(0xfffffff1); const Color focusColor = Color(0xfffffff2); const Color hoverColor = Color(0xfffffff3); + const Color defaultBackgroundColor = Color(0xfffffff4); + const Color selectedBackgroundColor = Color(0xfffffff5); const double splashRadius = 2.0; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const VisualDensity visualDensity = VisualDensity(horizontal: 1, vertical: 1); @@ -194,18 +210,18 @@ void main() { return MaterialApp( theme: ThemeData( radioTheme: RadioThemeData( - mouseCursor: const MaterialStatePropertyAll(themeMouseCursor), - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.selected)) { + mouseCursor: const WidgetStatePropertyAll(themeMouseCursor), + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { return themeSelectedFillColor; } return themeDefaultFillColor; }), - overlayColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.focused)) { + overlayColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.focused)) { return themeFocusOverlayColor; } - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.hovered)) { return themeHoverOverlayColor; } return null; @@ -213,6 +229,12 @@ void main() { splashRadius: themeSplashRadius, materialTapTargetSize: themeMaterialTapTargetSize, visualDensity: themeVisualDensity, + backgroundColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return themeSelectedBackgroundColor; + } + return themeDefaultBackgroundColor; + }), ), ), home: Scaffold( @@ -222,8 +244,8 @@ void main() { groupValue: 0, autofocus: autofocus, mouseCursor: mouseCursor, - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.selected)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { return selectedFillColor; } return defaultFillColor; @@ -233,6 +255,12 @@ void main() { splashRadius: splashRadius, materialTapTargetSize: materialTapTargetSize, visualDensity: visualDensity, + backgroundColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return selectedBackgroundColor; + } + return defaultBackgroundColor; + }), ), ), ); @@ -244,7 +272,7 @@ void main() { expect( _getRadioMaterial(tester), paints - ..circle(color: Colors.transparent) + ..circle(color: defaultBackgroundColor) ..circle(color: defaultFillColor), ); // Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity. @@ -256,7 +284,7 @@ void main() { expect( _getRadioMaterial(tester), paints - ..circle(color: Colors.transparent) + ..circle(color: selectedBackgroundColor) ..circle(color: selectedFillColor), );