@@ -64,6 +64,20 @@ const double _kInnerRadius = 4.5;
6464/// ** See code in examples/api/lib/material/radio/radio.0.dart **
6565/// {@end-tool}
6666///
67+ /// {@tool dartpad}
68+ /// Here is an example of how the you can override the default theme of a
69+ /// [Radio] with [WidgetStateProperty] .
70+ ///
71+ /// In this example:
72+ /// - The first [Radio] uses a custom [fillColor] that changes depending on whether
73+ /// the radio button is selected.
74+ /// - The second [Radio] applies a different [backgroundColor] based on its selection state.
75+ /// - The third [Radio] customizes the [side] property to display a different border color
76+ /// when selected or unselected.
77+ ///
78+ /// ** See code in examples/api/lib/material/radio/radio.1.dart **
79+ /// {@end-tool}
80+ ///
6781/// See also:
6882///
6983/// * [RadioListTile] , which combines this widget with a [ListTile] so that
@@ -107,6 +121,7 @@ class Radio<T> extends StatefulWidget {
107121 this .enabled,
108122 this .groupRegistry,
109123 this .backgroundColor,
124+ this .side,
110125 }) : _radioType = _RadioType .material,
111126 useCupertinoCheckmarkStyle = false ;
112127
@@ -155,6 +170,7 @@ class Radio<T> extends StatefulWidget {
155170 this .enabled,
156171 this .groupRegistry,
157172 this .backgroundColor,
173+ this .side,
158174 }) : _radioType = _RadioType .adaptive;
159175
160176 /// {@macro flutter.widget.RawRadio.value}
@@ -411,6 +427,21 @@ class Radio<T> extends StatefulWidget {
411427 /// If null, then it is transparent in all states.
412428 final WidgetStateProperty <Color ?>? backgroundColor;
413429
430+ /// The side for the circular border of the radio button, in all
431+ /// [WidgetState] s.
432+ ///
433+ /// This property can be a [BorderSide] or a [WidgetStateBorderSide] to leverage
434+ /// widget state resolution.
435+ ///
436+ /// Resolves in the following states:
437+ /// * [WidgetState.selected] .
438+ /// * [WidgetState.hovered] .
439+ /// * [WidgetState.focused] .
440+ /// * [WidgetState.disabled] .
441+ ///
442+ /// If null, then it defaults to a border using the fill color.
443+ final BorderSide ? side;
444+
414445 @override
415446 State <Radio <T >> createState () => _RadioState <T >();
416447}
@@ -517,6 +548,7 @@ class _RadioState<T> extends State<Radio<T>> {
517548 visualDensity: widget.visualDensity,
518549 materialTapTargetSize: widget.materialTapTargetSize,
519550 backgroundColor: widget.backgroundColor,
551+ side: widget.side,
520552 );
521553 },
522554 );
@@ -553,6 +585,7 @@ class _RadioPaint extends StatefulWidget {
553585 required this .visualDensity,
554586 required this .materialTapTargetSize,
555587 required this .backgroundColor,
588+ required this .side,
556589 });
557590
558591 final ToggleableStateMixin toggleableState;
@@ -565,6 +598,7 @@ class _RadioPaint extends StatefulWidget {
565598 final VisualDensity ? visualDensity;
566599 final MaterialTapTargetSize ? materialTapTargetSize;
567600 final WidgetStateProperty <Color ?>? backgroundColor;
601+ final BorderSide ? side;
568602
569603 @override
570604 State <StatefulWidget > createState () => _RadioPaintState ();
@@ -591,6 +625,16 @@ class _RadioPaintState extends State<_RadioPaint> {
591625 });
592626 }
593627
628+ BorderSide ? _resolveSide (BorderSide ? side, Set <MaterialState > states) {
629+ if (side is WidgetStateProperty ) {
630+ return WidgetStateProperty .resolveAs <BorderSide ?>(side, states);
631+ }
632+ if (! states.contains (WidgetState .selected)) {
633+ return side;
634+ }
635+ return null ;
636+ }
637+
594638 @override
595639 Widget build (BuildContext context) {
596640 final RadioThemeData radioTheme = RadioTheme .of (context);
@@ -678,6 +722,21 @@ class _RadioPaintState extends State<_RadioPaint> {
678722 ),
679723 };
680724 size += effectiveVisualDensity.baseSizeAdjustment;
725+ // TODO(ValentinVignal): Add side to RadioThemeData.
726+ final BorderSide activeSide =
727+ _resolveSide (widget.side, activeStates) ??
728+ BorderSide (
729+ color: effectiveActiveColor,
730+ width: 2.0 ,
731+ strokeAlign: BorderSide .strokeAlignCenter,
732+ );
733+ final BorderSide inactiveSide =
734+ _resolveSide (widget.side, inactiveStates) ??
735+ BorderSide (
736+ color: effectiveInactiveColor,
737+ width: 2.0 ,
738+ strokeAlign: BorderSide .strokeAlignCenter,
739+ );
681740
682741 return CustomPaint (
683742 size: size,
@@ -698,7 +757,9 @@ class _RadioPaintState extends State<_RadioPaint> {
698757 ..activeColor = effectiveActiveColor
699758 ..inactiveColor = effectiveInactiveColor
700759 ..activeBackgroundColor = activeBackgroundColor
701- ..inactiveBackgroundColor = inactiveBackgroundColor,
760+ ..inactiveBackgroundColor = inactiveBackgroundColor
761+ ..activeSide = activeSide
762+ ..inactiveSide = inactiveSide,
702763 );
703764 }
704765}
@@ -724,11 +785,36 @@ class _RadioPainter extends ToggleablePainter {
724785 notifyListeners ();
725786 }
726787
788+ BorderSide get inactiveSide => _inactiveSide! ;
789+ BorderSide ? _inactiveSide;
790+ set inactiveSide (BorderSide ? value) {
791+ if (_inactiveSide == value) {
792+ return ;
793+ }
794+ _inactiveSide = value;
795+ notifyListeners ();
796+ }
797+
798+ BorderSide get activeSide => _activeSide! ;
799+ BorderSide ? _activeSide;
800+ set activeSide (BorderSide ? value) {
801+ if (_activeSide == value) {
802+ return ;
803+ }
804+ _activeSide = value;
805+ notifyListeners ();
806+ }
807+
727808 @override
728809 void paint (Canvas canvas, Size size) {
729810 paintRadialReaction (canvas: canvas, origin: size.center (Offset .zero));
730811
731- final Offset center = (Offset .zero & size).center;
812+ final Rect rect = Offset .zero & size;
813+ final Offset center = rect.center;
814+ final Rect effectiveRect = (center & const Size .square (_kOuterRadius * 2 )).translate (
815+ - _kOuterRadius,
816+ - _kOuterRadius,
817+ );
732818
733819 // Background
734820 final Paint backgroundPaint =
@@ -738,12 +824,8 @@ class _RadioPainter extends ToggleablePainter {
738824 canvas.drawCircle (center, _kOuterRadius, backgroundPaint);
739825
740826 // Outer circle
741- final Paint outerCirclePaint =
742- Paint ()
743- ..color = Color .lerp (inactiveColor, activeColor, position.value)!
744- ..style = PaintingStyle .stroke
745- ..strokeWidth = 2.0 ;
746- canvas.drawCircle (center, _kOuterRadius, outerCirclePaint);
827+ final BorderSide side = BorderSide .lerp (inactiveSide, activeSide, position.value);
828+ CircleBorder (side: side).paint (canvas, effectiveRect);
747829
748830 // Inner circle
749831 if (! position.isDismissed) {
0 commit comments