customcheckbox.dart 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import 'package:flutter/material.dart';
  5. import 'package:flyinsonolite/infrastructure/scale.dart';
  6. // Examples can assume:
  7. // bool _throwShotAway = false;
  8. // late StateSetter setState;
  9. /// A Material Design checkbox.
  10. ///
  11. /// The checkbox itself does not maintain any state. Instead, when the state of
  12. /// the checkbox changes, the widget calls the [onChanged] callback. Most
  13. /// widgets that use a checkbox will listen for the [onChanged] callback and
  14. /// rebuild the checkbox with a new [value] to update the visual appearance of
  15. /// the checkbox.
  16. ///
  17. /// The checkbox can optionally display three values - true, false, and null -
  18. /// if [tristate] is true. When [value] is null a dash is displayed. By default
  19. /// [tristate] is false and the checkbox's [value] must be true or false.
  20. ///
  21. /// Requires one of its ancestors to be a [Material] widget.
  22. ///
  23. /// {@tool dartpad}
  24. /// This example shows how you can override the default theme of
  25. /// of a [CustomCheckbox] with a [MaterialStateProperty].
  26. /// In this example, the checkbox's color will be `Colors.blue` when the [CustomCheckbox]
  27. /// is being pressed, hovered, or focused. Otherwise, the checkbox's color will
  28. /// be `Colors.red`.
  29. ///
  30. /// ** See code in examples/api/lib/material/checkbox/checkbox.0.dart **
  31. /// {@end-tool}
  32. ///
  33. /// See also:
  34. ///
  35. /// * [CheckboxListTile], which combines this widget with a [ListTile] so that
  36. /// you can give the checkbox a label.
  37. /// * [Switch], a widget with semantics similar to [CustomCheckbox].
  38. /// * [Radio], for selecting among a set of explicit values.
  39. /// * [Slider], for selecting a value in a range.
  40. /// * <https://material.io/design/components/selection-controls.html#checkboxes>
  41. /// * <https://material.io/design/components/lists.html#types>
  42. class CustomCheckbox extends StatefulWidget {
  43. /// Creates a Material Design checkbox.
  44. ///
  45. /// The checkbox itself does not maintain any state. Instead, when the state of
  46. /// the checkbox changes, the widget calls the [onChanged] callback. Most
  47. /// widgets that use a checkbox will listen for the [onChanged] callback and
  48. /// rebuild the checkbox with a new [value] to update the visual appearance of
  49. /// the checkbox.
  50. ///
  51. /// The following arguments are required:
  52. ///
  53. /// * [value], which determines whether the checkbox is checked. The [value]
  54. /// can only be null if [tristate] is true.
  55. /// * [onChanged], which is called when the value of the checkbox should
  56. /// change. It can be set to null to disable the checkbox.
  57. ///
  58. /// The values of [tristate] and [autofocus] must not be null.
  59. const CustomCheckbox({
  60. super.key,
  61. required this.value,
  62. this.tristate = false,
  63. required this.onChanged,
  64. this.mouseCursor,
  65. this.activeColor,
  66. this.fillColor,
  67. this.checkColor,
  68. this.focusColor,
  69. this.hoverColor,
  70. this.overlayColor,
  71. this.splashRadius,
  72. this.materialTapTargetSize,
  73. this.visualDensity,
  74. this.focusNode,
  75. this.autofocus = false,
  76. this.shape,
  77. this.side,
  78. this.isError = false,
  79. }) : assert(tristate != null),
  80. assert(tristate || value != null),
  81. assert(autofocus != null);
  82. /// Whether this checkbox is checked.
  83. ///
  84. /// When [tristate] is true, a value of null corresponds to the mixed state.
  85. /// When [tristate] is false, this value must not be null.
  86. final bool? value;
  87. /// Called when the value of the checkbox should change.
  88. ///
  89. /// The checkbox passes the new value to the callback but does not actually
  90. /// change state until the parent widget rebuilds the checkbox with the new
  91. /// value.
  92. ///
  93. /// If this callback is null, the checkbox will be displayed as disabled
  94. /// and will not respond to input gestures.
  95. ///
  96. /// When the checkbox is tapped, if [tristate] is false (the default) then
  97. /// the [onChanged] callback will be applied to `!value`. If [tristate] is
  98. /// true this callback cycle from false to true to null.
  99. ///
  100. /// The callback provided to [onChanged] should update the state of the parent
  101. /// [StatefulWidget] using the [State.setState] method, so that the parent
  102. /// gets rebuilt; for example:
  103. ///
  104. /// ```dart
  105. /// Checkbox(
  106. /// value: _throwShotAway,
  107. /// onChanged: (bool? newValue) {
  108. /// setState(() {
  109. /// _throwShotAway = newValue!;
  110. /// });
  111. /// },
  112. /// )
  113. /// ```
  114. final ValueChanged<bool?>? onChanged;
  115. /// {@template flutter.material.checkbox.mouseCursor}
  116. /// The cursor for a mouse pointer when it enters or is hovering over the
  117. /// widget.
  118. ///
  119. /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  120. /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  121. ///
  122. /// * [MaterialState.selected].
  123. /// * [MaterialState.hovered].
  124. /// * [MaterialState.focused].
  125. /// * [MaterialState.disabled].
  126. /// {@endtemplate}
  127. ///
  128. /// When [value] is null and [tristate] is true, [MaterialState.selected] is
  129. /// included as a state.
  130. ///
  131. /// If null, then the value of [CheckboxThemeData.mouseCursor] is used. If
  132. /// that is also null, then [MaterialStateMouseCursor.clickable] is used.
  133. ///
  134. /// See also:
  135. ///
  136. /// * [MaterialStateMouseCursor], a [MouseCursor] that implements
  137. /// `MaterialStateProperty` which is used in APIs that need to accept
  138. /// either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
  139. final MouseCursor? mouseCursor;
  140. /// The color to use when this checkbox is checked.
  141. ///
  142. /// Defaults to [ColorScheme.secondary].
  143. ///
  144. /// If [fillColor] returns a non-null color in the [MaterialState.selected]
  145. /// state, it will be used instead of this color.
  146. final Color? activeColor;
  147. /// {@template flutter.material.checkbox.fillColor}
  148. /// The color that fills the checkbox, in all [MaterialState]s.
  149. ///
  150. /// Resolves in the following states:
  151. /// * [MaterialState.selected].
  152. /// * [MaterialState.hovered].
  153. /// * [MaterialState.focused].
  154. /// * [MaterialState.disabled].
  155. ///
  156. /// {@tool snippet}
  157. /// This example resolves the [fillColor] based on the current [MaterialState]
  158. /// of the [CustomCheckbox], providing a different [Color] when it is
  159. /// [MaterialState.disabled].
  160. ///
  161. /// ```dart
  162. /// Checkbox(
  163. /// value: true,
  164. /// onChanged: (_){},
  165. /// fillColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
  166. /// if (states.contains(MaterialState.disabled)) {
  167. /// return Colors.orange.withOpacity(.32);
  168. /// }
  169. /// return Colors.orange;
  170. /// })
  171. /// )
  172. /// ```
  173. /// {@end-tool}
  174. /// {@endtemplate}
  175. ///
  176. /// If null, then the value of [activeColor] is used in the selected
  177. /// state. If that is also null, the value of [CheckboxThemeData.fillColor]
  178. /// is used. If that is also null, then [ThemeData.disabledColor] is used in
  179. /// the disabled state, [ColorScheme.secondary] is used in the
  180. /// selected state, and [ThemeData.unselectedWidgetColor] is used in the
  181. /// default state.
  182. final MaterialStateProperty<Color?>? fillColor;
  183. /// {@template flutter.material.checkbox.checkColor}
  184. /// The color to use for the check icon when this checkbox is checked.
  185. /// {@endtemplate}
  186. ///
  187. /// If null, then the value of [CheckboxThemeData.checkColor] is used. If
  188. /// that is also null, then Color(0xFFFFFFFF) is used.
  189. final Color? checkColor;
  190. /// If true the checkbox's [value] can be true, false, or null.
  191. ///
  192. /// [CustomCheckbox] displays a dash when its value is null.
  193. ///
  194. /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
  195. /// callback will be applied to true if the current value is false, to null if
  196. /// value is true, and to false if value is null (i.e. it cycles through false
  197. /// => true => null => false when tapped).
  198. ///
  199. /// If tristate is false (the default), [value] must not be null.
  200. final bool tristate;
  201. /// {@template flutter.material.checkbox.materialTapTargetSize}
  202. /// Configures the minimum size of the tap target.
  203. /// {@endtemplate}
  204. ///
  205. /// If null, then the value of [CheckboxThemeData.materialTapTargetSize] is
  206. /// used. If that is also null, then the value of
  207. /// [ThemeData.materialTapTargetSize] is used.
  208. ///
  209. /// See also:
  210. ///
  211. /// * [MaterialTapTargetSize], for a description of how this affects tap targets.
  212. final MaterialTapTargetSize? materialTapTargetSize;
  213. /// {@template flutter.material.checkbox.visualDensity}
  214. /// Defines how compact the checkbox's layout will be.
  215. /// {@endtemplate}
  216. ///
  217. /// {@macro flutter.material.themedata.visualDensity}
  218. ///
  219. /// If null, then the value of [CheckboxThemeData.visualDensity] is used. If
  220. /// that is also null, then the value of [ThemeData.visualDensity] is used.
  221. ///
  222. /// See also:
  223. ///
  224. /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
  225. /// widgets within a [Theme].
  226. final VisualDensity? visualDensity;
  227. /// The color for the checkbox's [Material] when it has the input focus.
  228. ///
  229. /// If [overlayColor] returns a non-null color in the [MaterialState.focused]
  230. /// state, it will be used instead.
  231. ///
  232. /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
  233. /// focused state. If that is also null, then the value of
  234. /// [ThemeData.focusColor] is used.
  235. final Color? focusColor;
  236. /// The color for the checkbox's [Material] when a pointer is hovering over it.
  237. ///
  238. /// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
  239. /// state, it will be used instead.
  240. ///
  241. /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
  242. /// hovered state. If that is also null, then the value of
  243. /// [ThemeData.hoverColor] is used.
  244. final Color? hoverColor;
  245. /// {@template flutter.material.checkbox.overlayColor}
  246. /// The color for the checkbox's [Material].
  247. ///
  248. /// Resolves in the following states:
  249. /// * [MaterialState.pressed].
  250. /// * [MaterialState.selected].
  251. /// * [MaterialState.hovered].
  252. /// * [MaterialState.focused].
  253. /// {@endtemplate}
  254. ///
  255. /// If null, then the value of [activeColor] with alpha
  256. /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
  257. /// pressed, focused and hovered state. If that is also null,
  258. /// the value of [CheckboxThemeData.overlayColor] is used. If that is
  259. /// also null, then the value of [ColorScheme.secondary] with alpha
  260. /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
  261. /// is used in the pressed, focused and hovered state.
  262. final MaterialStateProperty<Color?>? overlayColor;
  263. /// {@template flutter.material.checkbox.splashRadius}
  264. /// The splash radius of the circular [Material] ink response.
  265. /// {@endtemplate}
  266. ///
  267. /// If null, then the value of [CheckboxThemeData.splashRadius] is used. If
  268. /// that is also null, then [kRadialReactionRadius] is used.
  269. final double? splashRadius;
  270. /// {@macro flutter.widgets.Focus.focusNode}
  271. final FocusNode? focusNode;
  272. /// {@macro flutter.widgets.Focus.autofocus}
  273. final bool autofocus;
  274. /// {@template flutter.material.checkbox.shape}
  275. /// The shape of the checkbox's [Material].
  276. /// {@endtemplate}
  277. ///
  278. /// If this property is null then [CheckboxThemeData.shape] of [ThemeData.checkboxTheme]
  279. /// is used. If that's null then the shape will be a [RoundedRectangleBorder]
  280. /// with a circular corner radius of 1.0.
  281. final OutlinedBorder? shape;
  282. /// {@template flutter.material.checkbox.side}
  283. /// The color and width of the checkbox's border.
  284. ///
  285. /// This property can be a [MaterialStateBorderSide] that can
  286. /// specify different border color and widths depending on the
  287. /// checkbox's state.
  288. ///
  289. /// Resolves in the following states:
  290. /// * [MaterialState.pressed].
  291. /// * [MaterialState.selected].
  292. /// * [MaterialState.hovered].
  293. /// * [MaterialState.focused].
  294. /// * [MaterialState.disabled].
  295. ///
  296. /// If this property is not a [MaterialStateBorderSide] and it is
  297. /// non-null, then it is only rendered when the checkbox's value is
  298. /// false. The difference in interpretation is for backwards
  299. /// compatibility.
  300. /// {@endtemplate}
  301. ///
  302. /// If this property is null, then [CheckboxThemeData.side] of
  303. /// [ThemeData.checkboxTheme] is used. If that is also null, then the side
  304. /// will be width 2.
  305. final BorderSide? side;
  306. /// True if this checkbox wants to show an error state.
  307. ///
  308. /// The checkbox will have different default container color and check color when
  309. /// this is true. This is only used when [ThemeData.useMaterial3] is set to true.
  310. ///
  311. /// Must not be null. Defaults to false.
  312. final bool isError;
  313. @override
  314. State<CustomCheckbox> createState() => _CustomCheckboxState();
  315. }
  316. class _CustomCheckboxState extends State<CustomCheckbox>
  317. with TickerProviderStateMixin, ToggleableStateMixin {
  318. final _CheckboxPainter _painter = _CheckboxPainter();
  319. bool? _previousValue;
  320. @override
  321. void initState() {
  322. super.initState();
  323. _previousValue = widget.value;
  324. }
  325. @override
  326. void didUpdateWidget(CustomCheckbox oldWidget) {
  327. super.didUpdateWidget(oldWidget);
  328. if (oldWidget.value != widget.value) {
  329. _previousValue = oldWidget.value;
  330. animateToValue();
  331. }
  332. }
  333. @override
  334. void dispose() {
  335. _painter.dispose();
  336. super.dispose();
  337. }
  338. @override
  339. ValueChanged<bool?>? get onChanged => widget.onChanged;
  340. @override
  341. bool get tristate => widget.tristate;
  342. @override
  343. bool? get value => widget.value;
  344. MaterialStateProperty<Color?> get _widgetFillColor {
  345. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  346. if (states.contains(MaterialState.disabled)) {
  347. return null;
  348. }
  349. if (states.contains(MaterialState.selected)) {
  350. return widget.activeColor;
  351. }
  352. return null;
  353. });
  354. }
  355. BorderSide? _resolveSide(BorderSide? side) {
  356. if (side is MaterialStateBorderSide) {
  357. return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
  358. }
  359. if (!states.contains(MaterialState.selected)) {
  360. return side;
  361. }
  362. return null;
  363. }
  364. @override
  365. Widget build(BuildContext context) {
  366. assert(debugCheckHasMaterial(context));
  367. final CheckboxThemeData checkboxTheme = CheckboxTheme.of(context);
  368. final CheckboxThemeData defaults = Theme.of(context).useMaterial3
  369. ? _CheckboxDefaultsM3(context)
  370. : _CheckboxDefaultsM2(context);
  371. final MaterialTapTargetSize effectiveMaterialTapTargetSize =
  372. widget.materialTapTargetSize ??
  373. checkboxTheme.materialTapTargetSize ??
  374. defaults.materialTapTargetSize!;
  375. final VisualDensity effectiveVisualDensity = widget.visualDensity ??
  376. checkboxTheme.visualDensity ??
  377. defaults.visualDensity!;
  378. Size sizeBeforeScale;
  379. switch (effectiveMaterialTapTargetSize) {
  380. case MaterialTapTargetSize.padded:
  381. sizeBeforeScale =
  382. Size(kMinInteractiveDimension.s, kMinInteractiveDimension.s);
  383. break;
  384. case MaterialTapTargetSize.shrinkWrap:
  385. sizeBeforeScale = const Size(
  386. kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
  387. break;
  388. }
  389. Size size = Size(
  390. (sizeBeforeScale.width + effectiveVisualDensity.baseSizeAdjustment.dx)
  391. .s,
  392. (sizeBeforeScale.height + effectiveVisualDensity.baseSizeAdjustment.dy)
  393. .s);
  394. final MaterialStateProperty<MouseCursor> effectiveMouseCursor =
  395. MaterialStateProperty.resolveWith<MouseCursor>(
  396. (Set<MaterialState> states) {
  397. return MaterialStateProperty.resolveAs<MouseCursor?>(
  398. widget.mouseCursor, states) ??
  399. checkboxTheme.mouseCursor?.resolve(states) ??
  400. MaterialStateMouseCursor.clickable.resolve(states);
  401. });
  402. // Colors need to be resolved in selected and non selected states separately
  403. // so that they can be lerped between.
  404. final Set<MaterialState> errorState = states..add(MaterialState.error);
  405. final Set<MaterialState> activeStates =
  406. widget.isError ? (errorState..add(MaterialState.selected)) : states
  407. ..add(MaterialState.selected);
  408. final Set<MaterialState> inactiveStates =
  409. widget.isError ? (errorState..remove(MaterialState.selected)) : states
  410. ..remove(MaterialState.selected);
  411. final Color? activeColor = widget.fillColor?.resolve(activeStates) ??
  412. _widgetFillColor.resolve(activeStates) ??
  413. checkboxTheme.fillColor?.resolve(activeStates);
  414. final Color effectiveActiveColor =
  415. activeColor ?? defaults.fillColor!.resolve(activeStates)!;
  416. final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates) ??
  417. _widgetFillColor.resolve(inactiveStates) ??
  418. checkboxTheme.fillColor?.resolve(inactiveStates);
  419. final Color effectiveInactiveColor =
  420. inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
  421. final Set<MaterialState> focusedStates =
  422. widget.isError ? (errorState..add(MaterialState.focused)) : states
  423. ..add(MaterialState.focused);
  424. Color effectiveFocusOverlayColor =
  425. widget.overlayColor?.resolve(focusedStates) ??
  426. widget.focusColor ??
  427. checkboxTheme.overlayColor?.resolve(focusedStates) ??
  428. defaults.overlayColor!.resolve(focusedStates)!;
  429. final Set<MaterialState> hoveredStates =
  430. widget.isError ? (errorState..add(MaterialState.hovered)) : states
  431. ..add(MaterialState.hovered);
  432. Color effectiveHoverOverlayColor =
  433. widget.overlayColor?.resolve(hoveredStates) ??
  434. widget.hoverColor ??
  435. checkboxTheme.overlayColor?.resolve(hoveredStates) ??
  436. defaults.overlayColor!.resolve(hoveredStates)!;
  437. final Set<MaterialState> activePressedStates = activeStates
  438. ..add(MaterialState.pressed);
  439. final Color effectiveActivePressedOverlayColor =
  440. widget.overlayColor?.resolve(activePressedStates) ??
  441. checkboxTheme.overlayColor?.resolve(activePressedStates) ??
  442. activeColor?.withAlpha(kRadialReactionAlpha) ??
  443. defaults.overlayColor!.resolve(activePressedStates)!;
  444. final Set<MaterialState> inactivePressedStates = inactiveStates
  445. ..add(MaterialState.pressed);
  446. final Color effectiveInactivePressedOverlayColor =
  447. widget.overlayColor?.resolve(inactivePressedStates) ??
  448. checkboxTheme.overlayColor?.resolve(inactivePressedStates) ??
  449. inactiveColor?.withAlpha(kRadialReactionAlpha) ??
  450. defaults.overlayColor!.resolve(inactivePressedStates)!;
  451. if (downPosition != null) {
  452. effectiveHoverOverlayColor = states.contains(MaterialState.selected)
  453. ? effectiveActivePressedOverlayColor
  454. : effectiveInactivePressedOverlayColor;
  455. effectiveFocusOverlayColor = states.contains(MaterialState.selected)
  456. ? effectiveActivePressedOverlayColor
  457. : effectiveInactivePressedOverlayColor;
  458. }
  459. final Set<MaterialState> checkStates =
  460. widget.isError ? (states..add(MaterialState.error)) : states;
  461. final Color effectiveCheckColor = widget.checkColor ??
  462. checkboxTheme.checkColor?.resolve(checkStates) ??
  463. defaults.checkColor!.resolve(checkStates)!;
  464. final double effectiveSplashRadius = widget.splashRadius ??
  465. checkboxTheme.splashRadius ??
  466. defaults.splashRadius!.s;
  467. return Semantics(
  468. checked: widget.value ?? false,
  469. mixed: widget.tristate ? widget.value == null : null,
  470. child: buildToggleable(
  471. mouseCursor: effectiveMouseCursor,
  472. focusNode: widget.focusNode,
  473. autofocus: widget.autofocus,
  474. size: size,
  475. painter: _painter
  476. ..position = position
  477. ..reaction = reaction
  478. ..reactionFocusFade = reactionFocusFade
  479. ..reactionHoverFade = reactionHoverFade
  480. ..inactiveReactionColor = effectiveInactivePressedOverlayColor
  481. ..reactionColor = effectiveActivePressedOverlayColor
  482. ..hoverColor = effectiveHoverOverlayColor
  483. ..focusColor = effectiveFocusOverlayColor
  484. ..splashRadius = effectiveSplashRadius
  485. ..downPosition = downPosition
  486. ..isFocused = states.contains(MaterialState.focused)
  487. ..isHovered = states.contains(MaterialState.hovered)
  488. ..activeColor = effectiveActiveColor
  489. ..inactiveColor = effectiveInactiveColor
  490. ..checkColor = effectiveCheckColor
  491. ..value = value
  492. ..previousValue = _previousValue
  493. ..shape = widget.shape ??
  494. checkboxTheme.shape ??
  495. RoundedRectangleBorder(
  496. borderRadius: BorderRadius.all(
  497. Radius.circular(1.s),
  498. ),
  499. )
  500. ..side =
  501. _resolveSide(widget.side) ?? _resolveSide(checkboxTheme.side),
  502. ),
  503. );
  504. }
  505. }
  506. class _CheckboxPainter extends ToggleablePainter {
  507. double get _kEdgeSize => 18.s;
  508. double get _kStrokeWidth => 2.s;
  509. Color get checkColor => _checkColor!;
  510. Color? _checkColor;
  511. set checkColor(Color value) {
  512. if (_checkColor == value) {
  513. return;
  514. }
  515. _checkColor = value;
  516. notifyListeners();
  517. }
  518. bool? get value => _value;
  519. bool? _value;
  520. set value(bool? value) {
  521. if (_value == value) {
  522. return;
  523. }
  524. _value = value;
  525. notifyListeners();
  526. }
  527. bool? get previousValue => _previousValue;
  528. bool? _previousValue;
  529. set previousValue(bool? value) {
  530. if (_previousValue == value) {
  531. return;
  532. }
  533. _previousValue = value;
  534. notifyListeners();
  535. }
  536. OutlinedBorder get shape => _shape!;
  537. OutlinedBorder? _shape;
  538. set shape(OutlinedBorder value) {
  539. if (_shape == value) {
  540. return;
  541. }
  542. _shape = value;
  543. notifyListeners();
  544. }
  545. BorderSide? get side => _side;
  546. BorderSide? _side;
  547. set side(BorderSide? value) {
  548. if (_side == value) {
  549. return;
  550. }
  551. _side = value;
  552. notifyListeners();
  553. }
  554. // The square outer bounds of the checkbox at t, with the specified origin.
  555. // At t == 0.0, the outer rect's size is _kEdgeSize (Checkbox.width)
  556. // At t == 0.5, .. is _kEdgeSize - _kStrokeWidth
  557. // At t == 1.0, .. is _kEdgeSize
  558. Rect _outerRectAt(Offset origin, double t) {
  559. final double inset = 1.0 - (t - 0.5).abs() * 2.0;
  560. final double size = _kEdgeSize - inset * _kStrokeWidth;
  561. final Rect rect =
  562. Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
  563. return rect;
  564. }
  565. // The checkbox's border color if value == false, or its fill color when
  566. // value == true or null.
  567. Color _colorAt(double t) {
  568. // As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
  569. return t >= 0.25
  570. ? activeColor
  571. : Color.lerp(inactiveColor, activeColor, t * 4.0)!;
  572. }
  573. // White stroke used to paint the check and dash.
  574. Paint _createStrokePaint() {
  575. return Paint()
  576. ..color = checkColor
  577. ..style = PaintingStyle.stroke
  578. ..strokeWidth = _kStrokeWidth;
  579. }
  580. void _drawBox(
  581. Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool fill) {
  582. if (fill) {
  583. canvas.drawPath(shape.getOuterPath(outer), paint);
  584. }
  585. if (side != null) {
  586. shape.copyWith(side: side).paint(canvas, outer);
  587. }
  588. }
  589. void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
  590. assert(t >= 0.0 && t <= 1.0);
  591. // As t goes from 0.0 to 1.0, animate the two check mark strokes from the
  592. // short side to the long side.
  593. final Path path = Path();
  594. Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45);
  595. Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7);
  596. Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25);
  597. if (t < 0.5) {
  598. final double strokeT = t * 2.0;
  599. final Offset drawMid = Offset.lerp(start, mid, strokeT)!;
  600. path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
  601. path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
  602. } else {
  603. final double strokeT = (t - 0.5) * 2.0;
  604. final Offset drawEnd = Offset.lerp(mid, end, strokeT)!;
  605. path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
  606. path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
  607. path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
  608. }
  609. canvas.drawPath(path, paint);
  610. }
  611. void _drawDash(Canvas canvas, Offset origin, double t, Paint paint) {
  612. assert(t >= 0.0 && t <= 1.0);
  613. // As t goes from 0.0 to 1.0, animate the horizontal line from the
  614. // mid point outwards.
  615. Offset start = Offset(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
  616. Offset mid = Offset(_kEdgeSize * 0.5, _kEdgeSize * 0.5);
  617. Offset end = Offset(_kEdgeSize * 0.8, _kEdgeSize * 0.5);
  618. final Offset drawStart = Offset.lerp(start, mid, 1.0 - t)!;
  619. final Offset drawEnd = Offset.lerp(mid, end, t)!;
  620. canvas.drawLine(origin + drawStart, origin + drawEnd, paint);
  621. }
  622. @override
  623. void paint(Canvas canvas, Size size) {
  624. paintRadialReaction(canvas: canvas, origin: size.center(Offset.zero));
  625. final Paint strokePaint = _createStrokePaint();
  626. final Offset origin = size / 2.0 - Size.square(_kEdgeSize) / 2.0 as Offset;
  627. final AnimationStatus status = position.status;
  628. final double tNormalized =
  629. status == AnimationStatus.forward || status == AnimationStatus.completed
  630. ? position.value
  631. : 1.0 - position.value;
  632. // Four cases: false to null, false to true, null to false, true to false
  633. if (previousValue == false || value == false) {
  634. final double t = value == false ? 1.0 - tNormalized : tNormalized;
  635. final Rect outer = _outerRectAt(origin, t);
  636. final Paint paint = Paint()..color = _colorAt(t);
  637. if (t <= 0.5) {
  638. final BorderSide border =
  639. side ?? BorderSide(width: 2.s, color: paint.color);
  640. _drawBox(canvas, outer, paint, border, false); // only paint the border
  641. } else {
  642. _drawBox(canvas, outer, paint, side, true);
  643. final double tShrink = (t - 0.5) * 2.0;
  644. if (previousValue == null || value == null) {
  645. _drawDash(canvas, origin, tShrink, strokePaint);
  646. } else {
  647. _drawCheck(canvas, origin, tShrink, strokePaint);
  648. }
  649. }
  650. } else {
  651. // Two cases: null to true, true to null
  652. final Rect outer = _outerRectAt(origin, 1.0);
  653. final Paint paint = Paint()..color = _colorAt(1.0);
  654. _drawBox(canvas, outer, paint, side, true);
  655. if (tNormalized <= 0.5) {
  656. final double tShrink = 1.0 - tNormalized * 2.0;
  657. if (previousValue ?? false) {
  658. _drawCheck(canvas, origin, tShrink, strokePaint);
  659. } else {
  660. _drawDash(canvas, origin, tShrink, strokePaint);
  661. }
  662. } else {
  663. final double tExpand = (tNormalized - 0.5) * 2.0;
  664. if (value ?? false) {
  665. _drawCheck(canvas, origin, tExpand, strokePaint);
  666. } else {
  667. _drawDash(canvas, origin, tExpand, strokePaint);
  668. }
  669. }
  670. }
  671. }
  672. }
  673. // Hand coded defaults based on Material Design 2.
  674. class _CheckboxDefaultsM2 extends CheckboxThemeData {
  675. _CheckboxDefaultsM2(BuildContext context)
  676. : _theme = Theme.of(context),
  677. _colors = Theme.of(context).colorScheme;
  678. final ThemeData _theme;
  679. final ColorScheme _colors;
  680. @override
  681. MaterialStateProperty<Color> get fillColor {
  682. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  683. if (states.contains(MaterialState.disabled)) {
  684. return _theme.disabledColor;
  685. }
  686. if (states.contains(MaterialState.selected)) {
  687. return _colors.secondary;
  688. }
  689. return _theme.unselectedWidgetColor;
  690. });
  691. }
  692. @override
  693. MaterialStateProperty<Color> get checkColor {
  694. return MaterialStateProperty.all<Color>(const Color(0xFFFFFFFF));
  695. }
  696. @override
  697. MaterialStateProperty<Color?> get overlayColor {
  698. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  699. if (states.contains(MaterialState.pressed)) {
  700. return fillColor.resolve(states).withAlpha(kRadialReactionAlpha);
  701. }
  702. if (states.contains(MaterialState.hovered)) {
  703. return _theme.hoverColor;
  704. }
  705. if (states.contains(MaterialState.focused)) {
  706. return _theme.focusColor;
  707. }
  708. return Colors.transparent;
  709. });
  710. }
  711. @override
  712. double get splashRadius => kRadialReactionRadius;
  713. @override
  714. MaterialTapTargetSize get materialTapTargetSize =>
  715. _theme.materialTapTargetSize;
  716. @override
  717. VisualDensity get visualDensity => _theme.visualDensity;
  718. }
  719. // BEGIN GENERATED TOKEN PROPERTIES - Checkbox
  720. // Do not edit by hand. The code between the "BEGIN GENERATED" and
  721. // "END GENERATED" comments are generated from data in the Material
  722. // Design token database by the script:
  723. // dev/tools/gen_defaults/bin/gen_defaults.dart.
  724. // Token database version: v0_132
  725. class _CheckboxDefaultsM3 extends CheckboxThemeData {
  726. _CheckboxDefaultsM3(BuildContext context)
  727. : _theme = Theme.of(context),
  728. _colors = Theme.of(context).colorScheme;
  729. final ThemeData _theme;
  730. final ColorScheme _colors;
  731. @override
  732. MaterialStateProperty<Color> get fillColor {
  733. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  734. if (states.contains(MaterialState.disabled)) {
  735. return _colors.onSurface.withOpacity(0.38);
  736. }
  737. if (states.contains(MaterialState.error)) {
  738. return _colors.error;
  739. }
  740. if (states.contains(MaterialState.selected)) {
  741. return _colors.primary;
  742. }
  743. if (states.contains(MaterialState.pressed)) {
  744. return _colors.onSurface;
  745. }
  746. if (states.contains(MaterialState.hovered)) {
  747. return _colors.onSurface;
  748. }
  749. if (states.contains(MaterialState.focused)) {
  750. return _colors.onSurface;
  751. }
  752. return _colors.onSurfaceVariant;
  753. });
  754. }
  755. @override
  756. MaterialStateProperty<Color> get checkColor {
  757. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  758. if (states.contains(MaterialState.disabled)) {
  759. if (states.contains(MaterialState.selected)) {
  760. return _colors.surface;
  761. }
  762. return Colors
  763. .transparent; // No icons available when the checkbox is unselected.
  764. }
  765. if (states.contains(MaterialState.selected)) {
  766. if (states.contains(MaterialState.error)) {
  767. return _colors.onError;
  768. }
  769. return _colors.onPrimary;
  770. }
  771. return Colors
  772. .transparent; // No icons available when the checkbox is unselected.
  773. });
  774. }
  775. @override
  776. MaterialStateProperty<Color> get overlayColor {
  777. return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  778. if (states.contains(MaterialState.error)) {
  779. if (states.contains(MaterialState.pressed)) {
  780. return _colors.error.withOpacity(0.12);
  781. }
  782. if (states.contains(MaterialState.hovered)) {
  783. return _colors.error.withOpacity(0.08);
  784. }
  785. if (states.contains(MaterialState.focused)) {
  786. return _colors.error.withOpacity(0.12);
  787. }
  788. }
  789. if (states.contains(MaterialState.selected)) {
  790. if (states.contains(MaterialState.pressed)) {
  791. return _colors.onSurface.withOpacity(0.12);
  792. }
  793. if (states.contains(MaterialState.hovered)) {
  794. return _colors.primary.withOpacity(0.08);
  795. }
  796. if (states.contains(MaterialState.focused)) {
  797. return _colors.primary.withOpacity(0.12);
  798. }
  799. return Colors.transparent;
  800. }
  801. if (states.contains(MaterialState.pressed)) {
  802. return _colors.primary.withOpacity(0.12);
  803. }
  804. if (states.contains(MaterialState.hovered)) {
  805. return _colors.onSurface.withOpacity(0.08);
  806. }
  807. if (states.contains(MaterialState.focused)) {
  808. return _colors.onSurface.withOpacity(0.12);
  809. }
  810. return Colors.transparent;
  811. });
  812. }
  813. @override
  814. double get splashRadius => 20.s;
  815. @override
  816. MaterialTapTargetSize get materialTapTargetSize =>
  817. _theme.materialTapTargetSize;
  818. @override
  819. VisualDensity get visualDensity => _theme.visualDensity;
  820. }
  821. // END GENERATED TOKEN PROPERTIES - Checkbox