radio.dart 22.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/widgets.dart';
6

7 8
import 'color_scheme.dart';
import 'colors.dart';
9
import 'constants.dart';
10
import 'debug.dart';
11
import 'material_state.dart';
12
import 'radio_theme.dart';
13
import 'theme.dart';
14
import 'theme_data.dart';
15
import 'toggleable.dart';
16

17 18 19 20 21 22
// Examples can assume:
// late BuildContext context;
// enum SingingCharacter { lafayette }
// late SingingCharacter? _character;
// late StateSetter setState;

23 24
const double _kOuterRadius = 8.0;
const double _kInnerRadius = 4.5;
25

26
/// A Material Design radio button.
27
///
28 29 30 31
/// Used to select between a number of mutually exclusive values. When one radio
/// button in a group is selected, the other radio buttons in the group cease to
/// be selected. The values are of type `T`, the type parameter of the [Radio]
/// class. Enums are commonly used for this purpose.
32
///
33 34 35 36 37 38
/// The radio button itself does not maintain any state. Instead, selecting the
/// radio invokes the [onChanged] callback, passing [value] as a parameter. If
/// [groupValue] and [value] match, this radio will be selected. Most widgets
/// will respond to [onChanged] by calling [State.setState] to update the
/// radio button's [groupValue].
///
39
/// {@tool dartpad}
40 41 42 43
/// Here is an example of Radio widgets wrapped in ListTiles, which is similar
/// to what you could get with the RadioListTile widget.
///
/// The currently selected character is passed into `groupValue`, which is
44
/// maintained by the example's `State`. In this case, the first [Radio]
45 46 47 48 49 50 51 52
/// will start off selected because `_character` is initialized to
/// `SingingCharacter.lafayette`.
///
/// If the second radio button is pressed, the example's state is updated
/// with `setState`, updating `_character` to `SingingCharacter.jefferson`.
/// This causes the buttons to rebuild with the updated `groupValue`, and
/// therefore the selection of the second button.
///
53 54
/// Requires one of its ancestors to be a [Material] widget.
///
55
/// ** See code in examples/api/lib/material/radio/radio.0.dart **
56
/// {@end-tool}
57 58
///
/// See also:
59
///
60 61
///  * [RadioListTile], which combines this widget with a [ListTile] so that
///    you can give the radio button a label.
62 63
///  * [Slider], for selecting a value in a range.
///  * [Checkbox] and [Switch], for toggling a particular value on or off.
64
///  * <https://material.io/design/components/selection-controls.html#radio-buttons>
65
class Radio<T> extends StatefulWidget {
66
  /// Creates a Material Design radio button.
67
  ///
68 69 70 71 72
  /// The radio button itself does not maintain any state. Instead, when the
  /// radio button is selected, the widget calls the [onChanged] callback. Most
  /// widgets that use a radio button will listen for the [onChanged] callback
  /// and rebuild the radio button with a new [groupValue] to update the visual
  /// appearance of the radio button.
73
  ///
74 75 76 77 78
  /// The following arguments are required:
  ///
  /// * [value] and [groupValue] together determine whether the radio button is
  ///   selected.
  /// * [onChanged] is called when the user selects this radio button.
79
  const Radio({
80
    super.key,
81 82 83
    required this.value,
    required this.groupValue,
    required this.onChanged,
84
    this.mouseCursor,
85
    this.toggleable = false,
86
    this.activeColor,
87
    this.fillColor,
88 89
    this.focusColor,
    this.hoverColor,
90
    this.overlayColor,
91
    this.splashRadius,
92
    this.materialTapTargetSize,
93
    this.visualDensity,
94 95 96
    this.focusNode,
    this.autofocus = false,
  }) : assert(autofocus != null),
97
       assert(toggleable != null);
98

99
  /// The value represented by this radio button.
Hixie's avatar
Hixie committed
100
  final T value;
101

102
  /// The currently selected value for a group of radio buttons.
103 104 105
  ///
  /// This radio button is considered selected if its [value] matches the
  /// [groupValue].
106
  final T? groupValue;
107 108 109

  /// Called when the user selects this radio button.
  ///
110 111 112 113 114
  /// The radio button passes [value] as a parameter to this callback. The radio
  /// button does not actually change state until the parent widget rebuilds the
  /// radio button with the new [groupValue].
  ///
  /// If null, the radio button will be displayed as disabled.
115
  ///
116 117 118
  /// The provided callback will not be invoked if this radio button is already
  /// selected.
  ///
119
  /// The callback provided to [onChanged] should update the state of the parent
120 121 122 123
  /// [StatefulWidget] using the [State.setState] method, so that the parent
  /// gets rebuilt; for example:
  ///
  /// ```dart
124
  /// Radio<SingingCharacter>(
125 126
  ///   value: SingingCharacter.lafayette,
  ///   groupValue: _character,
127
  ///   onChanged: (SingingCharacter? newValue) {
128 129 130 131
  ///     setState(() {
  ///       _character = newValue;
  ///     });
  ///   },
132
  /// )
133
  /// ```
134
  final ValueChanged<T?>? onChanged;
135

136
  /// {@template flutter.material.radio.mouseCursor}
137 138 139 140 141 142 143 144 145 146
  /// The cursor for a mouse pointer when it enters or is hovering over the
  /// widget.
  ///
  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  ///
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.focused].
  ///  * [MaterialState.disabled].
147
  /// {@endtemplate}
148
  ///
149 150 151 152 153 154 155 156
  /// If null, then the value of [RadioThemeData.mouseCursor] is used.
  /// If that is also null, then [MaterialStateMouseCursor.clickable] is used.
  ///
  /// See also:
  ///
  ///  * [MaterialStateMouseCursor], a [MouseCursor] that implements
  ///    `MaterialStateProperty` which is used in APIs that need to accept
  ///    either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
157
  final MouseCursor? mouseCursor;
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
  /// Set to true if this radio button is allowed to be returned to an
  /// indeterminate state by selecting it again when selected.
  ///
  /// To indicate returning to an indeterminate state, [onChanged] will be
  /// called with null.
  ///
  /// If true, [onChanged] can be called with [value] when selected while
  /// [groupValue] != [value], or with null when selected again while
  /// [groupValue] == [value].
  ///
  /// If false, [onChanged] will be called with [value] when it is selected
  /// while [groupValue] != [value], and only by selecting another radio button
  /// in the group (i.e. changing the value of [groupValue]) can this radio
  /// button be unselected.
  ///
  /// The default is false.
  ///
176
  /// {@tool dartpad}
177 178 179
  /// This example shows how to enable deselecting a radio button by setting the
  /// [toggleable] attribute.
  ///
180
  /// ** See code in examples/api/lib/material/radio/radio.toggleable.0.dart **
181 182 183
  /// {@end-tool}
  final bool toggleable;

184 185
  /// The color to use when this radio button is selected.
  ///
186
  /// Defaults to [ColorScheme.secondary].
187 188 189
  ///
  /// If [fillColor] returns a non-null color in the [MaterialState.selected]
  /// state, it will be used instead of this color.
190
  final Color? activeColor;
191

192 193
  /// {@template flutter.material.radio.fillColor}
  /// The color that fills the radio button, in all [MaterialState]s.
194 195 196 197 198 199
  ///
  /// Resolves in the following states:
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.focused].
  ///  * [MaterialState.disabled].
200 201 202 203 204 205 206 207 208 209 210
  ///
  /// {@tool snippet}
  /// This example resolves the [fillColor] based on the current [MaterialState]
  /// of the [Radio], providing a different [Color] when it is
  /// [MaterialState.disabled].
  ///
  /// ```dart
  /// Radio<int>(
  ///   value: 1,
  ///   groupValue: 1,
  ///   onChanged: (_){},
211
  ///   fillColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
212 213 214 215 216 217 218 219
  ///     if (states.contains(MaterialState.disabled)) {
  ///       return Colors.orange.withOpacity(.32);
  ///     }
  ///     return Colors.orange;
  ///   })
  /// )
  /// ```
  /// {@end-tool}
220 221 222 223 224
  /// {@endtemplate}
  ///
  /// If null, then the value of [activeColor] is used in the selected state. If
  /// that is also null, then the value of [RadioThemeData.fillColor] is used.
  /// If that is also null, then [ThemeData.disabledColor] is used in
225
  /// the disabled state, [ColorScheme.secondary] is used in the
226 227
  /// selected state, and [ThemeData.unselectedWidgetColor] is used in the
  /// default state.
228 229
  final MaterialStateProperty<Color?>? fillColor;

230
  /// {@template flutter.material.radio.materialTapTargetSize}
231
  /// Configures the minimum size of the tap target.
232
  /// {@endtemplate}
233
  ///
234 235 236
  /// If null, then the value of [RadioThemeData.materialTapTargetSize] is used.
  /// If that is also null, then the value of [ThemeData.materialTapTargetSize]
  /// is used.
237 238 239
  ///
  /// See also:
  ///
240
  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
241
  final MaterialTapTargetSize? materialTapTargetSize;
242

243
  /// {@template flutter.material.radio.visualDensity}
244
  /// Defines how compact the radio's layout will be.
245
  /// {@endtemplate}
246 247 248
  ///
  /// {@macro flutter.material.themedata.visualDensity}
  ///
249 250 251
  /// If null, then the value of [RadioThemeData.visualDensity] is used. If that
  /// is also null, then the value of [ThemeData.visualDensity] is used.
  ///
252 253
  /// See also:
  ///
254 255
  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
  ///    widgets within a [Theme].
256
  final VisualDensity? visualDensity;
257

258
  /// The color for the radio's [Material] when it has the input focus.
259
  ///
260 261 262
  /// If [overlayColor] returns a non-null color in the [MaterialState.focused]
  /// state, it will be used instead.
  ///
263 264 265
  /// If null, then the value of [RadioThemeData.overlayColor] is used in the
  /// focused state. If that is also null, then the value of
  /// [ThemeData.focusColor] is used.
266
  final Color? focusColor;
267 268

  /// The color for the radio's [Material] when a pointer is hovering over it.
269
  ///
270 271 272
  /// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
  /// state, it will be used instead.
  ///
273 274 275
  /// If null, then the value of [RadioThemeData.overlayColor] is used in the
  /// hovered state. If that is also null, then the value of
  /// [ThemeData.hoverColor] is used.
276
  final Color? hoverColor;
277

278 279 280 281 282 283 284 285 286 287 288 289 290 291
  /// {@template flutter.material.radio.overlayColor}
  /// The color for the checkbox's [Material].
  ///
  /// Resolves in the following states:
  ///  * [MaterialState.pressed].
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.focused].
  /// {@endtemplate}
  ///
  /// If null, then the value of [activeColor] with alpha
  /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
  /// pressed, focused and hovered state. If that is also null,
  /// the value of [RadioThemeData.overlayColor] is used. If that is also null,
292
  /// then the value of [ColorScheme.secondary] with alpha
293 294 295 296
  /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
  /// is used in the pressed, focused and hovered state.
  final MaterialStateProperty<Color?>? overlayColor;

297
  /// {@template flutter.material.radio.splashRadius}
298
  /// The splash radius of the circular [Material] ink response.
299
  /// {@endtemplate}
300
  ///
301 302
  /// If null, then the value of [RadioThemeData.splashRadius] is used. If that
  /// is also null, then [kRadialReactionRadius] is used.
303 304
  final double? splashRadius;

305
  /// {@macro flutter.widgets.Focus.focusNode}
306
  final FocusNode? focusNode;
307 308 309 310

  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

311 312
  bool get _selected => value == groupValue;

313
  @override
314
  State<Radio<T>> createState() => _RadioState<T>();
315 316
}

317 318
class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, ToggleableStateMixin {
  final _RadioPainter _painter = _RadioPainter();
319

320 321 322 323 324 325 326 327 328
  void _handleChanged(bool? selected) {
    if (selected == null) {
      widget.onChanged!(null);
      return;
    }
    if (selected) {
      widget.onChanged!(widget.value);
    }
  }
329

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
  @override
  void didUpdateWidget(Radio<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;
349

350 351
  @override
  bool? get value => widget._selected;
352 353 354 355 356 357 358 359 360 361 362 363 364

  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;
    });
  }

365
  @override
366
  Widget build(BuildContext context) {
367
    assert(debugCheckHasMaterial(context));
368
    final RadioThemeData radioTheme = RadioTheme.of(context);
369
    final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
370
    final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
371
      ?? radioTheme.materialTapTargetSize
372
      ?? defaults.materialTapTargetSize!;
373
    final VisualDensity effectiveVisualDensity = widget.visualDensity
374
      ?? radioTheme.visualDensity
375
      ?? defaults.visualDensity!;
376
    Size size;
377
    switch (effectiveMaterialTapTargetSize) {
378
      case MaterialTapTargetSize.padded:
379
        size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
380 381
        break;
      case MaterialTapTargetSize.shrinkWrap:
382
        size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
383 384
        break;
    }
385
    size += effectiveVisualDensity.baseSizeAdjustment;
386 387 388

    final MaterialStateProperty<MouseCursor> effectiveMouseCursor = MaterialStateProperty.resolveWith<MouseCursor>((Set<MaterialState> states) {
      return MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)
389
        ?? radioTheme.mouseCursor?.resolve(states)
390 391
        ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, states);
    });
392

393 394
    // Colors need to be resolved in selected and non selected states separately
    // so that they can be lerped between.
395 396
    final Set<MaterialState> activeStates = states..add(MaterialState.selected);
    final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected);
397
    final Color? activeColor = widget.fillColor?.resolve(activeStates)
398
      ?? _widgetFillColor.resolve(activeStates)
399 400 401
      ?? radioTheme.fillColor?.resolve(activeStates);
    final Color effectiveActiveColor = activeColor ?? defaults.fillColor!.resolve(activeStates)!;
    final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates)
402
      ?? _widgetFillColor.resolve(inactiveStates)
403 404
      ?? radioTheme.fillColor?.resolve(inactiveStates);
    final Color effectiveInactiveColor = inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
405

406
    final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
407
    Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
408
      ?? widget.focusColor
409
      ?? radioTheme.overlayColor?.resolve(focusedStates)
410
      ?? defaults.overlayColor!.resolve(focusedStates)!;
411

412
    final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
413 414 415 416
    Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
      ?? widget.hoverColor
      ?? radioTheme.overlayColor?.resolve(hoveredStates)
      ?? defaults.overlayColor!.resolve(hoveredStates)!;
417

418 419
    final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
    final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
420 421 422
      ?? radioTheme.overlayColor?.resolve(activePressedStates)
      ?? activeColor?.withAlpha(kRadialReactionAlpha)
      ?? defaults.overlayColor!.resolve(activePressedStates)!;
423 424 425

    final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
    final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
426 427 428 429 430 431 432 433 434 435 436 437
      ?? radioTheme.overlayColor?.resolve(inactivePressedStates)
      ?? inactiveColor?.withAlpha(kRadialReactionAlpha)
      ?? defaults.overlayColor!.resolve(inactivePressedStates)!;

    if (downPosition != null) {
      effectiveHoverOverlayColor = states.contains(MaterialState.selected)
        ? effectiveActivePressedOverlayColor
        : effectiveInactivePressedOverlayColor;
      effectiveFocusOverlayColor = states.contains(MaterialState.selected)
        ? effectiveActivePressedOverlayColor
        : effectiveInactivePressedOverlayColor;
    }
438

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
    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
456
          ..splashRadius = widget.splashRadius ?? radioTheme.splashRadius ?? kRadialReactionRadius
457 458 459 460
          ..downPosition = downPosition
          ..isFocused = states.contains(MaterialState.focused)
          ..isHovered = states.contains(MaterialState.hovered)
          ..activeColor = effectiveActiveColor
461
          ..inactiveColor = effectiveInactiveColor,
462
      ),
463 464 465
    );
  }
}
466

467
class _RadioPainter extends ToggleablePainter {
468
  @override
469 470
  void paint(Canvas canvas, Size size) {
    paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero));
471

472
    final Offset center = (Offset.zero & size).center;
473 474

    // Outer circle
475
    final Paint paint = Paint()
476
      ..color = Color.lerp(inactiveColor, activeColor, position.value)!
477
      ..style = PaintingStyle.stroke
478 479 480 481 482
      ..strokeWidth = 2.0;
    canvas.drawCircle(center, _kOuterRadius, paint);

    // Inner circle
    if (!position.isDismissed) {
483
      paint.style = PaintingStyle.fill;
484 485 486 487
      canvas.drawCircle(center, _kInnerRadius * position.value, paint);
    }
  }
}
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

// Hand coded defaults based on Material Design 2.
class _RadioDefaultsM2 extends RadioThemeData {
  _RadioDefaultsM2(this.context);

  final BuildContext context;
  late final ThemeData _theme = Theme.of(context);
  late final ColorScheme _colors = _theme.colorScheme;

  @override
  MaterialStateProperty<Color> get fillColor {
    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
      if (states.contains(MaterialState.disabled)) {
        return _theme.disabledColor;
      }
      if (states.contains(MaterialState.selected)) {
        return _colors.secondary;
      }
      return _theme.unselectedWidgetColor;
    });
  }

  @override
  MaterialStateProperty<Color> get overlayColor {
    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return fillColor.resolve(states).withAlpha(kRadialReactionAlpha);
      }
      if (states.contains(MaterialState.focused)) {
        return _theme.focusColor;
      }
      if (states.contains(MaterialState.hovered)) {
        return _theme.hoverColor;
      }
      return Colors.transparent;
    });
  }

  @override
  MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;

  @override
  VisualDensity get visualDensity => _theme.visualDensity;
}

// BEGIN GENERATED TOKEN PROPERTIES - Radio<T>

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

540
// Token database version: v0_150
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618

class _RadioDefaultsM3 extends RadioThemeData {
  _RadioDefaultsM3(this.context);

  final BuildContext context;
  late final ThemeData _theme = Theme.of(context);
  late final ColorScheme _colors = _theme.colorScheme;

  @override
  MaterialStateProperty<Color> get fillColor {
    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
      if (states.contains(MaterialState.selected)) {
        if (states.contains(MaterialState.disabled)) {
          return _colors.onSurface.withOpacity(0.38);
        }
        if (states.contains(MaterialState.pressed)) {
          return _colors.primary;
        }
        if (states.contains(MaterialState.hovered)) {
          return _colors.primary;
        }
        if (states.contains(MaterialState.focused)) {
          return _colors.primary;
        }
        return _colors.primary;
      }
      if (states.contains(MaterialState.disabled)) {
        return _colors.onSurface.withOpacity(0.38);
      }
      if (states.contains(MaterialState.pressed)) {
        return _colors.onSurface;
      }
      if (states.contains(MaterialState.hovered)) {
        return _colors.onSurface;
      }
      if (states.contains(MaterialState.focused)) {
        return _colors.onSurface;
      }
      return _colors.onSurfaceVariant;
    });
  }

  @override
  MaterialStateProperty<Color> get overlayColor {
    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
      if (states.contains(MaterialState.selected)) {
        if (states.contains(MaterialState.pressed)) {
          return _colors.onSurface.withOpacity(0.12);
        }
        if (states.contains(MaterialState.hovered)) {
          return _colors.primary.withOpacity(0.08);
        }
        if (states.contains(MaterialState.focused)) {
          return _colors.primary.withOpacity(0.12);
        }
        return Colors.transparent;
      }
      if (states.contains(MaterialState.pressed)) {
        return _colors.primary.withOpacity(0.12);
      }
      if (states.contains(MaterialState.hovered)) {
        return _colors.onSurface.withOpacity(0.08);
      }
      if (states.contains(MaterialState.focused)) {
        return _colors.onSurface.withOpacity(0.12);
      }
      return Colors.transparent;
    });
  }

  @override
  MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;

  @override
  VisualDensity get visualDensity => _theme.visualDensity;
}

// END GENERATED TOKEN PROPERTIES - Radio<T>