mirror of
https://github.com/flutter/flutter
synced 2024-09-13 21:32:11 +00:00
Migrated Checkbox
to Material 3 - Added Error State (#111153)
This commit is contained in:
parent
4d3c122434
commit
98eac3f198
|
@ -23,32 +23,14 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData {
|
|||
MaterialStateProperty<Color> get fillColor {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.disabled.container')};
|
||||
}
|
||||
return ${componentColor('md.comp.checkbox.unselected.disabled.outline')}.withOpacity(${opacity('md.comp.checkbox.unselected.disabled.container.opacity')});
|
||||
return ${componentColor('md.comp.checkbox.selected.disabled.container')};
|
||||
}
|
||||
if (states.contains(MaterialState.error)) {
|
||||
return ${componentColor('md.comp.checkbox.unselected.error.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.pressed.container')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.hover.container')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.focus.container')};
|
||||
}
|
||||
return ${componentColor('md.comp.checkbox.selected.container')};
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return ${componentColor('md.comp.checkbox.unselected.pressed.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${componentColor('md.comp.checkbox.unselected.hover.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${componentColor('md.comp.checkbox.unselected.focus.outline')};
|
||||
}
|
||||
return ${componentColor('md.comp.checkbox.unselected.outline')};
|
||||
});
|
||||
}
|
||||
|
@ -63,14 +45,8 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData {
|
|||
return Colors.transparent; // No icons available when the checkbox is unselected.
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.pressed.icon')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.hover.icon')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.focus.icon')};
|
||||
if (states.contains(MaterialState.error)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.error.icon')};
|
||||
}
|
||||
return ${componentColor('md.comp.checkbox.selected.icon')};
|
||||
}
|
||||
|
@ -81,6 +57,17 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData {
|
|||
@override
|
||||
MaterialStateProperty<Color> get overlayColor {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.error)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return ${componentColor('md.comp.checkbox.error.pressed.state-layer')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${componentColor('md.comp.checkbox.error.hover.state-layer')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${componentColor('md.comp.checkbox.error.focus.state-layer')}.withOpacity(0.12);
|
||||
}
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return ${componentColor('md.comp.checkbox.selected.pressed.state-layer')};
|
||||
|
|
74
examples/api/lib/material/checkbox/checkbox.1.dart
Normal file
74
examples/api/lib/material/checkbox/checkbox.1.dart
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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.
|
||||
|
||||
// Flutter code sample for M3 Checkbox with error state
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
static const String _title = 'Flutter Code Sample';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4)),
|
||||
title: _title,
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text(_title)),
|
||||
body: const Center(
|
||||
child: MyStatefulWidget(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyStatefulWidget extends StatefulWidget {
|
||||
const MyStatefulWidget({super.key});
|
||||
|
||||
@override
|
||||
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
|
||||
}
|
||||
|
||||
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
|
||||
bool? isChecked = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
tristate: true,
|
||||
value: isChecked,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isChecked = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Checkbox(
|
||||
isError: true,
|
||||
tristate: true,
|
||||
value: isChecked,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isChecked = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Checkbox(
|
||||
isError: true,
|
||||
tristate: true,
|
||||
value: isChecked,
|
||||
onChanged: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -87,6 +87,7 @@ class Checkbox extends StatefulWidget {
|
|||
this.autofocus = false,
|
||||
this.shape,
|
||||
this.side,
|
||||
this.isError = false,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
assert(autofocus != null);
|
||||
|
@ -332,6 +333,14 @@ class Checkbox extends StatefulWidget {
|
|||
/// will be width 2.
|
||||
final BorderSide? side;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Must not be null. Defaults to false.
|
||||
final bool isError;
|
||||
|
||||
/// The width of a checkbox widget.
|
||||
static const double width = 18.0;
|
||||
|
||||
|
@ -427,8 +436,9 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
|
|||
|
||||
// Colors need to be resolved in selected and non selected states separately
|
||||
// so that they can be lerped between.
|
||||
final Set<MaterialState> activeStates = states..add(MaterialState.selected);
|
||||
final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected);
|
||||
final Set<MaterialState> errorState = states..add(MaterialState.error);
|
||||
final Set<MaterialState> activeStates = widget.isError ? (errorState..add(MaterialState.selected)) : states..add(MaterialState.selected);
|
||||
final Set<MaterialState> inactiveStates = widget.isError ? (errorState..remove(MaterialState.selected)) : states..remove(MaterialState.selected);
|
||||
final Color? activeColor = widget.fillColor?.resolve(activeStates)
|
||||
?? _widgetFillColor.resolve(activeStates)
|
||||
?? checkboxTheme.fillColor?.resolve(activeStates);
|
||||
|
@ -440,14 +450,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
|
|||
final Color effectiveInactiveColor = inactiveColor
|
||||
?? defaults.fillColor!.resolve(inactiveStates)!;
|
||||
|
||||
final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
|
||||
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
|
||||
final Set<MaterialState> focusedStates = widget.isError ? (errorState..add(MaterialState.focused)) : states..add(MaterialState.focused);
|
||||
Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
|
||||
?? widget.focusColor
|
||||
?? checkboxTheme.overlayColor?.resolve(focusedStates)
|
||||
?? defaults.overlayColor!.resolve(focusedStates)!;
|
||||
|
||||
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
|
||||
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
|
||||
final Set<MaterialState> hoveredStates = widget.isError ? (errorState..add(MaterialState.hovered)) : states..add(MaterialState.hovered);
|
||||
Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
|
||||
?? widget.hoverColor
|
||||
?? checkboxTheme.overlayColor?.resolve(hoveredStates)
|
||||
?? defaults.overlayColor!.resolve(hoveredStates)!;
|
||||
|
@ -464,9 +474,19 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
|
|||
?? 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(states)
|
||||
?? defaults.checkColor!.resolve(states)!;
|
||||
?? checkboxTheme.checkColor?.resolve(checkStates)
|
||||
?? defaults.checkColor!.resolve(checkStates)!;
|
||||
|
||||
final double effectiveSplashRadius = widget.splashRadius
|
||||
?? checkboxTheme.splashRadius
|
||||
|
@ -758,32 +778,14 @@ class _CheckboxDefaultsM3 extends CheckboxThemeData {
|
|||
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.onSurface.withOpacity(0.38);
|
||||
}
|
||||
if (states.contains(MaterialState.error)) {
|
||||
return _colors.error;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return _colors.primary;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _colors.primary;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return _colors.primary;
|
||||
}
|
||||
return _colors.primary;
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return _colors.onSurface;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _colors.onSurface;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return _colors.onSurface;
|
||||
}
|
||||
return _colors.onSurface;
|
||||
});
|
||||
}
|
||||
|
@ -798,14 +800,8 @@ class _CheckboxDefaultsM3 extends CheckboxThemeData {
|
|||
return Colors.transparent; // No icons available when the checkbox is unselected.
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return _colors.onPrimary;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _colors.onPrimary;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return _colors.onPrimary;
|
||||
if (states.contains(MaterialState.error)) {
|
||||
return _colors.onError;
|
||||
}
|
||||
return _colors.onPrimary;
|
||||
}
|
||||
|
@ -816,6 +812,17 @@ class _CheckboxDefaultsM3 extends CheckboxThemeData {
|
|||
@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);
|
||||
|
|
|
@ -1261,6 +1261,7 @@ class ThemeData with Diagnosticable {
|
|||
/// - [ActionChip] (used for Assist and Suggestion chips),
|
||||
/// - [FilterChip], [ChoiceChip] (used for single selection filter chips),
|
||||
/// - [InputChip]
|
||||
/// * Checkbox: [Checkbox]
|
||||
/// * Dialogs: [Dialog], [AlertDialog]
|
||||
/// * Lists: [ListTile]
|
||||
/// * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar])
|
||||
|
|
|
@ -1088,6 +1088,7 @@ void main() {
|
|||
reason: 'Default active pressed Checkbox should have overlay color from default fillColor',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container()); // reset test
|
||||
await tester.pumpWidget(buildCheckbox(focused: true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
@ -1220,6 +1221,7 @@ void main() {
|
|||
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container()); // reset test
|
||||
await tester.pumpWidget(buildCheckbox(focused: true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
@ -1550,6 +1552,77 @@ void main() {
|
|||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expect(find.text(tapTooltip), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Checkbox has default error color when isError is set to true - M3', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
||||
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
||||
bool? value = true;
|
||||
Widget buildApp({bool autoFocus = true}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
||||
return Checkbox(
|
||||
isError: true,
|
||||
value: value,
|
||||
onChanged: (bool? newValue) {
|
||||
setState(() {
|
||||
value = newValue;
|
||||
});
|
||||
},
|
||||
autofocus: autoFocus,
|
||||
focusNode: focusNode,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Focused
|
||||
await tester.pumpWidget(buildApp());
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(Checkbox))),
|
||||
paints..circle(color: theme.colorScheme.error.withOpacity(0.12))..path(color: theme.colorScheme.error)..path(color: theme.colorScheme.onError)
|
||||
);
|
||||
|
||||
// Default color
|
||||
await tester.pumpWidget(Container());
|
||||
await tester.pumpWidget(buildApp(autoFocus: false));
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isFalse);
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(Checkbox))),
|
||||
paints..path(color: theme.colorScheme.error)..path(color: theme.colorScheme.onError)
|
||||
);
|
||||
|
||||
// Start hovering
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(Checkbox))),
|
||||
paints
|
||||
..circle(color: theme.colorScheme.error.withOpacity(0.08))
|
||||
..path(color: theme.colorScheme.error)
|
||||
);
|
||||
|
||||
// Start pressing
|
||||
final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
|
||||
await tester.pump();
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(Checkbox))),
|
||||
paints
|
||||
..circle(color: theme.colorScheme.error.withOpacity(0.12))
|
||||
..path(color: theme.colorScheme.error)
|
||||
);
|
||||
await gestureLongPress.up();
|
||||
await tester.pump();
|
||||
});
|
||||
}
|
||||
|
||||
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
|
||||
|
|
Loading…
Reference in a new issue