| // 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/cupertino.dart'; |
| |
| import 'checkbox_theme.dart'; |
| import 'color_scheme.dart'; |
| import 'colors.dart'; |
| import 'constants.dart'; |
| import 'debug.dart'; |
| import 'material_state.dart'; |
| import 'theme.dart'; |
| import 'theme_data.dart'; |
| import 'toggleable.dart'; |
| |
| // Examples can assume: |
| // bool _throwShotAway = false; |
| // late StateSetter setState; |
| |
| enum _CheckboxType { material, adaptive } |
| |
| /// A Material Design checkbox. |
| /// |
| /// The checkbox itself does not maintain any state. Instead, when the state of |
| /// the checkbox changes, the widget calls the [onChanged] callback. Most |
| /// widgets that use a checkbox will listen for the [onChanged] callback and |
| /// rebuild the checkbox with a new [value] to update the visual appearance of |
| /// the checkbox. |
| /// |
| /// The checkbox can optionally display three values - true, false, and null - |
| /// if [tristate] is true. When [value] is null a dash is displayed. By default |
| /// [tristate] is false and the checkbox's [value] must be true or false. |
| /// |
| /// Requires one of its ancestors to be a [Material] widget. |
| /// |
| /// {@tool dartpad} |
| /// This example shows how you can override the default theme of |
| /// a [Checkbox] with a [MaterialStateProperty]. |
| /// In this example, the checkbox's color will be `Colors.blue` when the [Checkbox] |
| /// is being pressed, hovered, or focused. Otherwise, the checkbox's color will |
| /// be `Colors.red`. |
| /// |
| /// ** See code in examples/api/lib/material/checkbox/checkbox.0.dart ** |
| /// {@end-tool} |
| /// |
| /// {@tool dartpad} |
| /// This example shows what the checkbox error state looks like. |
| /// |
| /// ** See code in examples/api/lib/material/checkbox/checkbox.1.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [CheckboxListTile], which combines this widget with a [ListTile] so that |
| /// you can give the checkbox a label. |
| /// * [Switch], a widget with semantics similar to [Checkbox]. |
| /// * [Radio], for selecting among a set of explicit values. |
| /// * [Slider], for selecting a value in a range. |
| /// * <https://material.io/design/components/selection-controls.html#checkboxes> |
| /// * <https://material.io/design/components/lists.html#types> |
| class Checkbox extends StatefulWidget { |
| /// Creates a Material Design checkbox. |
| /// |
| /// The checkbox itself does not maintain any state. Instead, when the state of |
| /// the checkbox changes, the widget calls the [onChanged] callback. Most |
| /// widgets that use a checkbox will listen for the [onChanged] callback and |
| /// rebuild the checkbox with a new [value] to update the visual appearance of |
| /// the checkbox. |
| /// |
| /// The following arguments are required: |
| /// |
| /// * [value], which determines whether the checkbox is checked. The [value] |
| /// can only be null if [tristate] is true. |
| /// * [onChanged], which is called when the value of the checkbox should |
| /// change. It can be set to null to disable the checkbox. |
| /// |
| /// The values of [tristate] and [autofocus] must not be null. |
| const Checkbox({ |
| super.key, |
| required this.value, |
| this.tristate = false, |
| required this.onChanged, |
| this.mouseCursor, |
| 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, |
| this.isError = false, |
| this.semanticLabel, |
| }) : _checkboxType = _CheckboxType.material, |
| assert(tristate || value != null); |
| |
| /// Creates an adaptive [Checkbox] based on whether the target platform is iOS |
| /// or macOS, following Material design's |
| /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html). |
| /// |
| /// On iOS and macOS, this constructor creates a [CupertinoCheckbox], which has |
| /// matching functionality and presentation as Material checkboxes, and are the |
| /// graphics expected on iOS. On other platforms, this creates a Material |
| /// design [Checkbox]. |
| /// |
| /// If a [CupertinoCheckbox] is created, the following parameters are ignored: |
| /// [mouseCursor], [fillColor], [hoverColor], [overlayColor], [splashRadius], |
| /// [materialTapTargetSize], [visualDensity], [isError]. However, [shape] and |
| /// [side] will still affect the [CupertinoCheckbox] and should be handled if |
| /// native fidelity is important. |
| /// |
| /// The target platform is based on the current [Theme]: [ThemeData.platform]. |
| const Checkbox.adaptive({ |
| super.key, |
| required this.value, |
| this.tristate = false, |
| required this.onChanged, |
| this.mouseCursor, |
| 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, |
| this.isError = false, |
| this.semanticLabel, |
| }) : _checkboxType = _CheckboxType.adaptive, |
| assert(tristate || value != null); |
| |
| /// Whether this checkbox is checked. |
| /// |
| /// When [tristate] is true, a value of null corresponds to the mixed state. |
| /// When [tristate] is false, this value must not be null. |
| final bool? value; |
| |
| /// Called when the value of the checkbox should change. |
| /// |
| /// The checkbox passes the new value to the callback but does not actually |
| /// change state until the parent widget rebuilds the checkbox with the new |
| /// value. |
| /// |
| /// If this callback is null, the checkbox will be displayed as disabled |
| /// and will not respond to input gestures. |
| /// |
| /// When the checkbox is tapped, if [tristate] is false (the default) then |
| /// the [onChanged] callback will be applied to `!value`. If [tristate] is |
| /// true this callback cycle from false to true to null. |
| /// |
| /// The callback provided to [onChanged] should update the state of the parent |
| /// [StatefulWidget] using the [State.setState] method, so that the parent |
| /// gets rebuilt; for example: |
| /// |
| /// ```dart |
| /// Checkbox( |
| /// value: _throwShotAway, |
| /// onChanged: (bool? newValue) { |
| /// setState(() { |
| /// _throwShotAway = newValue!; |
| /// }); |
| /// }, |
| /// ) |
| /// ``` |
| final ValueChanged<bool?>? onChanged; |
| |
| /// {@template flutter.material.checkbox.mouseCursor} |
| /// 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]. |
| /// {@endtemplate} |
| /// |
| /// When [value] is null and [tristate] is true, [MaterialState.selected] is |
| /// included as a state. |
| /// |
| /// If null, then the value of [CheckboxThemeData.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>]. |
| final MouseCursor? mouseCursor; |
| |
| /// The color to use when this checkbox is checked. |
| /// |
| /// Defaults to [ColorScheme.secondary]. |
| /// |
| /// If [fillColor] returns a non-null color in the [MaterialState.selected] |
| /// state, it will be used instead of this color. |
| final Color? activeColor; |
| |
| /// {@template flutter.material.checkbox.fillColor} |
| /// The color that fills the checkbox, in all [MaterialState]s. |
| /// |
| /// Resolves in the following states: |
| /// * [MaterialState.selected]. |
| /// * [MaterialState.hovered]. |
| /// * [MaterialState.focused]. |
| /// * [MaterialState.disabled]. |
| /// |
| /// {@tool snippet} |
| /// This example resolves the [fillColor] based on the current [MaterialState] |
| /// of the [Checkbox], providing a different [Color] when it is |
| /// [MaterialState.disabled]. |
| /// |
| /// ```dart |
| /// Checkbox( |
| /// value: true, |
| /// onChanged: (_){}, |
| /// fillColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { |
| /// if (states.contains(MaterialState.disabled)) { |
| /// return Colors.orange.withOpacity(.32); |
| /// } |
| /// return Colors.orange; |
| /// }) |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| /// {@endtemplate} |
| /// |
| /// If null, then the value of [activeColor] is used in the selected |
| /// state. If that is also null, the value of [CheckboxThemeData.fillColor] |
| /// is used. If that is also null, then [ThemeData.disabledColor] is used in |
| /// the disabled state, [ColorScheme.secondary] is used in the |
| /// selected state, and [ThemeData.unselectedWidgetColor] is used in the |
| /// default state. |
| final MaterialStateProperty<Color?>? fillColor; |
| |
| /// {@template flutter.material.checkbox.checkColor} |
| /// The color to use for the check icon when this checkbox is checked. |
| /// {@endtemplate} |
| /// |
| /// If null, then the value of [CheckboxThemeData.checkColor] is used. If |
| /// that is also null, then Color(0xFFFFFFFF) is used. |
| final Color? checkColor; |
| |
| /// If true the checkbox's [value] can be true, false, or null. |
| /// |
| /// [Checkbox] displays a dash when its value is null. |
| /// |
| /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged] |
| /// callback will be applied to true if the current value is false, to null if |
| /// value is true, and to false if value is null (i.e. it cycles through false |
| /// => true => null => false when tapped). |
| /// |
| /// If tristate is false (the default), [value] must not be null. |
| final bool tristate; |
| |
| /// {@template flutter.material.checkbox.materialTapTargetSize} |
| /// Configures the minimum size of the tap target. |
| /// {@endtemplate} |
| /// |
| /// If null, then the value of [CheckboxThemeData.materialTapTargetSize] is |
| /// used. If that is also null, then the value of |
| /// [ThemeData.materialTapTargetSize] is used. |
| /// |
| /// See also: |
| /// |
| /// * [MaterialTapTargetSize], for a description of how this affects tap targets. |
| final MaterialTapTargetSize? materialTapTargetSize; |
| |
| /// {@template flutter.material.checkbox.visualDensity} |
| /// Defines how compact the checkbox's layout will be. |
| /// {@endtemplate} |
| /// |
| /// {@macro flutter.material.themedata.visualDensity} |
| /// |
| /// If null, then the value of [CheckboxThemeData.visualDensity] is used. If |
| /// that is also null, then the value of [ThemeData.visualDensity] is used. |
| /// |
| /// See also: |
| /// |
| /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all |
| /// widgets within a [Theme]. |
| final VisualDensity? visualDensity; |
| |
| /// The color for the checkbox's [Material] when it has the input focus. |
| /// |
| /// If [overlayColor] returns a non-null color in the [MaterialState.focused] |
| /// state, it will be used instead. |
| /// |
| /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the |
| /// focused state. If that is also null, then the value of |
| /// [ThemeData.focusColor] is used. |
| final Color? focusColor; |
| |
| /// {@template flutter.material.checkbox.hoverColor} |
| /// The color for the checkbox's [Material] when a pointer is hovering over it. |
| /// |
| /// If [overlayColor] returns a non-null color in the [MaterialState.hovered] |
| /// state, it will be used instead. |
| /// {@endtemplate} |
| /// |
| /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the |
| /// hovered state. If that is also null, then the value of |
| /// [ThemeData.hoverColor] is used. |
| final Color? hoverColor; |
| |
| /// {@template flutter.material.checkbox.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 [CheckboxThemeData.overlayColor] is used. If that is |
| /// also null, then the value of [ColorScheme.secondary] with alpha |
| /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor] |
| /// is used in the pressed, focused and hovered state. |
| final MaterialStateProperty<Color?>? overlayColor; |
| |
| /// {@template flutter.material.checkbox.splashRadius} |
| /// The splash radius of the circular [Material] ink response. |
| /// {@endtemplate} |
| /// |
| /// If null, then the value of [CheckboxThemeData.splashRadius] is used. If |
| /// that is also null, then [kRadialReactionRadius] is used. |
| final double? splashRadius; |
| |
| /// {@macro flutter.widgets.Focus.focusNode} |
| final FocusNode? focusNode; |
| |
| /// {@macro flutter.widgets.Focus.autofocus} |
| final bool autofocus; |
| |
| /// {@template flutter.material.checkbox.shape} |
| /// The shape of the checkbox's [Material]. |
| /// {@endtemplate} |
| /// |
| /// If this property is null then [CheckboxThemeData.shape] of [ThemeData.checkboxTheme] |
| /// is used. If that's null then the shape will be a [RoundedRectangleBorder] |
| /// with a circular corner radius of 1.0 in Material 2, and 2.0 in Material 3. |
| final OutlinedBorder? shape; |
| |
| /// {@template flutter.material.checkbox.side} |
| /// The color and width of the checkbox's border. |
| /// |
| /// This property can be a [MaterialStateBorderSide] that can |
| /// specify different border color and widths depending on the |
| /// checkbox's state. |
| /// |
| /// Resolves in the following states: |
| /// * [MaterialState.pressed]. |
| /// * [MaterialState.selected]. |
| /// * [MaterialState.hovered]. |
| /// * [MaterialState.focused]. |
| /// * [MaterialState.disabled]. |
| /// * [MaterialState.error]. |
| /// |
| /// If this property is not a [MaterialStateBorderSide] and it is |
| /// non-null, then it is only rendered when the checkbox's value is |
| /// false. The difference in interpretation is for backwards |
| /// compatibility. |
| /// {@endtemplate} |
| /// |
| /// If this property is null, then [CheckboxThemeData.side] of |
| /// [ThemeData.checkboxTheme] is used. If that is also null, then the side |
| /// will be width 2. |
| final BorderSide? side; |
| |
| /// {@template flutter.material.checkbox.isError} |
| /// True if this checkbox wants to show an error state. |
| /// |
| /// The checkbox will have different default container color and check color when |
| /// this is true. This is only used when [ThemeData.useMaterial3] is set to true. |
| /// {@endtemplate} |
| /// |
| /// Must not be null. Defaults to false. |
| final bool isError; |
| |
| /// {@template flutter.material.checkbox.semanticLabel} |
| /// The semantic label for the checkobox that will be announced by screen readers. |
| /// |
| /// This is announced in accessibility modes (e.g TalkBack/VoiceOver). |
| /// |
| /// This label does not show in the UI. |
| /// {@endtemplate} |
| final String? semanticLabel; |
| |
| /// The width of a checkbox widget. |
| static const double width = 18.0; |
| |
| final _CheckboxType _checkboxType; |
| |
| @override |
| State<Checkbox> createState() => _CheckboxState(); |
| } |
| |
| class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, ToggleableStateMixin { |
| final _CheckboxPainter _painter = _CheckboxPainter(); |
| bool? _previousValue; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _previousValue = widget.value; |
| } |
| |
| @override |
| void didUpdateWidget(Checkbox oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.value != widget.value) { |
| _previousValue = oldWidget.value; |
| animateToValue(); |
| } |
| } |
| |
| @override |
| void dispose() { |
| _painter.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| ValueChanged<bool?>? get onChanged => widget.onChanged; |
| |
| @override |
| bool get tristate => widget.tristate; |
| |
| @override |
| bool? get value => widget.value; |
| |
| 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; |
| }); |
| } |
| |
| BorderSide? _resolveSide(BorderSide? side, Set<MaterialState> states) { |
| if (side is MaterialStateBorderSide) { |
| return MaterialStateProperty.resolveAs<BorderSide?>(side, states); |
| } |
| if (!states.contains(MaterialState.selected)) { |
| return side; |
| } |
| return null; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| switch (widget._checkboxType) { |
| case _CheckboxType.material: |
| break; |
| |
| case _CheckboxType.adaptive: |
| final ThemeData theme = Theme.of(context); |
| switch (theme.platform) { |
| case TargetPlatform.android: |
| case TargetPlatform.fuchsia: |
| case TargetPlatform.linux: |
| case TargetPlatform.windows: |
| break; |
| case TargetPlatform.iOS: |
| case TargetPlatform.macOS: |
| return CupertinoCheckbox( |
| value: value, |
| tristate: tristate, |
| onChanged: onChanged, |
| activeColor: widget.activeColor, |
| checkColor: widget.checkColor, |
| focusColor: widget.focusColor, |
| focusNode: widget.focusNode, |
| autofocus: widget.autofocus, |
| side: widget.side, |
| shape: widget.shape, |
| ); |
| } |
| } |
| |
| assert(debugCheckHasMaterial(context)); |
| final CheckboxThemeData checkboxTheme = CheckboxTheme.of(context); |
| final CheckboxThemeData defaults = Theme.of(context).useMaterial3 |
| ? _CheckboxDefaultsM3(context) |
| : _CheckboxDefaultsM2(context); |
| final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize |
| ?? checkboxTheme.materialTapTargetSize |
| ?? defaults.materialTapTargetSize!; |
| final VisualDensity effectiveVisualDensity = widget.visualDensity |
| ?? checkboxTheme.visualDensity |
| ?? defaults.visualDensity!; |
| Size size; |
| switch (effectiveMaterialTapTargetSize) { |
| case MaterialTapTargetSize.padded: |
| size = const Size(kMinInteractiveDimension, kMinInteractiveDimension); |
| case MaterialTapTargetSize.shrinkWrap: |
| size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0); |
| } |
| size += effectiveVisualDensity.baseSizeAdjustment; |
| |
| final MaterialStateProperty<MouseCursor> effectiveMouseCursor = MaterialStateProperty.resolveWith<MouseCursor>((Set<MaterialState> states) { |
| return MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states) |
| ?? checkboxTheme.mouseCursor?.resolve(states) |
| ?? MaterialStateMouseCursor.clickable.resolve(states); |
| }); |
| |
| // Colors need to be resolved in selected and non selected states separately |
| final Set<MaterialState> activeStates = states..add(MaterialState.selected); |
| final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected); |
| if (widget.isError) { |
| activeStates.add(MaterialState.error); |
| inactiveStates.add(MaterialState.error); |
| } |
| final Color? activeColor = widget.fillColor?.resolve(activeStates) |
| ?? _widgetFillColor.resolve(activeStates) |
| ?? checkboxTheme.fillColor?.resolve(activeStates); |
| final Color effectiveActiveColor = activeColor |
| ?? defaults.fillColor!.resolve(activeStates)!; |
| final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates) |
| ?? _widgetFillColor.resolve(inactiveStates) |
| ?? checkboxTheme.fillColor?.resolve(inactiveStates); |
| final Color effectiveInactiveColor = inactiveColor |
| ?? defaults.fillColor!.resolve(inactiveStates)!; |
| |
| final BorderSide activeSide = _resolveSide(widget.side, activeStates) |
| ?? _resolveSide(checkboxTheme.side, activeStates) |
| ?? _resolveSide(defaults.side, activeStates)!; |
| final BorderSide inactiveSide = _resolveSide(widget.side, inactiveStates) |
| ?? _resolveSide(checkboxTheme.side, inactiveStates) |
| ?? _resolveSide(defaults.side, inactiveStates)!; |
| |
| final Set<MaterialState> focusedStates = states..add(MaterialState.focused); |
| if (widget.isError) { |
| focusedStates.add(MaterialState.error); |
| } |
| Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates) |
| ?? widget.focusColor |
| ?? checkboxTheme.overlayColor?.resolve(focusedStates) |
| ?? defaults.overlayColor!.resolve(focusedStates)!; |
| |
| final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered); |
| if (widget.isError) { |
| hoveredStates.add(MaterialState.error); |
| } |
| Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates) |
| ?? widget.hoverColor |
| ?? checkboxTheme.overlayColor?.resolve(hoveredStates) |
| ?? defaults.overlayColor!.resolve(hoveredStates)!; |
| |
| final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed); |
| final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates) |
| ?? checkboxTheme.overlayColor?.resolve(activePressedStates) |
| ?? activeColor?.withAlpha(kRadialReactionAlpha) |
| ?? defaults.overlayColor!.resolve(activePressedStates)!; |
| |
| final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed); |
| final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates) |
| ?? checkboxTheme.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; |
| } |
| |
| final Set<MaterialState> checkStates = widget.isError ? (states..add(MaterialState.error)) : states; |
| final Color effectiveCheckColor = widget.checkColor |
| ?? checkboxTheme.checkColor?.resolve(checkStates) |
| ?? defaults.checkColor!.resolve(checkStates)!; |
| |
| final double effectiveSplashRadius = widget.splashRadius |
| ?? checkboxTheme.splashRadius |
| ?? defaults.splashRadius!; |
| |
| return Semantics( |
| label: widget.semanticLabel, |
| checked: widget.value ?? false, |
| mixed: widget.tristate ? widget.value == null : null, |
| child: buildToggleable( |
| mouseCursor: effectiveMouseCursor, |
| focusNode: widget.focusNode, |
| autofocus: widget.autofocus, |
| size: size, |
| painter: _painter |
| ..position = position |
| ..reaction = reaction |
| ..reactionFocusFade = reactionFocusFade |
| ..reactionHoverFade = reactionHoverFade |
| ..inactiveReactionColor = effectiveInactivePressedOverlayColor |
| ..reactionColor = effectiveActivePressedOverlayColor |
| ..hoverColor = effectiveHoverOverlayColor |
| ..focusColor = effectiveFocusOverlayColor |
| ..splashRadius = effectiveSplashRadius |
| ..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 ?? checkboxTheme.shape ?? defaults.shape! |
| ..activeSide = activeSide |
| ..inactiveSide = inactiveSide, |
| ), |
| ); |
| } |
| } |
| |
| const double _kEdgeSize = Checkbox.width; |
| const double _kStrokeWidth = 2.0; |
| |
| class _CheckboxPainter 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(); |
| } |
| |
| OutlinedBorder get shape => _shape!; |
| OutlinedBorder? _shape; |
| set shape(OutlinedBorder value) { |
| if (_shape == value) { |
| return; |
| } |
| _shape = value; |
| notifyListeners(); |
| } |
| |
| BorderSide get activeSide => _activeSide!; |
| BorderSide? _activeSide; |
| set activeSide(BorderSide value) { |
| if (_activeSide == value) { |
| return; |
| } |
| _activeSide = value; |
| notifyListeners(); |
| } |
| |
| BorderSide get inactiveSide => _inactiveSide!; |
| BorderSide? _inactiveSide; |
| set inactiveSide(BorderSide value) { |
| if (_inactiveSide == value) { |
| return; |
| } |
| _inactiveSide = value; |
| notifyListeners(); |
| } |
| |
| // The square outer bounds of the checkbox at t, with the specified origin. |
| // At t == 0.0, the outer rect's size is _kEdgeSize (Checkbox.width) |
| // At t == 0.5, .. is _kEdgeSize - _kStrokeWidth |
| // At t == 1.0, .. is _kEdgeSize |
| 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 + inset, origin.dy + inset, size, size); |
| return rect; |
| } |
| |
| // The checkbox's fill color |
| Color _colorAt(double t) { |
| // As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor. |
| return t >= 0.25 ? activeColor : Color.lerp(inactiveColor, activeColor, t * 4.0)!; |
| } |
| |
| // White stroke used to paint the check and dash. |
| Paint _createStrokePaint() { |
| return Paint() |
| ..color = checkColor |
| ..style = PaintingStyle.stroke |
| ..strokeWidth = _kStrokeWidth; |
| } |
| |
| void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side) { |
| canvas.drawPath(shape.getOuterPath(outer), paint); |
| if (side != null) { |
| shape.copyWith(side: side).paint(canvas, outer); |
| } |
| } |
| |
| void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) { |
| assert(t >= 0.0 && t <= 1.0); |
| // As t goes from 0.0 to 1.0, animate the two check mark strokes from the |
| // short side to the long side. |
| final Path path = Path(); |
| const Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45); |
| const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7); |
| const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25); |
| if (t < 0.5) { |
| final double strokeT = t * 2.0; |
| final Offset drawMid = Offset.lerp(start, mid, strokeT)!; |
| path.moveTo(origin.dx + start.dx, origin.dy + start.dy); |
| path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy); |
| } else { |
| final double strokeT = (t - 0.5) * 2.0; |
| final Offset drawEnd = Offset.lerp(mid, end, strokeT)!; |
| path.moveTo(origin.dx + start.dx, origin.dy + start.dy); |
| path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy); |
| path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy); |
| } |
| canvas.drawPath(path, paint); |
| } |
| |
| void _drawDash(Canvas canvas, Offset origin, double t, Paint paint) { |
| assert(t >= 0.0 && t <= 1.0); |
| // As t goes from 0.0 to 1.0, animate the horizontal line from the |
| // mid point outwards. |
| const Offset start = Offset(_kEdgeSize * 0.2, _kEdgeSize * 0.5); |
| const Offset mid = Offset(_kEdgeSize * 0.5, _kEdgeSize * 0.5); |
| const Offset end = Offset(_kEdgeSize * 0.8, _kEdgeSize * 0.5); |
| final Offset drawStart = Offset.lerp(start, mid, 1.0 - t)!; |
| final Offset drawEnd = Offset.lerp(mid, end, t)!; |
| canvas.drawLine(origin + drawStart, origin + drawEnd, paint); |
| } |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero)); |
| |
| final Paint strokePaint = _createStrokePaint(); |
| 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; |
| |
| // Four cases: false to null, false to true, null to false, true to false |
| if (previousValue == false || value == false) { |
| final 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 = BorderSide.lerp(inactiveSide, activeSide, t); |
| _drawBox(canvas, outer, paint, border); |
| } else { |
| _drawBox(canvas, outer, paint, activeSide); |
| final double tShrink = (t - 0.5) * 2.0; |
| if (previousValue == null || value == null) { |
| _drawDash(canvas, origin, tShrink, strokePaint); |
| } else { |
| _drawCheck(canvas, origin, tShrink, strokePaint); |
| } |
| } |
| } else { // Two cases: null to true, true to null |
| final Rect outer = _outerRectAt(origin, 1.0); |
| final Paint paint = Paint() ..color = _colorAt(1.0); |
| |
| _drawBox(canvas, outer, paint, activeSide); |
| if (tNormalized <= 0.5) { |
| final double tShrink = 1.0 - tNormalized * 2.0; |
| if (previousValue ?? false) { |
| _drawCheck(canvas, origin, tShrink, strokePaint); |
| } else { |
| _drawDash(canvas, origin, tShrink, strokePaint); |
| } |
| } else { |
| final double tExpand = (tNormalized - 0.5) * 2.0; |
| if (value ?? false) { |
| _drawCheck(canvas, origin, tExpand, strokePaint); |
| } else { |
| _drawDash(canvas, origin, tExpand, strokePaint); |
| } |
| } |
| } |
| } |
| } |
| |
| // Hand coded defaults based on Material Design 2. |
| class _CheckboxDefaultsM2 extends CheckboxThemeData { |
| _CheckboxDefaultsM2(BuildContext context) |
| : _theme = Theme.of(context), |
| _colors = Theme.of(context).colorScheme; |
| |
| final ThemeData _theme; |
| final ColorScheme _colors; |
| |
| @override |
| MaterialStateBorderSide? get side { |
| return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| if (states.contains(MaterialState.selected)) { |
| return const BorderSide(width: 2.0, color: Colors.transparent); |
| } |
| return BorderSide(width: 2.0, color: _theme.disabledColor); |
| } |
| if (states.contains(MaterialState.selected)) { |
| return const BorderSide(width: 2.0, color: Colors.transparent); |
| } |
| return BorderSide(width: 2.0, color: _theme.unselectedWidgetColor); |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<Color> get fillColor { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| if (states.contains(MaterialState.selected)) { |
| return _theme.disabledColor; |
| } |
| return Colors.transparent; |
| } |
| if (states.contains(MaterialState.selected)) { |
| return _colors.secondary; |
| } |
| return Colors.transparent; |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<Color> get checkColor { |
| return MaterialStateProperty.all<Color>(const Color(0xFFFFFFFF)); |
| } |
| |
| @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.hovered)) { |
| return _theme.hoverColor; |
| } |
| if (states.contains(MaterialState.focused)) { |
| return _theme.focusColor; |
| } |
| return Colors.transparent; |
| }); |
| } |
| |
| @override |
| double get splashRadius => kRadialReactionRadius; |
| |
| @override |
| MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; |
| |
| @override |
| VisualDensity get visualDensity => _theme.visualDensity; |
| |
| @override |
| OutlinedBorder get shape => const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(1.0)), |
| ); |
| } |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Checkbox |
| |
| // 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. |
| |
| class _CheckboxDefaultsM3 extends CheckboxThemeData { |
| _CheckboxDefaultsM3(BuildContext context) |
| : _theme = Theme.of(context), |
| _colors = Theme.of(context).colorScheme; |
| |
| final ThemeData _theme; |
| final ColorScheme _colors; |
| |
| @override |
| MaterialStateBorderSide? get side { |
| return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| if (states.contains(MaterialState.selected)) { |
| return const BorderSide(width: 2.0, color: Colors.transparent); |
| } |
| return BorderSide(width: 2.0, color: _colors.onSurface.withOpacity(0.38)); |
| } |
| if (states.contains(MaterialState.selected)) { |
| return const BorderSide(width: 0.0, color: Colors.transparent); |
| } |
| if (states.contains(MaterialState.error)) { |
| return BorderSide(width: 2.0, color: _colors.error); |
| } |
| if (states.contains(MaterialState.pressed)) { |
| return BorderSide(width: 2.0, color: _colors.onSurface); |
| } |
| if (states.contains(MaterialState.hovered)) { |
| return BorderSide(width: 2.0, color: _colors.onSurface); |
| } |
| if (states.contains(MaterialState.focused)) { |
| return BorderSide(width: 2.0, color: _colors.onSurface); |
| } |
| return BorderSide(width: 2.0, color: _colors.onSurfaceVariant); |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<Color> get fillColor { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| if (states.contains(MaterialState.selected)) { |
| return _colors.onSurface.withOpacity(0.38); |
| } |
| return Colors.transparent; |
| } |
| if (states.contains(MaterialState.selected)) { |
| if (states.contains(MaterialState.error)) { |
| return _colors.error; |
| } |
| return _colors.primary; |
| } |
| return Colors.transparent; |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<Color> get checkColor { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled)) { |
| if (states.contains(MaterialState.selected)) { |
| return _colors.surface; |
| } |
| return Colors.transparent; // No icons available when the checkbox is unselected. |
| } |
| if (states.contains(MaterialState.selected)) { |
| if (states.contains(MaterialState.error)) { |
| return _colors.onError; |
| } |
| return _colors.onPrimary; |
| } |
| return Colors.transparent; // No icons available when the checkbox is unselected. |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<Color> get overlayColor { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.error)) { |
| if (states.contains(MaterialState.pressed)) { |
| return _colors.error.withOpacity(0.12); |
| } |
| if (states.contains(MaterialState.hovered)) { |
| return _colors.error.withOpacity(0.08); |
| } |
| if (states.contains(MaterialState.focused)) { |
| return _colors.error.withOpacity(0.12); |
| } |
| } |
| 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 |
| double get splashRadius => 40.0 / 2; |
| |
| @override |
| MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; |
| |
| @override |
| VisualDensity get visualDensity => _theme.visualDensity; |
| |
| @override |
| OutlinedBorder get shape => const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(2.0)), |
| ); |
| } |
| |
| // END GENERATED TOKEN PROPERTIES - Checkbox |