Skip to content

Instantly share code, notes, and snippets.

@gladson
Created December 20, 2021 10:57
Show Gist options
  • Save gladson/965ca797cf6eb89cd39cf7fa5a8afa4b to your computer and use it in GitHub Desktop.
Save gladson/965ca797cf6eb89cd39cf7fa5a8afa4b to your computer and use it in GitHub Desktop.
Custom Radio Button - Flutter
import 'package:flutter/material.dart';
const double _kEdgeSize = Checkbox.width * 1.5;
const double _kStrokeWidth = 0.0;
class SRadio<T> extends StatefulWidget {
const SRadio({
Key? key,
required this.value,
required this.groupValue,
required this.onChanged,
this.mouseCursor,
this.toggleable = false,
this.activeColor,
this.fillColor,
this.checkColor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.materialTapTargetSize,
this.visualDensity,
this.focusNode,
this.autofocus = false,
this.shape,
this.side,
}) : super(key: key);
final T value;
final T? groupValue;
final ValueChanged<T?>? onChanged;
final MouseCursor? mouseCursor;
final Color? checkColor;
final bool toggleable;
final Color? activeColor;
final MaterialStateProperty<Color?>? fillColor;
final MaterialTapTargetSize? materialTapTargetSize;
final VisualDensity? visualDensity;
final Color? focusColor;
final Color? hoverColor;
final MaterialStateProperty<Color?>? overlayColor;
final double? splashRadius;
final FocusNode? focusNode;
final bool autofocus;
final OutlinedBorder? shape;
final BorderSide? side;
bool get _selected => value == groupValue;
@override
State<SRadio<T>> createState() => _SRadioState<T>();
}
class _SRadioState<T> extends State<SRadio<T>>
with TickerProviderStateMixin, ToggleableStateMixin {
final _SRadioPainter _painter = _SRadioPainter();
bool? _previousValue;
void _handleChanged(bool? selected) {
if (selected == null) {
widget.onChanged!(null);
return;
}
if (selected) {
widget.onChanged!(widget.value);
}
}
@override
void didUpdateWidget(SRadio<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._selected != oldWidget._selected) {
animateToValue();
}
}
@override
void dispose() {
_painter.dispose();
super.dispose();
}
@override
ValueChanged<bool?>? get onChanged =>
widget.onChanged != null ? _handleChanged : null;
@override
bool get tristate => widget.toggleable;
@override
bool? get value => widget._selected;
MaterialStateProperty<Color?> get _widgetFillColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return null;
}
if (states.contains(MaterialState.selected)) {
return widget.activeColor;
}
return null;
});
}
MaterialStateProperty<Color> get _defaultFillColor {
final ThemeData themeData = Theme.of(context);
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return themeData.disabledColor;
}
if (states.contains(MaterialState.selected)) {
return themeData.toggleableActiveColor;
}
return themeData.unselectedWidgetColor;
});
}
BorderSide? _resolveSide(BorderSide? side) {
if (side is MaterialStateBorderSide) {
return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
}
if (!states.contains(MaterialState.selected)) {
return side;
}
return null;
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize =
widget.materialTapTargetSize ??
themeData.radioTheme.materialTapTargetSize ??
themeData.materialTapTargetSize;
final VisualDensity effectiveVisualDensity = widget.visualDensity ??
themeData.radioTheme.visualDensity ??
themeData.visualDensity;
Size size;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(
kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break;
}
size += effectiveVisualDensity.baseSizeAdjustment;
final MaterialStateProperty<MouseCursor> effectiveMouseCursor =
MaterialStateProperty.resolveWith<MouseCursor>(
(Set<MaterialState> states) {
return MaterialStateProperty.resolveAs<MouseCursor?>(
widget.mouseCursor, states) ??
themeData.radioTheme.mouseCursor?.resolve(states) ??
MaterialStateProperty.resolveAs<MouseCursor>(
MaterialStateMouseCursor.clickable, states);
});
final Set<MaterialState> activeStates = states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = states
..remove(MaterialState.selected);
final Color effectiveActiveColor =
widget.fillColor?.resolve(activeStates) ??
_widgetFillColor.resolve(activeStates) ??
themeData.radioTheme.fillColor?.resolve(activeStates) ??
_defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor =
widget.fillColor?.resolve(inactiveStates) ??
_widgetFillColor.resolve(inactiveStates) ??
themeData.radioTheme.fillColor?.resolve(inactiveStates) ??
_defaultFillColor.resolve(inactiveStates);
final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor =
widget.overlayColor?.resolve(focusedStates) ??
widget.focusColor ??
themeData.radioTheme.overlayColor?.resolve(focusedStates) ??
themeData.focusColor;
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor =
widget.overlayColor?.resolve(hoveredStates) ??
widget.hoverColor ??
themeData.radioTheme.overlayColor?.resolve(hoveredStates) ??
themeData.hoverColor;
final Set<MaterialState> activePressedStates = activeStates
..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor =
widget.overlayColor?.resolve(activePressedStates) ??
themeData.radioTheme.overlayColor?.resolve(activePressedStates) ??
effectiveActiveColor.withAlpha(kRadialReactionAlpha);
final Set<MaterialState> inactivePressedStates = inactiveStates
..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor =
widget.overlayColor?.resolve(inactivePressedStates) ??
themeData.radioTheme.overlayColor?.resolve(inactivePressedStates) ??
effectiveActiveColor.withAlpha(kRadialReactionAlpha);
final Color effectiveCheckColor = widget.checkColor ??
themeData.checkboxTheme.checkColor?.resolve(states) ??
const Color(0xFFFFFFFF);
return Semantics(
inMutuallyExclusiveGroup: true,
checked: widget._selected,
child: buildToggleable(
focusNode: widget.focusNode,
autofocus: widget.autofocus,
mouseCursor: effectiveMouseCursor,
size: size,
painter: _painter
..position = position
..reaction = reaction
..reactionFocusFade = reactionFocusFade
..reactionHoverFade = reactionHoverFade
..inactiveReactionColor = effectiveInactivePressedOverlayColor
..reactionColor = effectiveActivePressedOverlayColor
..hoverColor = effectiveHoverOverlayColor
..focusColor = effectiveFocusOverlayColor
..splashRadius = widget.splashRadius ??
themeData.radioTheme.splashRadius ??
kRadialReactionRadius
..downPosition = downPosition
..isFocused = states.contains(MaterialState.focused)
..isHovered = states.contains(MaterialState.hovered)
..activeColor = effectiveActiveColor
..inactiveColor = effectiveInactiveColor
..checkColor = effectiveCheckColor
..value = value
..previousValue = _previousValue
..shape = widget.shape ??
themeData.checkboxTheme.shape ??
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(1.0)),
)
..side = _resolveSide(widget.side) ??
_resolveSide(themeData.checkboxTheme.side),
),
);
}
}
class _SRadioPainter extends ToggleablePainter {
Color get checkColor => _checkColor!;
Color? _checkColor;
set checkColor(Color value) {
if (_checkColor == value) {
return;
}
_checkColor = value;
notifyListeners();
}
bool? get value => _value;
bool? _value;
set value(bool? value) {
if (_value == value) {
return;
}
_value = value;
notifyListeners();
}
bool? get previousValue => _previousValue;
bool? _previousValue;
set previousValue(bool? value) {
if (_previousValue == value) {
return;
}
_previousValue = value;
notifyListeners();
}
Rect _outerRectAt(Offset origin, double t) {
final double inset = 1.0 - (t - 0.5).abs() * 2.0;
final double size = _kEdgeSize - inset * _kStrokeWidth;
final Rect rect = Rect.fromLTWH(origin.dx, origin.dy, size, size);
return rect;
}
OutlinedBorder get shape => _shape!;
OutlinedBorder? _shape;
set shape(OutlinedBorder value) {
if (_shape == value) {
return;
}
_shape = value;
notifyListeners();
}
BorderSide? get side => _side;
BorderSide? _side;
set side(BorderSide? value) {
if (_side == value) {
return;
}
_side = value;
notifyListeners();
}
void _drawBox(
Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool fill) {
if (fill) {
var size = const Size(30, 30);
final Offset origin =
size - const Size.square(_kEdgeSize) / 1.7 as Offset;
final shapeBounds = Rect.fromLTWH(8, 0, size.width, size.height);
final backgroundPath = Path();
backgroundPath
..moveTo(shapeBounds.left, shapeBounds.top)
..addRect(outer)
..close();
canvas.drawPath(backgroundPath, paint);
var paint1 = paint
..color = _checkColor!
..style = PaintingStyle.fill;
canvas.drawRect(origin & const Size(20, 20), paint1);
}
if (side != null) {
shape.copyWith(side: side).paint(canvas, outer);
}
}
Color _colorAt(double t) {
return t >= 0.25
? activeColor
: Color.lerp(inactiveColor, activeColor, t * 4.0)!;
}
@override
void paint(Canvas canvas, Size size) {
paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero));
final Offset origin =
size / 2.0 - const Size.square(_kEdgeSize) / 2.0 as Offset;
final AnimationStatus status = position.status;
final double tNormalized =
status == AnimationStatus.forward || status == AnimationStatus.completed
? position.value
: 1.0 - position.value;
if (previousValue == false || value == false) {
double t = value == false ? 1.0 - tNormalized : tNormalized;
final Rect outer = _outerRectAt(origin, t);
final Paint paint = Paint()..color = _colorAt(t);
if (t <= 0.5) {
final BorderSide border =
side ?? BorderSide(width: 2, color: paint.color);
_drawBox(canvas, outer, paint, border, false);
} else {
_drawBox(canvas, outer, paint, side, true);
}
} else {
final Rect outer = _outerRectAt(origin, 8.0);
final Paint paint = Paint()..color = _colorAt(1.0);
paint
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
_drawBox(canvas, outer, paint, side, true);
}
}
}
@gladson
Copy link
Author

gladson commented Dec 20, 2021

Para usar no projeto:

SRadio(
  value: i,
  groupValue: _value,
  checkColor: const Color(0xFFFF0000),
  fillColor: MaterialStateProperty.all(const Color(0xFFFF0000)),
  onChanged: (value) {
    setState(() {
      _value = value;
      print(value);
    });
  },
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment