233 lines
7.4 KiB
Dart
233 lines
7.4 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'basic.dart';
|
|
import 'focus_manager.dart';
|
|
import 'framework.dart';
|
|
import 'localizations.dart';
|
|
import 'radio_group.dart';
|
|
import 'ticker_provider.dart';
|
|
import 'toggleable.dart';
|
|
import 'widget_state.dart';
|
|
|
|
/// Signature for [RawRadio.builder].
|
|
///
|
|
/// The builder can use `state` to determine the state of the radio and build
|
|
/// the visual.
|
|
///
|
|
/// {@macro flutter.widgets.ToggleableStateMixin.buildToggleableWithChild}
|
|
typedef RadioBuilder = Widget Function(BuildContext context, ToggleableStateMixin state);
|
|
|
|
/// A Radio button that provides basic radio functionalities.
|
|
///
|
|
/// Provide the `builder` to draw UI for radio.
|
|
///
|
|
/// {@macro flutter.widgets.ToggleableStateMixin.buildToggleableWithChild}
|
|
///
|
|
/// This widget allows selection 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.
|
|
///
|
|
/// {@macro flutter.widget.RawRadio.groupValue}
|
|
///
|
|
/// If [enabled] is false, the radio will not be interactive.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Radio], which uses this widget to build a Material styled radio button.
|
|
/// * [CupertinoRadio], which uses this widget to build a Cupertino styled
|
|
/// radio button.
|
|
class RawRadio<T> extends StatefulWidget {
|
|
/// Creates a radio button.
|
|
///
|
|
/// If [enabled] is true, the [groupRegistry] must not be null.
|
|
const RawRadio({
|
|
super.key,
|
|
required this.value,
|
|
required this.mouseCursor,
|
|
required this.toggleable,
|
|
required this.focusNode,
|
|
required this.autofocus,
|
|
required this.groupRegistry,
|
|
required this.enabled,
|
|
required this.builder,
|
|
}) : assert(!enabled || groupRegistry != null, 'an enabled raw radio must have a registry');
|
|
|
|
/// {@template flutter.widget.RawRadio.value}
|
|
/// The value represented by this radio button.
|
|
/// {@endtemplate}
|
|
final T value;
|
|
|
|
/// {@template flutter.widget.RawRadio.mouseCursor}
|
|
/// The cursor for a mouse pointer when it enters or is hovering over the
|
|
/// widget.
|
|
///
|
|
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
|
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
|
///
|
|
/// * [WidgetState.selected].
|
|
/// * [WidgetState.hovered].
|
|
/// * [WidgetState.focused].
|
|
/// * [WidgetState.disabled].
|
|
/// {@endtemplate}
|
|
final WidgetStateProperty<MouseCursor> mouseCursor;
|
|
|
|
/// {@template flutter.widget.RawRadio.toggleable}
|
|
/// 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, [RadioGroup.onChanged]
|
|
/// of the [RadioGroup] above the widget tree will be called with null.
|
|
///
|
|
/// If true, [RadioGroup.onChanged] is called with [value] when selected while
|
|
/// [RadioGroup.groupValue] != [value], and with null when selected again while
|
|
/// [RadioGroup.groupValue] == [value].
|
|
///
|
|
/// If false, [RadioGroup.onChanged] will be called with [value] when it is
|
|
/// selected while [RadioGroup.groupValue] != [value], and only by selecting
|
|
/// another radio button in the group (i.e. changing the value of
|
|
/// [RadioGroup.groupValue]) can this radio button be unselected.
|
|
///
|
|
/// The default is false.
|
|
/// {@endtemplate}
|
|
final bool toggleable;
|
|
|
|
/// {@macro flutter.widgets.Focus.focusNode}
|
|
final FocusNode focusNode;
|
|
|
|
/// {@macro flutter.widgets.Focus.autofocus}
|
|
final bool autofocus;
|
|
|
|
/// The builder for the radio button visual.
|
|
///
|
|
/// Use the input `state` to determine the current state of the radio.
|
|
///
|
|
/// {@macro flutter.widgets.ToggleableStateMixin.buildToggleableWithChild}
|
|
final RadioBuilder builder;
|
|
|
|
/// Whether this widget is enabled.
|
|
final bool enabled;
|
|
|
|
/// {@template flutter.widget.RawRadio.groupRegistry}
|
|
/// The registry this radio registers to.
|
|
/// {@endtemplate}
|
|
///
|
|
/// {@template flutter.widget.RawRadio.groupValue}
|
|
/// The radio relies on [groupRegistry] to maintains the state for selection.
|
|
/// If use in conjunction with a [RadioGroup] widget, use [RadioGroup.maybeOf]
|
|
/// to get the group registry from the context.
|
|
/// {@endtemplate}
|
|
final RadioGroupRegistry<T>? groupRegistry;
|
|
|
|
@override
|
|
State<RawRadio<T>> createState() => _RawRadioState<T>();
|
|
}
|
|
|
|
class _RawRadioState<T> extends State<RawRadio<T>>
|
|
with TickerProviderStateMixin, ToggleableStateMixin, RadioClient<T> {
|
|
@override
|
|
FocusNode get focusNode => widget.focusNode;
|
|
|
|
@override
|
|
bool get enabled => isInteractive;
|
|
|
|
@override
|
|
T get radioValue => widget.value;
|
|
|
|
@override
|
|
void initState() {
|
|
// This has to be before the init state because the [ToggleableStateMixin]
|
|
// expect the [value] is up-to-date when init its state.
|
|
registry = widget.groupRegistry;
|
|
super.initState();
|
|
}
|
|
|
|
/// Handle selection status changed.
|
|
///
|
|
/// if `selected` is false, nothing happens.
|
|
///
|
|
/// if `selected` is true, select this radio. i.e. [Radio.onChanged] is called
|
|
/// with [Radio.value]. This also updates the group value in [RadioGroup] if it
|
|
/// is in use.
|
|
///
|
|
/// if `selected` is null, unselect this radio. Same as `selected` is true
|
|
/// except group value is set to null.
|
|
void _handleChanged(bool? selected) {
|
|
assert(registry != null);
|
|
if (!(selected ?? true)) {
|
|
return;
|
|
}
|
|
if (selected ?? false) {
|
|
registry!.onChanged(widget.value);
|
|
} else {
|
|
registry!.onChanged(null);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(RawRadio<T> oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
registry = widget.groupRegistry;
|
|
animateToValue(); // The registry's group value may have changed
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
registry = null;
|
|
}
|
|
|
|
@override
|
|
ValueChanged<bool?>? get onChanged => registry != null ? _handleChanged : null;
|
|
|
|
@override
|
|
bool get tristate => widget.toggleable;
|
|
|
|
@override
|
|
bool? get value => widget.value == registry?.groupValue;
|
|
|
|
@override
|
|
bool get isInteractive => widget.enabled;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bool? accessibilitySelected;
|
|
String? semanticsHint;
|
|
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
accessibilitySelected = null;
|
|
semanticsHint = null;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
accessibilitySelected = value;
|
|
// Only provide hint for unselected radio buttons to avoid duplication
|
|
// of the selected state announcement.
|
|
// Selected state is already announced by iOS via the 'selected' property.
|
|
if (!(value ?? false)) {
|
|
final WidgetsLocalizations localizations = WidgetsLocalizations.of(context);
|
|
semanticsHint = localizations.radioButtonUnselectedLabel;
|
|
}
|
|
}
|
|
|
|
return Semantics(
|
|
inMutuallyExclusiveGroup: true,
|
|
checked: value,
|
|
selected: accessibilitySelected,
|
|
hint: semanticsHint,
|
|
child: buildToggleableWithChild(
|
|
focusNode: focusNode,
|
|
autofocus: widget.autofocus,
|
|
mouseCursor: widget.mouseCursor,
|
|
child: widget.builder(context, this),
|
|
),
|
|
);
|
|
}
|
|
}
|