Support SemanticsFlag for Header (#15255)
diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart
index 694b89b..cfc8372 100644
--- a/examples/flutter_gallery/lib/gallery/home.dart
+++ b/examples/flutter_gallery/lib/gallery/home.dart
@@ -152,7 +152,10 @@
child: new SafeArea(
top: false,
bottom: false,
- child: new Text(galleryItem.category, style: headerStyle),
+ child: new Semantics(
+ header: true,
+ child: new Text(galleryItem.category, style: headerStyle),
+ ),
),
),
)
diff --git a/packages/flutter/lib/src/rendering/custom_paint.dart b/packages/flutter/lib/src/rendering/custom_paint.dart
index 79beea4..a6dc53b 100644
--- a/packages/flutter/lib/src/rendering/custom_paint.dart
+++ b/packages/flutter/lib/src/rendering/custom_paint.dart
@@ -817,6 +817,21 @@
if (properties.button != null) {
config.isButton = properties.button;
}
+ if (properties.textField != null) {
+ config.isTextField = properties.textField;
+ }
+ if (properties.focused != null) {
+ config.isFocused = properties.focused;
+ }
+ if (properties.enabled != null) {
+ config.isEnabled = properties.enabled;
+ }
+ if (properties.inMutuallyExclusiveGroup != null) {
+ config.isInMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup;
+ }
+ if (properties.header != null) {
+ config.isHeader = properties.header;
+ }
if (properties.label != null) {
config.label = properties.label;
}
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index a4d9946..fc685b6 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -3015,6 +3015,10 @@
bool checked,
bool selected,
bool button,
+ bool header,
+ bool textField,
+ bool focused,
+ bool inMutuallyExclusiveGroup,
String label,
String value,
String increasedValue,
@@ -3045,6 +3049,10 @@
_checked = checked,
_selected = selected,
_button = button,
+ _header = header,
+ _textField = textField,
+ _focused = focused,
+ _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
_label = label,
_value = value,
_increasedValue = increasedValue,
@@ -3152,6 +3160,47 @@
markNeedsSemanticsUpdate();
}
+ /// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value.
+ bool get header => _header;
+ bool _header;
+ set header(bool value) {
+ if (header == value)
+ return;
+ _header = value;
+ markNeedsSemanticsUpdate();
+ }
+
+ /// If non-null, sets the [SemanticsNode.isTextField] semantic to the given value.
+ bool get textField => _textField;
+ bool _textField;
+ set textField(bool value) {
+ if (textField == value)
+ return;
+ _textField = value;
+ markNeedsSemanticsUpdate();
+ }
+
+ /// If non-null, sets the [SemanticsNode.isFocused] semantic to the given value.
+ bool get focused => _focused;
+ bool _focused;
+ set focused(bool value) {
+ if (focused == value)
+ return;
+ _focused = value;
+ markNeedsSemanticsUpdate();
+ }
+
+ /// If non-null, sets the [SemanticsNode.isInMutuallyExclusiveGroup] semantic
+ /// to the given value.
+ bool get inMutuallyExclusiveGroup => _inMutuallyExclusiveGroup;
+ bool _inMutuallyExclusiveGroup;
+ set inMutuallyExclusiveGroup(bool value) {
+ if (inMutuallyExclusiveGroup == value)
+ return;
+ _inMutuallyExclusiveGroup = value;
+ markNeedsSemanticsUpdate();
+ }
+
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
///
/// The reading direction is given by [textDirection].
@@ -3581,6 +3630,14 @@
config.isSelected = selected;
if (button != null)
config.isButton = button;
+ if (header != null)
+ config.isHeader = header;
+ if (textField != null)
+ config.isTextField = textField;
+ if (focused != null)
+ config.isFocused = focused;
+ if (inMutuallyExclusiveGroup != null)
+ config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
if (label != null)
config.label = label;
if (value != null)
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index 2318fdd..812c769 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -315,6 +315,10 @@
this.checked,
this.selected,
this.button,
+ this.header,
+ this.textField,
+ this.focused,
+ this.inMutuallyExclusiveGroup,
this.label,
this.value,
this.increasedValue,
@@ -366,6 +370,35 @@
/// is focused.
final bool button;
+ /// If non-null, indicates that this subtree represents a header.
+ ///
+ /// A header divides into sections. For example, an address book application
+ /// might define headers A, B, C, etc. to divide the list of alphabetically
+ /// sorted contacts into sections.
+ final bool header;
+
+ /// If non-null, indicates that this subtree represents a text field.
+ ///
+ /// TalkBack/VoiceOver provide special affordances to enter text into a
+ /// text field.
+ final bool textField;
+
+ /// If non-null, whether the node currently holds input focus.
+ ///
+ /// At most one node in the tree should hold input focus at any point in time.
+ ///
+ /// Input focus (indicates that the node will receive keyboard events) is not
+ /// to be confused with accessibility focus. Accessibility focus is the
+ /// green/black rectangular that TalkBack/VoiceOver on the screen and is
+ /// separate from input focus.
+ final bool focused;
+
+ /// If non-null, whether a semantic node is in a mutually exclusive group.
+ ///
+ /// For example, a radio button is in a mutually exclusive group because only
+ /// one radio button in that group can be marked as [checked].
+ final bool inMutuallyExclusiveGroup;
+
/// Provides a textual description of the widget.
///
/// If a label is provided, there must either by an ambient [Directionality]
@@ -2365,6 +2398,12 @@
_setFlag(SemanticsFlag.isButton, value);
}
+ /// Whether the owning [RenderObject] is a header (true) or not (false).
+ bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
+ set isHeader(bool value) {
+ _setFlag(SemanticsFlag.isHeader, value);
+ }
+
/// Whether the owning [RenderObject] is a text field.
bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
set isTextField(bool value) {
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 7f1b565..a7cad3a 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -4847,6 +4847,10 @@
bool checked,
bool selected,
bool button,
+ bool header,
+ bool textField,
+ bool focused,
+ bool inMutuallyExclusiveGroup,
String label,
String value,
String increasedValue,
@@ -4881,6 +4885,10 @@
checked: checked,
selected: selected,
button: button,
+ header: header,
+ textField: textField,
+ focused: focused,
+ inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
label: label,
value: value,
increasedValue: increasedValue,
@@ -4960,6 +4968,10 @@
checked: properties.checked,
selected: properties.selected,
button: properties.button,
+ header: properties.header,
+ textField: properties.textField,
+ focused: properties.focused,
+ inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
label: properties.label,
value: properties.value,
increasedValue: properties.increasedValue,
diff --git a/packages/flutter/test/widgets/custom_painter_test.dart b/packages/flutter/test/widgets/custom_painter_test.dart
index 8484293..04d6fa3 100644
--- a/packages/flutter/test/widgets/custom_painter_test.dart
+++ b/packages/flutter/test/widgets/custom_painter_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
@@ -397,6 +398,52 @@
semantics.dispose();
});
+ testWidgets('Supports all flags', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+
+ await tester.pumpWidget(new CustomPaint(
+ painter: new _PainterWithSemantics(
+ semantics: new CustomPainterSemantics(
+ key: const ValueKey<int>(1),
+ rect: new Rect.fromLTRB(1.0, 2.0, 3.0, 4.0),
+ properties: const SemanticsProperties(
+ enabled: true,
+ checked: true,
+ selected: true,
+ button: true,
+ textField: true,
+ focused: true,
+ inMutuallyExclusiveGroup: true,
+ header: true,
+ ),
+ ),
+ ),
+ ));
+
+ const int expectedId = 2;
+ final TestSemantics expectedSemantics = new TestSemantics.root(
+ children: <TestSemantics>[
+ new TestSemantics.rootChild(
+ id: 1,
+ previousNodeId: -1,
+ nextNodeId: expectedId,
+ children: <TestSemantics>[
+ new TestSemantics.rootChild(
+ id: expectedId,
+ rect: TestSemantics.fullScreen,
+ flags: SemanticsFlag.values.values.toList(),
+ previousNodeId: 1,
+ nextNodeId: -1,
+ ),
+ ]
+ ),
+ ],
+ );
+ expect(semantics, hasSemantics(expectedSemantics, ignoreRect: true, ignoreTransform: true));
+
+ semantics.dispose();
+ });
+
group('diffing', () {
testWidgets('complains about duplicate keys', (WidgetTester tester) async {
final SemanticsTester semanticsTester = new SemanticsTester(tester);
diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart
index ae925ced..d6ad7cb 100644
--- a/packages/flutter/test/widgets/semantics_test.dart
+++ b/packages/flutter/test/widgets/semantics_test.dart
@@ -449,6 +449,39 @@
semantics.dispose();
});
+ testWidgets('Semantics widget supports all flags', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+
+ await tester.pumpWidget(
+ new Semantics(
+ container: true,
+ // flags
+ enabled: true,
+ checked: true,
+ selected: true,
+ button: true,
+ textField: true,
+ focused: true,
+ inMutuallyExclusiveGroup: true,
+ header: true,
+ )
+ );
+
+ final TestSemantics expectedSemantics = new TestSemantics.root(
+ children: <TestSemantics>[
+ new TestSemantics.rootChild(
+ rect: TestSemantics.fullScreen,
+ flags: SemanticsFlag.values.values.toList(),
+ previousNodeId: -1,
+ nextNodeId: -1,
+ ),
+ ],
+ );
+ expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
+
+ semantics.dispose();
+ });
+
testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;