Unverified Commit 4d6ef78c authored by hangyu's avatar hangyu Committed by GitHub

Add optional labelText and semanticLabel to Checkbox (#124555)

Re-open from https://github.com/flutter/flutter/pull/116551

This PR added optional labelText (which will be  rendered side by side with Checkbox in the future, and thus is also announced by default by screen readers), and semanticLabel(which will be announced by screen reader if provided, overrides labelText, in order to do that we might want to wrap the Text widget inside ExcludeSemantics in the future).

Issues fixed:
[b/239564167]
parent 094bd858
...@@ -90,6 +90,7 @@ class Checkbox extends StatefulWidget { ...@@ -90,6 +90,7 @@ class Checkbox extends StatefulWidget {
this.shape, this.shape,
this.side, this.side,
this.isError = false, this.isError = false,
this.semanticLabel,
}) : _checkboxType = _CheckboxType.material, }) : _checkboxType = _CheckboxType.material,
assert(tristate || value != null); assert(tristate || value != null);
...@@ -129,6 +130,7 @@ class Checkbox extends StatefulWidget { ...@@ -129,6 +130,7 @@ class Checkbox extends StatefulWidget {
this.shape, this.shape,
this.side, this.side,
this.isError = false, this.isError = false,
this.semanticLabel,
}) : _checkboxType = _CheckboxType.adaptive, }) : _checkboxType = _CheckboxType.adaptive,
assert(tristate || value != null); assert(tristate || value != null);
...@@ -386,6 +388,15 @@ class Checkbox extends StatefulWidget { ...@@ -386,6 +388,15 @@ class Checkbox extends StatefulWidget {
/// Must not be null. Defaults to false. /// Must not be null. Defaults to false.
final bool isError; 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. /// The width of a checkbox widget.
static const double width = 18.0; static const double width = 18.0;
...@@ -568,6 +579,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg ...@@ -568,6 +579,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
?? defaults.splashRadius!; ?? defaults.splashRadius!;
return Semantics( return Semantics(
label: widget.semanticLabel,
checked: widget.value ?? false, checked: widget.value ?? false,
mixed: widget.tristate ? widget.value == null : null, mixed: widget.tristate ? widget.value == null : null,
child: buildToggleable( child: buildToggleable(
......
...@@ -194,6 +194,7 @@ class CheckboxListTile extends StatelessWidget { ...@@ -194,6 +194,7 @@ class CheckboxListTile extends StatelessWidget {
this.selectedTileColor, this.selectedTileColor,
this.onFocusChange, this.onFocusChange,
this.enableFeedback, this.enableFeedback,
this.checkboxSemanticLabel,
}) : _checkboxType = _CheckboxType.material, }) : _checkboxType = _CheckboxType.material,
assert(tristate || value != null), assert(tristate || value != null),
assert(!isThreeLine || subtitle != null); assert(!isThreeLine || subtitle != null);
...@@ -237,6 +238,7 @@ class CheckboxListTile extends StatelessWidget { ...@@ -237,6 +238,7 @@ class CheckboxListTile extends StatelessWidget {
this.selectedTileColor, this.selectedTileColor,
this.onFocusChange, this.onFocusChange,
this.enableFeedback, this.enableFeedback,
this.checkboxSemanticLabel,
}) : _checkboxType = _CheckboxType.adaptive, }) : _checkboxType = _CheckboxType.adaptive,
assert(tristate || value != null), assert(tristate || value != null),
assert(!isThreeLine || subtitle != null); assert(!isThreeLine || subtitle != null);
...@@ -452,6 +454,9 @@ class CheckboxListTile extends StatelessWidget { ...@@ -452,6 +454,9 @@ class CheckboxListTile extends StatelessWidget {
/// inoperative. /// inoperative.
final bool? enabled; final bool? enabled;
/// {@macro flutter.material.checkbox.semanticLabel}
final String? checkboxSemanticLabel;
final _CheckboxType _checkboxType; final _CheckboxType _checkboxType;
void _handleValueChange() { void _handleValueChange() {
...@@ -488,6 +493,7 @@ class CheckboxListTile extends StatelessWidget { ...@@ -488,6 +493,7 @@ class CheckboxListTile extends StatelessWidget {
shape: checkboxShape, shape: checkboxShape,
side: side, side: side,
isError: isError, isError: isError,
semanticLabel: checkboxSemanticLabel,
); );
case _CheckboxType.adaptive: case _CheckboxType.adaptive:
control = Checkbox.adaptive( control = Checkbox.adaptive(
...@@ -506,6 +512,7 @@ class CheckboxListTile extends StatelessWidget { ...@@ -506,6 +512,7 @@ class CheckboxListTile extends StatelessWidget {
shape: checkboxShape, shape: checkboxShape,
side: side, side: side,
isError: isError, isError: isError,
semanticLabel: checkboxSemanticLabel,
); );
} }
......
...@@ -993,6 +993,31 @@ void main() { ...@@ -993,6 +993,31 @@ void main() {
expect(feedback.hapticCount, 0); expect(feedback.hapticCount, 0);
}); });
}); });
testWidgets('CheckboxListTile has proper semantics', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(wrap(
child: CheckboxListTile(
value: true,
onChanged: (bool? value) { log.add(value); },
title: const Text('Hello'),
checkboxSemanticLabel: 'there',
),
));
expect(tester.getSemantics(find.byType(CheckboxListTile)), matchesSemantics(
hasCheckedState: true,
isChecked: true,
hasEnabledState: true,
isEnabled: true,
hasTapAction: true,
isFocusable: true,
label: 'Hello\nthere',
));
handle.dispose();
});
} }
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor { class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
......
...@@ -191,6 +191,31 @@ void main() { ...@@ -191,6 +191,31 @@ void main() {
hasEnabledState: true, hasEnabledState: true,
)); ));
// Check if semanticLabel is there.
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Theme(
data: theme,
child: Material(
child: Checkbox(
semanticLabel: 'checkbox',
value: true,
onChanged: (bool? b) { },
),
),
),
));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
label: 'checkbox',
textDirection: TextDirection.ltr,
hasCheckedState: true,
hasEnabledState: true,
isChecked: true,
isEnabled: true,
hasTapAction: true,
isFocusable: true,
));
handle.dispose(); handle.dispose();
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment