Add the option to configure a chip check mark color (#40608)

Add the option to configure a chip check mark color
This commit is contained in:
Anthony 2019-09-17 14:17:34 -04:00 committed by GitHub
parent fab3eb21c2
commit c17086a06d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 290 additions and 17 deletions

View file

@ -245,6 +245,38 @@ abstract class DeletableChipAttributes {
String get deleteButtonTooltipMessage;
}
/// An interface for material design chips that can have check marks.
///
/// The defaults mentioned in the documentation for each attribute are what
/// the implementing classes typically use for defaults (but this class doesn't
/// provide or enforce them).
///
/// See also:
///
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * <https://material.io/design/components/chips.html>
abstract class CheckmarkableChipAttributes {
// This class is intended to be used as an interface, and should not be
// extended directly.
factory CheckmarkableChipAttributes._() => null;
/// Whether or not to show a check mark when [selected] is true.
///
/// Defaults to true.
bool get showCheckmark;
/// [Color] of the chip's check mark when a check mark is visible.
///
/// This will override the color set by the platform's brightness setting.
///
/// If null, it will defer to a color selected by the platform's brightness
/// setting.
Color get checkmarkColor;
}
/// An interface for material design chips that can be selected.
///
/// The defaults mentioned in the documentation for each attribute are what
@ -640,6 +672,7 @@ class InputChip extends StatelessWidget
ChipAttributes,
DeletableChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes,
TappableChipAttributes {
/// Creates an [InputChip].
@ -679,6 +712,8 @@ class InputChip extends StatelessWidget
this.elevation,
this.shadowColor,
this.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(isEnabled != null),
@ -742,6 +777,10 @@ class InputChip extends StatelessWidget
@override
final Color selectedShadowColor;
@override
final bool showCheckmark;
@override
final Color checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
@ -774,6 +813,8 @@ class InputChip extends StatelessWidget
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
avatarBorder: avatarBorder,
);
@ -1044,6 +1085,7 @@ class FilterChip extends StatelessWidget
implements
ChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes {
/// Create a chip that acts like a checkbox.
///
@ -1072,6 +1114,8 @@ class FilterChip extends StatelessWidget
this.elevation,
this.shadowColor,
this.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(label != null),
@ -1122,6 +1166,10 @@ class FilterChip extends StatelessWidget
@override
final Color selectedShadowColor;
@override
final bool showCheckmark;
@override
final Color checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
@ -1152,6 +1200,8 @@ class FilterChip extends StatelessWidget
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
avatarBorder: avatarBorder,
);
}
@ -1333,6 +1383,7 @@ class RawChip extends StatefulWidget
ChipAttributes,
DeletableChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes,
TappableChipAttributes {
/// Creates a RawChip
@ -1360,7 +1411,6 @@ class RawChip extends StatefulWidget
this.pressElevation,
this.tapEnabled = true,
this.selected = false,
this.showCheckmark = true,
this.isEnabled = true,
this.disabledColor,
this.selectedColor,
@ -1374,6 +1424,8 @@ class RawChip extends StatefulWidget
this.elevation,
this.shadowColor,
this.selectedShadowColor,
this.showCheckmark = true,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(label != null),
assert(isEnabled != null),
@ -1438,15 +1490,11 @@ class RawChip extends StatefulWidget
@override
final Color selectedShadowColor;
@override
final CircleBorder avatarBorder;
/// Whether or not to show a check mark when [selected] is true.
///
/// For instance, the [ChoiceChip] sets this to false so that it can be
/// be selected without showing the check mark.
///
/// Defaults to true.
final bool showCheckmark;
@override
final Color checkmarkColor;
@override
final CircleBorder avatarBorder;
/// If set, this indicates that the chip should be disabled if all of the
/// tap callbacks ([onSelected], [onPressed]) are null.
@ -1719,6 +1767,8 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
final double pressElevation = widget.pressElevation ?? chipTheme.pressElevation ?? _defaultPressElevation;
final Color shadowColor = widget.shadowColor ?? chipTheme.shadowColor ?? _defaultShadowColor;
final Color selectedShadowColor = widget.selectedShadowColor ?? chipTheme.selectedShadowColor ?? _defaultShadowColor;
final Color checkmarkColor = widget.checkmarkColor ?? chipTheme.checkmarkColor;
final bool showCheckmark = widget.showCheckmark ?? chipTheme.showCheckmark ?? true;
final TextStyle effectiveLabelStyle = widget.labelStyle ?? chipTheme.labelStyle;
final Color resolvedLabelColor = MaterialStateProperty.resolveAs<Color>(effectiveLabelStyle?.color, _states);
@ -1779,7 +1829,8 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection),
showAvatar: hasAvatar,
showCheckmark: widget.showCheckmark,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
canTapBody: canTap,
),
value: widget.selected,
@ -2048,6 +2099,7 @@ class _ChipRenderTheme {
@required this.labelPadding,
@required this.showAvatar,
@required this.showCheckmark,
@required this.checkmarkColor,
@required this.canTapBody,
});
@ -2059,6 +2111,7 @@ class _ChipRenderTheme {
final EdgeInsets labelPadding;
final bool showAvatar;
final bool showCheckmark;
final Color checkmarkColor;
final bool canTapBody;
@override
@ -2078,6 +2131,7 @@ class _ChipRenderTheme {
&& typedOther.labelPadding == labelPadding
&& typedOther.showAvatar == showAvatar
&& typedOther.showCheckmark == showCheckmark
&& typedOther.checkmarkColor == checkmarkColor
&& typedOther.canTapBody == canTapBody;
}
@ -2092,6 +2146,7 @@ class _ChipRenderTheme {
labelPadding,
showAvatar,
showCheckmark,
checkmarkColor,
canTapBody,
);
}
@ -2584,13 +2639,17 @@ class _RenderChip extends RenderBox {
void _paintCheck(Canvas canvas, Offset origin, double size) {
Color paintColor;
switch (theme.brightness) {
case Brightness.light:
paintColor = theme.showAvatar ? Colors.white : Colors.black.withAlpha(_kCheckmarkAlpha);
break;
case Brightness.dark:
paintColor = theme.showAvatar ? Colors.black : Colors.white.withAlpha(_kCheckmarkAlpha);
break;
if (theme.checkmarkColor != null) {
paintColor = theme.checkmarkColor;
} else {
switch (theme.brightness) {
case Brightness.light:
paintColor = theme.showAvatar ? Colors.white : Colors.black.withAlpha(_kCheckmarkAlpha);
break;
case Brightness.dark:
paintColor = theme.showAvatar ? Colors.black : Colors.white.withAlpha(_kCheckmarkAlpha);
break;
}
}
final ColorTween fadeTween = ColorTween(begin: Colors.transparent, end: paintColor);

View file

@ -183,6 +183,8 @@ class ChipThemeData extends Diagnosticable {
@required this.secondarySelectedColor,
this.shadowColor,
this.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
@required this.labelPadding,
@required this.padding,
@required this.shape,
@ -326,6 +328,19 @@ class ChipThemeData extends Diagnosticable {
/// * [shadowColor]
final Color selectedShadowColor;
/// Whether or not to show a check mark when [selected] is true.
///
/// For instance, the [ChoiceChip] sets this to false so that it can be
/// selected without showing the check mark.
///
/// Defaults to true.
final bool showCheckmark;
/// Color of the chip's check mark when a check mark is visible.
///
/// This will override the color set by the platform's brightness setting.
final Color checkmarkColor;
/// The padding around the [label] widget.
///
/// By default, this is 4 logical pixels at the beginning and the end of the
@ -380,6 +395,7 @@ class ChipThemeData extends Diagnosticable {
Color secondarySelectedColor,
Color shadowColor,
Color selectedShadowColor,
Color checkmarkColor,
EdgeInsetsGeometry labelPadding,
EdgeInsetsGeometry padding,
ShapeBorder shape,
@ -397,6 +413,7 @@ class ChipThemeData extends Diagnosticable {
secondarySelectedColor: secondarySelectedColor ?? this.secondarySelectedColor,
shadowColor: shadowColor ?? this.shadowColor,
selectedShadowColor: selectedShadowColor ?? this.selectedShadowColor,
checkmarkColor: checkmarkColor ?? this.checkmarkColor,
labelPadding: labelPadding ?? this.labelPadding,
padding: padding ?? this.padding,
shape: shape ?? this.shape,
@ -425,6 +442,7 @@ class ChipThemeData extends Diagnosticable {
secondarySelectedColor: Color.lerp(a?.secondarySelectedColor, b?.secondarySelectedColor, t),
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
selectedShadowColor: Color.lerp(a?.selectedShadowColor, b?.selectedShadowColor, t),
checkmarkColor: Color.lerp(a?.checkmarkColor, b?.checkmarkColor, t),
labelPadding: EdgeInsetsGeometry.lerp(a?.labelPadding, b?.labelPadding, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
@ -446,6 +464,7 @@ class ChipThemeData extends Diagnosticable {
secondarySelectedColor,
shadowColor,
selectedShadowColor,
checkmarkColor,
labelPadding,
padding,
shape,
@ -473,6 +492,7 @@ class ChipThemeData extends Diagnosticable {
&& otherData.secondarySelectedColor == secondarySelectedColor
&& otherData.shadowColor == shadowColor
&& otherData.selectedShadowColor == selectedShadowColor
&& otherData.checkmarkColor == checkmarkColor
&& otherData.labelPadding == labelPadding
&& otherData.padding == padding
&& otherData.shape == shape
@ -499,6 +519,7 @@ class ChipThemeData extends Diagnosticable {
properties.add(ColorProperty('secondarySelectedColor', secondarySelectedColor, defaultValue: defaultData.secondarySelectedColor));
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: defaultData.shadowColor));
properties.add(ColorProperty('selectedShadowColor', selectedShadowColor, defaultValue: defaultData.selectedShadowColor));
properties.add(ColorProperty('checkMarkColor', checkmarkColor, defaultValue: defaultData.checkmarkColor));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('labelPadding', labelPadding, defaultValue: defaultData.labelPadding));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultData.padding));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultData.shape));

View file

@ -74,8 +74,10 @@ Widget _wrapForChip({
Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
@ -130,6 +132,62 @@ Future<void> _testConstrainedLabel(
expect(chipSize.height, chipParentHeight);
}
Widget _selectedInputChip({ Color checkmarkColor }) {
return InputChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
);
}
Widget _selectedFilterChip({ Color checkmarkColor }) {
return FilterChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
onSelected: (bool _) { },
);
}
Future<void> _pumpCheckmarkChip(
WidgetTester tester, {
@required Widget chip,
Color themeColor,
Brightness brightness = Brightness.light,
}) async {
await tester.pumpWidget(
_wrapForChip(
brightness: brightness,
child: Builder(
builder: (BuildContext context) {
final ChipThemeData chipTheme = ChipTheme.of(context);
return ChipTheme(
data: themeColor == null ? chipTheme : chipTheme.copyWith(
checkmarkColor: themeColor,
),
child: chip,
);
},
)
)
);
}
void _expectCheckmarkColor(Finder finder, Color color) {
expect(
finder,
paints
// The first path that is painted is the selection overlay. We do not care
// how it is painted but it has to be added it to this pattern so that the
// check mark can be checked next.
..path()
// The second path that is painted is the check mark.
..path(color: color),
);
}
void main() {
testWidgets('Chip control test', (WidgetTester tester) async {
final FeedbackTester feedback = FeedbackTester();
@ -1904,4 +1962,132 @@ void main() {
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
});
testWidgets('Input chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
brightness: Brightness.light,
);
_expectCheckmarkColor(
find.byType(InputChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
brightness: Brightness.light,
);
_expectCheckmarkColor(
find.byType(FilterChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
brightness: Brightness.dark,
);
_expectCheckmarkColor(
find.byType(InputChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
brightness: Brightness.dark,
);
_expectCheckmarkColor(
find.byType(FilterChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(checkmarkColor: const Color(0xff00ff00)),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(checkmarkColor: const Color(0xff00ff00)),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xffff0000),
);
});
testWidgets('Filter chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xffff0000),
);
});
}

View file

@ -236,6 +236,7 @@ void main() {
pressElevation: 4.0,
shadowColor: Colors.black,
selectedShadowColor: Colors.black,
checkmarkColor: Colors.black,
);
final ChipThemeData chipThemeWhite = ChipThemeData.fromDefaults(
secondaryColor: Colors.white,
@ -248,6 +249,7 @@ void main() {
pressElevation: 10.0,
shadowColor: Colors.white,
selectedShadowColor: Colors.white,
checkmarkColor: Colors.white,
);
final ChipThemeData lerp = ChipThemeData.lerp(chipThemeBlack, chipThemeWhite, 0.5);
@ -267,6 +269,7 @@ void main() {
expect(lerp.brightness, equals(Brightness.light));
expect(lerp.elevation, 3.0);
expect(lerp.pressElevation, 7.0);
expect(lerp.checkmarkColor, equals(middleGrey));
expect(ChipThemeData.lerp(null, null, 0.25), isNull);
@ -286,6 +289,7 @@ void main() {
expect(lerpANull25.brightness, equals(Brightness.light));
expect(lerpANull25.elevation, 1.25);
expect(lerpANull25.pressElevation, 2.5);
expect(lerpANull25.checkmarkColor, equals(Colors.white.withAlpha(0x40)));
final ChipThemeData lerpANull75 = ChipThemeData.lerp(null, chipThemeWhite, 0.75);
expect(lerpANull75.backgroundColor, equals(Colors.black.withAlpha(0x17)));
@ -303,6 +307,7 @@ void main() {
expect(lerpANull75.brightness, equals(Brightness.light));
expect(lerpANull75.elevation, 3.75);
expect(lerpANull75.pressElevation, 7.5);
expect(lerpANull75.checkmarkColor, equals(Colors.white.withAlpha(0xbf)));
final ChipThemeData lerpBNull25 = ChipThemeData.lerp(chipThemeBlack, null, 0.25);
expect(lerpBNull25.backgroundColor, equals(Colors.white.withAlpha(0x17)));
@ -320,6 +325,7 @@ void main() {
expect(lerpBNull25.brightness, equals(Brightness.dark));
expect(lerpBNull25.elevation, 0.75);
expect(lerpBNull25.pressElevation, 3.0);
expect(lerpBNull25.checkmarkColor, equals(Colors.black.withAlpha(0xbf)));
final ChipThemeData lerpBNull75 = ChipThemeData.lerp(chipThemeBlack, null, 0.75);
expect(lerpBNull75.backgroundColor, equals(Colors.white.withAlpha(0x08)));
@ -337,6 +343,7 @@ void main() {
expect(lerpBNull75.brightness, equals(Brightness.light));
expect(lerpBNull75.elevation, 0.25);
expect(lerpBNull75.pressElevation, 1.0);
expect(lerpBNull75.checkmarkColor, equals(Colors.black.withAlpha(0x40)));
});
testWidgets('Chip uses stateful color from chip theme', (WidgetTester tester) async {