Allow specifying and a11y label for Icon widget (#12475)
* Allow specifying and a11y label for Icon widget
diff --git a/packages/flutter/lib/src/widgets/icon.dart b/packages/flutter/lib/src/widgets/icon.dart
index f2cef83..c400239 100644
--- a/packages/flutter/lib/src/widgets/icon.dart
+++ b/packages/flutter/lib/src/widgets/icon.dart
@@ -35,7 +35,8 @@
const Icon(this.icon, {
Key key,
this.size,
- this.color
+ this.color,
+ this.semanticLabel,
}) : super(key: key);
/// The icon to display. The available icons are described in [Icons].
@@ -83,6 +84,14 @@
/// ```
final Color color;
+ /// Semantic label for the icon.
+ ///
+ /// This would be read out in accessibility modes (e.g TalkBack/VoiceOver).
+ /// This label does not show in the UI.
+ ///
+ /// See [Semantics.label];
+ final String semanticLabel;
+
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
@@ -93,27 +102,29 @@
final double iconSize = size ?? iconTheme.size;
if (icon == null)
- return new SizedBox(width: iconSize, height: iconSize);
+ return _wrapWithSemantics(new SizedBox(width: iconSize, height: iconSize));
final double iconOpacity = iconTheme.opacity;
Color iconColor = color ?? iconTheme.color;
if (iconOpacity != 1.0)
iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
- return new ExcludeSemantics(
- child: new SizedBox(
- width: iconSize,
- height: iconSize,
- child: new Center(
- child: new RichText(
- textDirection: textDirection, // Since we already fetched it for the assert...
- text: new TextSpan(
- text: new String.fromCharCode(icon.codePoint),
- style: new TextStyle(
- inherit: false,
- color: iconColor,
- fontSize: iconSize,
- fontFamily: icon.fontFamily,
+ return _wrapWithSemantics(
+ new ExcludeSemantics(
+ child: new SizedBox(
+ width: iconSize,
+ height: iconSize,
+ child: new Center(
+ child: new RichText(
+ textDirection: textDirection, // Since we already fetched it for the assert...
+ text: new TextSpan(
+ text: new String.fromCharCode(icon.codePoint),
+ style: new TextStyle(
+ inherit: false,
+ color: iconColor,
+ fontSize: iconSize,
+ fontFamily: icon.fontFamily,
+ ),
),
),
),
@@ -122,6 +133,17 @@
);
}
+ /// Wraps the widget with a Semantics widget if [semanticLabel] is set.
+ Widget _wrapWithSemantics(Widget widget) {
+ if (semanticLabel == null)
+ return widget;
+
+ return new Semantics(
+ child: widget,
+ label: semanticLabel,
+ );
+ }
+
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
diff --git a/packages/flutter/test/widgets/icon_test.dart b/packages/flutter/test/widgets/icon_test.dart
index 3ad61de..3f95d75 100644
--- a/packages/flutter/test/widgets/icon_test.dart
+++ b/packages/flutter/test/widgets/icon_test.dart
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
+import 'semantics_tester.dart';
+
void main() {
testWidgets('Can set opacity for an Icon', (WidgetTester tester) async {
await tester.pumpWidget(
@@ -122,4 +125,40 @@
final RichText richText = tester.firstWidget(find.byType(RichText));
expect(richText.text.style.fontFamily, equals('Roboto'));
});
+
+ testWidgets('Icon with semantic label', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+
+ await tester.pumpWidget(
+ const Directionality(
+ textDirection: TextDirection.ltr,
+ child: const Center(
+ child: const Icon(
+ Icons.title,
+ semanticLabel: 'a label',
+ ),
+ ),
+ ),
+ );
+
+ expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label')));
+ });
+
+ testWidgets('Null icon with semantic label', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+
+ await tester.pumpWidget(
+ const Directionality(
+ textDirection: TextDirection.ltr,
+ child: const Center(
+ child: const Icon(
+ null,
+ semanticLabel: 'a label',
+ ),
+ ),
+ ),
+ );
+
+ expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label')));
+ });
}