Skip to content

Commit a433f88

Browse files
authored
Checkbox.fillColor should be applied to checkbox's background color when it is unchecked. (#125643)
1 parent 50f83fc commit a433f88

File tree

4 files changed

+206
-61
lines changed

4 files changed

+206
-61
lines changed

dev/tools/gen_defaults/lib/checkbox_template.dart

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,49 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData {
2020
final ColorScheme _colors;
2121
2222
@override
23-
MaterialStateProperty<Color> get fillColor {
24-
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
23+
MaterialStateBorderSide? get side {
24+
return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) {
2525
if (states.contains(MaterialState.disabled)) {
26-
return ${componentColor('md.comp.checkbox.selected.disabled.container')};
27-
}
28-
if (states.contains(MaterialState.error)) {
29-
return ${componentColor('md.comp.checkbox.unselected.error.outline')};
26+
if (states.contains(MaterialState.selected)) {
27+
return const BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: Colors.transparent);
28+
}
29+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.disabled.outline')}.withOpacity(${tokens['md.comp.checkbox.unselected.disabled.container.opacity']}));
3030
}
3131
if (states.contains(MaterialState.selected)) {
32-
return ${componentColor('md.comp.checkbox.selected.container')};
32+
return const BorderSide(width: ${tokens['md.comp.checkbox.selected.outline.width']}, color: Colors.transparent);
33+
}
34+
if (states.contains(MaterialState.error)) {
35+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.error.outline')});
3336
}
3437
if (states.contains(MaterialState.pressed)) {
35-
return ${componentColor('md.comp.checkbox.unselected.pressed.outline')};
38+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.pressed.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.pressed.outline')});
3639
}
3740
if (states.contains(MaterialState.hovered)) {
38-
return ${componentColor('md.comp.checkbox.unselected.hover.outline')};
41+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.hover.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.hover.outline')});
3942
}
4043
if (states.contains(MaterialState.focused)) {
41-
return ${componentColor('md.comp.checkbox.unselected.focus.outline')};
44+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.focus.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.focus.outline')});
45+
}
46+
return BorderSide(width: ${tokens['md.comp.checkbox.unselected.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.outline')});
47+
});
48+
}
49+
50+
@override
51+
MaterialStateProperty<Color> get fillColor {
52+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
53+
if (states.contains(MaterialState.disabled)) {
54+
if (states.contains(MaterialState.selected)) {
55+
return ${componentColor('md.comp.checkbox.selected.disabled.container')};
56+
}
57+
return Colors.transparent;
4258
}
43-
return ${componentColor('md.comp.checkbox.unselected.outline')};
59+
if (states.contains(MaterialState.selected)) {
60+
if (states.contains(MaterialState.error)) {
61+
return ${componentColor('md.comp.checkbox.selected.error.container')};
62+
}
63+
return ${componentColor('md.comp.checkbox.selected.container')};
64+
}
65+
return Colors.transparent;
4466
});
4567
}
4668

packages/flutter/lib/src/material/checkbox.dart

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class Checkbox extends StatefulWidget {
104104
/// design [Checkbox].
105105
///
106106
/// If a [CupertinoCheckbox] is created, the following parameters are ignored:
107-
/// [mouseCursor], [hoverColor], [overlayColor], [splashRadius],
107+
/// [mouseCursor], [fillColor], [hoverColor], [overlayColor], [splashRadius],
108108
/// [materialTapTargetSize], [visualDensity], [isError]. However, [shape] and
109109
/// [side] will still affect the [CupertinoCheckbox] and should be handled if
110110
/// native fidelity is important.
@@ -452,10 +452,9 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
452452
});
453453
}
454454

455-
BorderSide? _resolveSide(BorderSide? side) {
455+
BorderSide? _resolveSide(BorderSide? side, Set<MaterialState> states) {
456456
if (side is MaterialStateBorderSide) {
457-
final Set<MaterialState> sideStates = widget.isError ? (states..add(MaterialState.error)) : states;
458-
return MaterialStateProperty.resolveAs<BorderSide?>(side, sideStates);
457+
return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
459458
}
460459
if (!states.contains(MaterialState.selected)) {
461460
return side;
@@ -521,10 +520,12 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
521520
});
522521

523522
// Colors need to be resolved in selected and non selected states separately
524-
// so that they can be lerped between.
525-
final Set<MaterialState> errorState = states..add(MaterialState.error);
526-
final Set<MaterialState> activeStates = widget.isError ? (errorState..add(MaterialState.selected)) : states..add(MaterialState.selected);
527-
final Set<MaterialState> inactiveStates = widget.isError ? (errorState..remove(MaterialState.selected)) : states..remove(MaterialState.selected);
523+
final Set<MaterialState> activeStates = states..add(MaterialState.selected);
524+
final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected);
525+
if (widget.isError) {
526+
activeStates.add(MaterialState.error);
527+
inactiveStates.add(MaterialState.error);
528+
}
528529
final Color? activeColor = widget.fillColor?.resolve(activeStates)
529530
?? _widgetFillColor.resolve(activeStates)
530531
?? checkboxTheme.fillColor?.resolve(activeStates);
@@ -536,13 +537,26 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
536537
final Color effectiveInactiveColor = inactiveColor
537538
?? defaults.fillColor!.resolve(inactiveStates)!;
538539

539-
final Set<MaterialState> focusedStates = widget.isError ? (errorState..add(MaterialState.focused)) : states..add(MaterialState.focused);
540+
final BorderSide activeSide = _resolveSide(widget.side, activeStates)
541+
?? _resolveSide(checkboxTheme.side, activeStates)
542+
?? _resolveSide(defaults.side, activeStates)!;
543+
final BorderSide inactiveSide = _resolveSide(widget.side, inactiveStates)
544+
?? _resolveSide(checkboxTheme.side, inactiveStates)
545+
?? _resolveSide(defaults.side, inactiveStates)!;
546+
547+
final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
548+
if (widget.isError) {
549+
focusedStates.add(MaterialState.error);
550+
}
540551
Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
541552
?? widget.focusColor
542553
?? checkboxTheme.overlayColor?.resolve(focusedStates)
543554
?? defaults.overlayColor!.resolve(focusedStates)!;
544555

545-
final Set<MaterialState> hoveredStates = widget.isError ? (errorState..add(MaterialState.hovered)) : states..add(MaterialState.hovered);
556+
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
557+
if (widget.isError) {
558+
hoveredStates.add(MaterialState.error);
559+
}
546560
Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
547561
?? widget.hoverColor
548562
?? checkboxTheme.overlayColor?.resolve(hoveredStates)
@@ -606,7 +620,8 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
606620
..value = value
607621
..previousValue = _previousValue
608622
..shape = widget.shape ?? checkboxTheme.shape ?? defaults.shape!
609-
..side = _resolveSide(widget.side) ?? _resolveSide(checkboxTheme.side),
623+
..activeSide = activeSide
624+
..inactiveSide = inactiveSide,
610625
),
611626
);
612627
}
@@ -656,13 +671,23 @@ class _CheckboxPainter extends ToggleablePainter {
656671
notifyListeners();
657672
}
658673

659-
BorderSide? get side => _side;
660-
BorderSide? _side;
661-
set side(BorderSide? value) {
662-
if (_side == value) {
674+
BorderSide get activeSide => _activeSide!;
675+
BorderSide? _activeSide;
676+
set activeSide(BorderSide value) {
677+
if (_activeSide == value) {
663678
return;
664679
}
665-
_side = value;
680+
_activeSide = value;
681+
notifyListeners();
682+
}
683+
684+
BorderSide get inactiveSide => _inactiveSide!;
685+
BorderSide? _inactiveSide;
686+
set inactiveSide(BorderSide value) {
687+
if (_inactiveSide == value) {
688+
return;
689+
}
690+
_inactiveSide = value;
666691
notifyListeners();
667692
}
668693

@@ -677,8 +702,7 @@ class _CheckboxPainter extends ToggleablePainter {
677702
return rect;
678703
}
679704

680-
// The checkbox's border color if value == false, or its fill color when
681-
// value == true or null.
705+
// The checkbox's fill color
682706
Color _colorAt(double t) {
683707
// As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
684708
return t >= 0.25 ? activeColor : Color.lerp(inactiveColor, activeColor, t * 4.0)!;
@@ -692,10 +716,8 @@ class _CheckboxPainter extends ToggleablePainter {
692716
..strokeWidth = _kStrokeWidth;
693717
}
694718

695-
void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool fill) {
696-
if (fill) {
697-
canvas.drawPath(shape.getOuterPath(outer), paint);
698-
}
719+
void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side) {
720+
canvas.drawPath(shape.getOuterPath(outer), paint);
699721
if (side != null) {
700722
shape.copyWith(side: side).paint(canvas, outer);
701723
}
@@ -754,10 +776,10 @@ class _CheckboxPainter extends ToggleablePainter {
754776
final Paint paint = Paint()..color = _colorAt(t);
755777

756778
if (t <= 0.5) {
757-
final BorderSide border = side ?? BorderSide(width: 2, color: paint.color);
758-
_drawBox(canvas, outer, paint, border, false); // only paint the border
779+
final BorderSide border = BorderSide.lerp(inactiveSide, activeSide, t);
780+
_drawBox(canvas, outer, paint, border);
759781
} else {
760-
_drawBox(canvas, outer, paint, side, true);
782+
_drawBox(canvas, outer, paint, activeSide);
761783
final double tShrink = (t - 0.5) * 2.0;
762784
if (previousValue == null || value == null) {
763785
_drawDash(canvas, origin, tShrink, strokePaint);
@@ -769,7 +791,7 @@ class _CheckboxPainter extends ToggleablePainter {
769791
final Rect outer = _outerRectAt(origin, 1.0);
770792
final Paint paint = Paint() ..color = _colorAt(1.0);
771793

772-
_drawBox(canvas, outer, paint, side, true);
794+
_drawBox(canvas, outer, paint, activeSide);
773795
if (tNormalized <= 0.5) {
774796
final double tShrink = 1.0 - tNormalized * 2.0;
775797
if (previousValue ?? false) {
@@ -798,16 +820,35 @@ class _CheckboxDefaultsM2 extends CheckboxThemeData {
798820
final ThemeData _theme;
799821
final ColorScheme _colors;
800822

823+
@override
824+
MaterialStateBorderSide? get side {
825+
return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) {
826+
if (states.contains(MaterialState.disabled)) {
827+
if (states.contains(MaterialState.selected)) {
828+
return const BorderSide(width: 2.0, color: Colors.transparent);
829+
}
830+
return BorderSide(width: 2.0, color: _theme.disabledColor);
831+
}
832+
if (states.contains(MaterialState.selected)) {
833+
return const BorderSide(width: 2.0, color: Colors.transparent);
834+
}
835+
return BorderSide(width: 2.0, color: _theme.unselectedWidgetColor);
836+
});
837+
}
838+
801839
@override
802840
MaterialStateProperty<Color> get fillColor {
803841
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
804842
if (states.contains(MaterialState.disabled)) {
805-
return _theme.disabledColor;
843+
if (states.contains(MaterialState.selected)) {
844+
return _theme.disabledColor;
845+
}
846+
return Colors.transparent;
806847
}
807848
if (states.contains(MaterialState.selected)) {
808849
return _colors.secondary;
809850
}
810-
return _theme.unselectedWidgetColor;
851+
return Colors.transparent;
811852
});
812853
}
813854

@@ -865,27 +906,49 @@ class _CheckboxDefaultsM3 extends CheckboxThemeData {
865906
final ColorScheme _colors;
866907

867908
@override
868-
MaterialStateProperty<Color> get fillColor {
869-
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
909+
MaterialStateBorderSide? get side {
910+
return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) {
870911
if (states.contains(MaterialState.disabled)) {
871-
return _colors.onSurface.withOpacity(0.38);
872-
}
873-
if (states.contains(MaterialState.error)) {
874-
return _colors.error;
912+
if (states.contains(MaterialState.selected)) {
913+
return const BorderSide(width: 2.0, color: Colors.transparent);
914+
}
915+
return BorderSide(width: 2.0, color: _colors.onSurface.withOpacity(0.38));
875916
}
876917
if (states.contains(MaterialState.selected)) {
877-
return _colors.primary;
918+
return const BorderSide(width: 0.0, color: Colors.transparent);
919+
}
920+
if (states.contains(MaterialState.error)) {
921+
return BorderSide(width: 2.0, color: _colors.error);
878922
}
879923
if (states.contains(MaterialState.pressed)) {
880-
return _colors.onSurface;
924+
return BorderSide(width: 2.0, color: _colors.onSurface);
881925
}
882926
if (states.contains(MaterialState.hovered)) {
883-
return _colors.onSurface;
927+
return BorderSide(width: 2.0, color: _colors.onSurface);
884928
}
885929
if (states.contains(MaterialState.focused)) {
886-
return _colors.onSurface;
930+
return BorderSide(width: 2.0, color: _colors.onSurface);
887931
}
888-
return _colors.onSurfaceVariant;
932+
return BorderSide(width: 2.0, color: _colors.onSurfaceVariant);
933+
});
934+
}
935+
936+
@override
937+
MaterialStateProperty<Color> get fillColor {
938+
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
939+
if (states.contains(MaterialState.disabled)) {
940+
if (states.contains(MaterialState.selected)) {
941+
return _colors.onSurface.withOpacity(0.38);
942+
}
943+
return Colors.transparent;
944+
}
945+
if (states.contains(MaterialState.selected)) {
946+
if (states.contains(MaterialState.error)) {
947+
return _colors.error;
948+
}
949+
return _colors.primary;
950+
}
951+
return Colors.transparent;
889952
});
890953
}
891954

0 commit comments

Comments
 (0)