SliverSemantics (#167300)

Fixes #166785 

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Renzo Olivares <roliv@google.com>
This commit is contained in:
Renzo Olivares 2025-07-08 09:53:24 -07:00 committed by GitHub
parent 4f72a04a5e
commit 220477fc2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 4029 additions and 787 deletions

View File

@ -154,7 +154,8 @@ class _SliverEnsureSemanticsExampleState extends State<SliverEnsureSemanticsExam
),
),
SliverEnsureSemantics(
sliver: SliverSemanticsList(
sliver: SliverSemantics(
role: SemanticsRole.list,
sliver: SliverFixedExtentList(
itemExtent: 44.0,
delegate: SliverChildBuilderDelegate(
@ -189,19 +190,3 @@ class _SliverEnsureSemanticsExampleState extends State<SliverEnsureSemanticsExam
);
}
}
// A sliver that assigns the role of SemanticsRole.list to its sliver child.
class SliverSemanticsList extends SingleChildRenderObjectWidget {
const SliverSemanticsList({super.key, required Widget sliver}) : super(child: sliver);
@override
RenderSliverSemanticsList createRenderObject(BuildContext context) => RenderSliverSemanticsList();
}
class RenderSliverSemanticsList extends RenderProxySliver {
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.role = SemanticsRole.list;
}
}

View File

@ -4595,6 +4595,493 @@ mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {
}
}
/// A mixin for [RenderObject]s that want to annotate the [SemanticsNode]
/// for their subtree.
mixin SemanticsAnnotationsMixin on RenderObject {
/// Initializes the semantics annotations for this mixin.
// Parameters added to this method should be marked as required to ensure
// callers of the method provide a value.
void initSemanticsAnnotations({
required SemanticsProperties properties,
required bool container,
required bool explicitChildNodes,
required bool excludeSemantics,
required bool blockUserActions,
required Locale? localeForSubtree,
required TextDirection? textDirection,
}) {
_properties = properties;
_container = container;
_explicitChildNodes = explicitChildNodes;
_excludeSemantics = excludeSemantics;
_blockUserActions = blockUserActions;
_localeForSubtree = localeForSubtree;
_textDirection = textDirection;
_updateAttributedFields(_properties);
}
/// All of the [SemanticsProperties] for this [SemanticsAnnotationsMixin].
SemanticsProperties get properties => _properties;
late SemanticsProperties _properties;
set properties(SemanticsProperties value) {
if (_properties == value) {
return;
}
_properties = value;
_updateAttributedFields(_properties);
markNeedsSemanticsUpdate();
}
/// If 'container' is true, this [RenderObject] will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors.
///
/// Whether descendants of this [RenderObject] can add their semantic information
/// to the [SemanticsNode] introduced by this configuration is controlled by
/// [explicitChildNodes].
bool get container => _container;
late bool _container;
set container(bool value) {
if (container == value) {
return;
}
_container = value;
markNeedsSemanticsUpdate();
}
/// Whether descendants of this [RenderObject] are allowed to add semantic
/// information to the [SemanticsNode] annotated by this widget.
///
/// When set to false descendants are allowed to annotate [SemanticsNode]s of
/// their parent with the semantic information they want to contribute to the
/// semantic tree.
/// When set to true the only way for descendants to contribute semantic
/// information to the semantic tree is to introduce new explicit
/// [SemanticsNode]s to the tree.
///
/// This setting is often used in combination with
/// [SemanticsConfiguration.isSemanticBoundary] to create semantic boundaries
/// that are either writable or not for children.
bool get explicitChildNodes => _explicitChildNodes;
late bool _explicitChildNodes;
set explicitChildNodes(bool value) {
if (_explicitChildNodes == value) {
return;
}
_explicitChildNodes = value;
markNeedsSemanticsUpdate();
}
/// Whether descendants of this [RenderObject] should have their semantic
/// information ignored.
///
/// When this flag is set to true, all child semantics nodes are ignored.
/// This can be used as a convenience for cases where a child is wrapped in
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
bool get excludeSemantics => _excludeSemantics;
late bool _excludeSemantics;
set excludeSemantics(bool value) {
if (_excludeSemantics == value) {
return;
}
_excludeSemantics = value;
markNeedsSemanticsUpdate();
}
/// Whether to block user interactions for the semantics subtree.
///
/// Setting this true prevents user from activating pointer related
/// [SemanticsAction]s, such as [SemanticsAction.tap] or
/// [SemanticsAction.longPress].
bool get blockUserActions => _blockUserActions;
late bool _blockUserActions;
set blockUserActions(bool value) {
if (_blockUserActions == value) {
return;
}
_blockUserActions = value;
markNeedsSemanticsUpdate();
}
/// The [Locale] for the semantics subtree.
///
/// Setting this to null will inherit locale from ancestor semantics node.
Locale? get localeForSubtree => _localeForSubtree;
Locale? _localeForSubtree;
set localeForSubtree(Locale? value) {
if (_localeForSubtree == value) {
return;
}
_localeForSubtree = value;
markNeedsSemanticsUpdate();
}
void _updateAttributedFields(SemanticsProperties value) {
_attributedLabel = _effectiveAttributedLabel(value);
_attributedValue = _effectiveAttributedValue(value);
_attributedIncreasedValue = _effectiveAttributedIncreasedValue(value);
_attributedDecreasedValue = _effectiveAttributedDecreasedValue(value);
_attributedHint = _effectiveAttributedHint(value);
}
AttributedString? _effectiveAttributedLabel(SemanticsProperties value) {
return value.attributedLabel ?? (value.label == null ? null : AttributedString(value.label!));
}
AttributedString? _effectiveAttributedValue(SemanticsProperties value) {
return value.attributedValue ?? (value.value == null ? null : AttributedString(value.value!));
}
AttributedString? _effectiveAttributedIncreasedValue(SemanticsProperties value) {
return value.attributedIncreasedValue ??
(value.increasedValue == null ? null : AttributedString(value.increasedValue!));
}
AttributedString? _effectiveAttributedDecreasedValue(SemanticsProperties value) {
return properties.attributedDecreasedValue ??
(value.decreasedValue == null ? null : AttributedString(value.decreasedValue!));
}
AttributedString? _effectiveAttributedHint(SemanticsProperties value) {
return value.attributedHint ?? (value.hint == null ? null : AttributedString(value.hint!));
}
AttributedString? _attributedLabel;
AttributedString? _attributedValue;
AttributedString? _attributedIncreasedValue;
AttributedString? _attributedDecreasedValue;
AttributedString? _attributedHint;
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given
/// value.
///
/// This must not be null if [SemanticsProperties.attributedLabel],
/// [SemanticsProperties.attributedHint],
/// [SemanticsProperties.attributedValue],
/// [SemanticsProperties.attributedIncreasedValue], or
/// [SemanticsProperties.attributedDecreasedValue] are not null.
TextDirection? get textDirection => _textDirection;
TextDirection? _textDirection;
set textDirection(TextDirection? value) {
if (textDirection == value) {
return;
}
_textDirection = value;
markNeedsSemanticsUpdate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (excludeSemantics) {
return;
}
super.visitChildrenForSemantics(visitor);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = container;
config.explicitChildNodes = explicitChildNodes;
config.isBlockingUserActions = blockUserActions;
config.localeForSubtree = localeForSubtree;
assert(
((_properties.scopesRoute ?? false) && explicitChildNodes) ||
!(_properties.scopesRoute ?? false),
'explicitChildNodes must be set to true if scopes route is true',
);
assert(
!((_properties.toggled ?? false) && (_properties.checked ?? false)),
'A semantics node cannot be toggled and checked at the same time',
);
if (_properties.enabled != null) {
config.isEnabled = _properties.enabled;
}
if (_properties.checked != null) {
config.isChecked = _properties.checked;
}
if (_properties.mixed != null) {
config.isCheckStateMixed = _properties.mixed;
}
if (_properties.toggled != null) {
config.isToggled = _properties.toggled;
}
if (_properties.selected != null) {
config.isSelected = _properties.selected!;
}
if (_properties.button != null) {
config.isButton = _properties.button!;
}
if (_properties.expanded != null) {
config.isExpanded = _properties.expanded;
}
if (_properties.link != null) {
config.isLink = _properties.link!;
}
if (_properties.linkUrl != null) {
config.linkUrl = _properties.linkUrl;
}
if (_properties.slider != null) {
config.isSlider = _properties.slider!;
}
if (_properties.keyboardKey != null) {
config.isKeyboardKey = _properties.keyboardKey!;
}
if (_properties.header != null) {
config.isHeader = _properties.header!;
}
if (_properties.headingLevel != null) {
config.headingLevel = _properties.headingLevel!;
}
if (_properties.textField != null) {
config.isTextField = _properties.textField!;
}
if (_properties.readOnly != null) {
config.isReadOnly = _properties.readOnly!;
}
if (_properties.focusable != null) {
config.isFocusable = _properties.focusable!;
}
if (_properties.focused != null) {
config.isFocused = _properties.focused!;
}
if (_properties.inMutuallyExclusiveGroup != null) {
config.isInMutuallyExclusiveGroup = _properties.inMutuallyExclusiveGroup!;
}
if (_properties.obscured != null) {
config.isObscured = _properties.obscured!;
}
if (_properties.multiline != null) {
config.isMultiline = _properties.multiline!;
}
if (_properties.hidden != null) {
config.isHidden = _properties.hidden!;
}
if (_properties.image != null) {
config.isImage = _properties.image!;
}
if (_properties.isRequired != null) {
config.isRequired = _properties.isRequired;
}
if (_properties.identifier != null) {
config.identifier = _properties.identifier!;
}
if (_attributedLabel != null) {
config.attributedLabel = _attributedLabel!;
}
if (_attributedValue != null) {
config.attributedValue = _attributedValue!;
}
if (_attributedIncreasedValue != null) {
config.attributedIncreasedValue = _attributedIncreasedValue!;
}
if (_attributedDecreasedValue != null) {
config.attributedDecreasedValue = _attributedDecreasedValue!;
}
if (_attributedHint != null) {
config.attributedHint = _attributedHint!;
}
if (_properties.tooltip != null) {
config.tooltip = _properties.tooltip!;
}
if (_properties.hintOverrides != null && _properties.hintOverrides!.isNotEmpty) {
config.hintOverrides = _properties.hintOverrides;
}
if (_properties.scopesRoute != null) {
config.scopesRoute = _properties.scopesRoute!;
}
if (_properties.namesRoute != null) {
config.namesRoute = _properties.namesRoute!;
}
if (_properties.liveRegion != null) {
config.liveRegion = _properties.liveRegion!;
}
if (_properties.maxValueLength != null) {
config.maxValueLength = _properties.maxValueLength;
}
if (_properties.currentValueLength != null) {
config.currentValueLength = _properties.currentValueLength;
}
if (textDirection != null) {
config.textDirection = textDirection;
}
if (_properties.sortKey != null) {
config.sortKey = _properties.sortKey;
}
if (_properties.tagForChildren != null) {
config.addTagForChildren(_properties.tagForChildren!);
}
if (properties.role != null) {
config.role = _properties.role!;
}
if (_properties.controlsNodes != null) {
config.controlsNodes = _properties.controlsNodes;
}
if (config.validationResult != _properties.validationResult) {
config.validationResult = _properties.validationResult;
}
if (_properties.inputType != null) {
config.inputType = _properties.inputType!;
}
// Registering _perform* as action handlers instead of the user provided
// ones to ensure that changing a user provided handler from a non-null to
// another non-null value doesn't require a semantics update.
if (_properties.onTap != null) {
config.onTap = _performTap;
}
if (_properties.onLongPress != null) {
config.onLongPress = _performLongPress;
}
if (_properties.onDismiss != null) {
config.onDismiss = _performDismiss;
}
if (_properties.onScrollLeft != null) {
config.onScrollLeft = _performScrollLeft;
}
if (_properties.onScrollRight != null) {
config.onScrollRight = _performScrollRight;
}
if (_properties.onScrollUp != null) {
config.onScrollUp = _performScrollUp;
}
if (_properties.onScrollDown != null) {
config.onScrollDown = _performScrollDown;
}
if (_properties.onIncrease != null) {
config.onIncrease = _performIncrease;
}
if (_properties.onDecrease != null) {
config.onDecrease = _performDecrease;
}
if (_properties.onCopy != null) {
config.onCopy = _performCopy;
}
if (_properties.onCut != null) {
config.onCut = _performCut;
}
if (_properties.onPaste != null) {
config.onPaste = _performPaste;
}
if (_properties.onMoveCursorForwardByCharacter != null) {
config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
}
if (_properties.onMoveCursorBackwardByCharacter != null) {
config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
}
if (_properties.onMoveCursorForwardByWord != null) {
config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
}
if (_properties.onMoveCursorBackwardByWord != null) {
config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
}
if (_properties.onSetSelection != null) {
config.onSetSelection = _performSetSelection;
}
if (_properties.onSetText != null) {
config.onSetText = _performSetText;
}
if (_properties.onDidGainAccessibilityFocus != null) {
config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
}
if (_properties.onDidLoseAccessibilityFocus != null) {
config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
}
if (_properties.onFocus != null) {
config.onFocus = _performFocus;
}
if (_properties.customSemanticsActions != null) {
config.customSemanticsActions = _properties.customSemanticsActions!;
}
}
void _performTap() {
_properties.onTap?.call();
}
void _performLongPress() {
_properties.onLongPress?.call();
}
void _performDismiss() {
_properties.onDismiss?.call();
}
void _performScrollLeft() {
_properties.onScrollLeft?.call();
}
void _performScrollRight() {
_properties.onScrollRight?.call();
}
void _performScrollUp() {
_properties.onScrollUp?.call();
}
void _performScrollDown() {
_properties.onScrollDown?.call();
}
void _performIncrease() {
_properties.onIncrease?.call();
}
void _performDecrease() {
_properties.onDecrease?.call();
}
void _performCopy() {
_properties.onCopy?.call();
}
void _performCut() {
_properties.onCut?.call();
}
void _performPaste() {
_properties.onPaste?.call();
}
void _performMoveCursorForwardByCharacter(bool extendSelection) {
_properties.onMoveCursorForwardByCharacter?.call(extendSelection);
}
void _performMoveCursorBackwardByCharacter(bool extendSelection) {
_properties.onMoveCursorBackwardByCharacter?.call(extendSelection);
}
void _performMoveCursorForwardByWord(bool extendSelection) {
_properties.onMoveCursorForwardByWord?.call(extendSelection);
}
void _performMoveCursorBackwardByWord(bool extendSelection) {
_properties.onMoveCursorBackwardByWord?.call(extendSelection);
}
void _performSetSelection(TextSelection selection) {
_properties.onSetSelection?.call(selection);
}
void _performSetText(String text) {
_properties.onSetText?.call(text);
}
void _performDidGainAccessibilityFocus() {
_properties.onDidGainAccessibilityFocus?.call();
}
void _performDidLoseAccessibilityFocus() {
_properties.onDidLoseAccessibilityFocus?.call();
}
void _performFocus() {
_properties.onFocus?.call();
}
}
/// Properties of _RenderObjectSemantics that are imposed from parent.
@immutable
final class _SemanticsParentData {

View File

@ -4231,7 +4231,7 @@ class RenderSemanticsGestureHandler extends RenderProxyBoxWithHitTestBehavior {
}
/// Add annotations to the [SemanticsNode] for this subtree.
class RenderSemanticsAnnotations extends RenderProxyBox {
class RenderSemanticsAnnotations extends RenderProxyBox with SemanticsAnnotationsMixin {
/// Creates a render object that attaches a semantic annotation.
///
/// If the [SemanticsProperties.attributedLabel] is not null, the [textDirection] must also not be null.
@ -4244,476 +4244,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool blockUserActions = false,
Locale? localeForSubtree,
TextDirection? textDirection,
}) : _container = container,
_explicitChildNodes = explicitChildNodes,
_excludeSemantics = excludeSemantics,
_blockUserActions = blockUserActions,
_localeForSubtree = localeForSubtree,
_textDirection = textDirection,
_properties = properties,
super(child) {
_updateAttributedFields(_properties);
}
/// All of the [SemanticsProperties] for this [RenderSemanticsAnnotations].
SemanticsProperties get properties => _properties;
SemanticsProperties _properties;
set properties(SemanticsProperties value) {
if (_properties == value) {
return;
}
_properties = value;
_updateAttributedFields(_properties);
markNeedsSemanticsUpdate();
}
/// If 'container' is true, this [RenderObject] will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors.
///
/// Whether descendants of this [RenderObject] can add their semantic information
/// to the [SemanticsNode] introduced by this configuration is controlled by
/// [explicitChildNodes].
bool get container => _container;
bool _container;
set container(bool value) {
if (container == value) {
return;
}
_container = value;
markNeedsSemanticsUpdate();
}
/// Whether descendants of this [RenderObject] are allowed to add semantic
/// information to the [SemanticsNode] annotated by this widget.
///
/// When set to false descendants are allowed to annotate [SemanticsNode]s of
/// their parent with the semantic information they want to contribute to the
/// semantic tree.
/// When set to true the only way for descendants to contribute semantic
/// information to the semantic tree is to introduce new explicit
/// [SemanticsNode]s to the tree.
///
/// This setting is often used in combination with
/// [SemanticsConfiguration.isSemanticBoundary] to create semantic boundaries
/// that are either writable or not for children.
bool get explicitChildNodes => _explicitChildNodes;
bool _explicitChildNodes;
set explicitChildNodes(bool value) {
if (_explicitChildNodes == value) {
return;
}
_explicitChildNodes = value;
markNeedsSemanticsUpdate();
}
/// Whether descendants of this [RenderObject] should have their semantic
/// information ignored.
///
/// When this flag is set to true, all child semantics nodes are ignored.
/// This can be used as a convenience for cases where a child is wrapped in
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
bool get excludeSemantics => _excludeSemantics;
bool _excludeSemantics;
set excludeSemantics(bool value) {
if (_excludeSemantics == value) {
return;
}
_excludeSemantics = value;
markNeedsSemanticsUpdate();
}
/// Whether to block user interactions for the semantics subtree.
///
/// Setting this true prevents user from activating pointer related
/// [SemanticsAction]s, such as [SemanticsAction.tap] or
/// [SemanticsAction.longPress].
bool get blockUserActions => _blockUserActions;
bool _blockUserActions;
set blockUserActions(bool value) {
if (_blockUserActions == value) {
return;
}
_blockUserActions = value;
markNeedsSemanticsUpdate();
}
/// The [Locale] for the semantics subtree.
///
/// Setting this to null will inherit locale from ancestor semantics node
Locale? get localeForSubtree => _localeForSubtree;
Locale? _localeForSubtree;
set localeForSubtree(Locale? value) {
if (_localeForSubtree == value) {
return;
}
_localeForSubtree = value;
markNeedsSemanticsUpdate();
}
void _updateAttributedFields(SemanticsProperties value) {
_attributedLabel = _effectiveAttributedLabel(value);
_attributedValue = _effectiveAttributedValue(value);
_attributedIncreasedValue = _effectiveAttributedIncreasedValue(value);
_attributedDecreasedValue = _effectiveAttributedDecreasedValue(value);
_attributedHint = _effectiveAttributedHint(value);
}
AttributedString? _effectiveAttributedLabel(SemanticsProperties value) {
return value.attributedLabel ?? (value.label == null ? null : AttributedString(value.label!));
}
AttributedString? _effectiveAttributedValue(SemanticsProperties value) {
return value.attributedValue ?? (value.value == null ? null : AttributedString(value.value!));
}
AttributedString? _effectiveAttributedIncreasedValue(SemanticsProperties value) {
return value.attributedIncreasedValue ??
(value.increasedValue == null ? null : AttributedString(value.increasedValue!));
}
AttributedString? _effectiveAttributedDecreasedValue(SemanticsProperties value) {
return properties.attributedDecreasedValue ??
(value.decreasedValue == null ? null : AttributedString(value.decreasedValue!));
}
AttributedString? _effectiveAttributedHint(SemanticsProperties value) {
return value.attributedHint ?? (value.hint == null ? null : AttributedString(value.hint!));
}
AttributedString? _attributedLabel;
AttributedString? _attributedValue;
AttributedString? _attributedIncreasedValue;
AttributedString? _attributedDecreasedValue;
AttributedString? _attributedHint;
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given
/// value.
///
/// This must not be null if [SemanticsProperties.attributedLabel],
/// [SemanticsProperties.attributedHint],
/// [SemanticsProperties.attributedValue],
/// [SemanticsProperties.attributedIncreasedValue], or
/// [SemanticsProperties.attributedDecreasedValue] are not null.
TextDirection? get textDirection => _textDirection;
TextDirection? _textDirection;
set textDirection(TextDirection? value) {
if (textDirection == value) {
return;
}
_textDirection = value;
markNeedsSemanticsUpdate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (excludeSemantics) {
return;
}
super.visitChildrenForSemantics(visitor);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = container;
config.explicitChildNodes = explicitChildNodes;
config.isBlockingUserActions = blockUserActions;
config.localeForSubtree = localeForSubtree;
assert(
((_properties.scopesRoute ?? false) && explicitChildNodes) ||
!(_properties.scopesRoute ?? false),
'explicitChildNodes must be set to true if scopes route is true',
}) : super(child) {
initSemanticsAnnotations(
properties: properties,
container: container,
explicitChildNodes: explicitChildNodes,
excludeSemantics: excludeSemantics,
blockUserActions: blockUserActions,
localeForSubtree: localeForSubtree,
textDirection: textDirection,
);
assert(
!((_properties.toggled ?? false) && (_properties.checked ?? false)),
'A semantics node cannot be toggled and checked at the same time',
);
if (_properties.enabled != null) {
config.isEnabled = _properties.enabled;
}
if (_properties.checked != null) {
config.isChecked = _properties.checked;
}
if (_properties.mixed != null) {
config.isCheckStateMixed = _properties.mixed;
}
if (_properties.toggled != null) {
config.isToggled = _properties.toggled;
}
if (_properties.selected != null) {
config.isSelected = _properties.selected!;
}
if (_properties.button != null) {
config.isButton = _properties.button!;
}
if (_properties.expanded != null) {
config.isExpanded = _properties.expanded;
}
if (_properties.link != null) {
config.isLink = _properties.link!;
}
if (_properties.linkUrl != null) {
config.linkUrl = _properties.linkUrl;
}
if (_properties.slider != null) {
config.isSlider = _properties.slider!;
}
if (_properties.keyboardKey != null) {
config.isKeyboardKey = _properties.keyboardKey!;
}
if (_properties.header != null) {
config.isHeader = _properties.header!;
}
if (_properties.headingLevel != null) {
config.headingLevel = _properties.headingLevel!;
}
if (_properties.textField != null) {
config.isTextField = _properties.textField!;
}
if (_properties.readOnly != null) {
config.isReadOnly = _properties.readOnly!;
}
if (_properties.focusable != null) {
config.isFocusable = _properties.focusable!;
}
if (_properties.focused != null) {
config.isFocused = _properties.focused!;
}
if (_properties.inMutuallyExclusiveGroup != null) {
config.isInMutuallyExclusiveGroup = _properties.inMutuallyExclusiveGroup!;
}
if (_properties.obscured != null) {
config.isObscured = _properties.obscured!;
}
if (_properties.multiline != null) {
config.isMultiline = _properties.multiline!;
}
if (_properties.hidden != null) {
config.isHidden = _properties.hidden!;
}
if (_properties.image != null) {
config.isImage = _properties.image!;
}
if (_properties.isRequired != null) {
config.isRequired = _properties.isRequired;
}
if (_properties.identifier != null) {
config.identifier = _properties.identifier!;
}
if (_attributedLabel != null) {
config.attributedLabel = _attributedLabel!;
}
if (_attributedValue != null) {
config.attributedValue = _attributedValue!;
}
if (_attributedIncreasedValue != null) {
config.attributedIncreasedValue = _attributedIncreasedValue!;
}
if (_attributedDecreasedValue != null) {
config.attributedDecreasedValue = _attributedDecreasedValue!;
}
if (_attributedHint != null) {
config.attributedHint = _attributedHint!;
}
if (_properties.tooltip != null) {
config.tooltip = _properties.tooltip!;
}
if (_properties.hintOverrides != null && _properties.hintOverrides!.isNotEmpty) {
config.hintOverrides = _properties.hintOverrides;
}
if (_properties.scopesRoute != null) {
config.scopesRoute = _properties.scopesRoute!;
}
if (_properties.namesRoute != null) {
config.namesRoute = _properties.namesRoute!;
}
if (_properties.liveRegion != null) {
config.liveRegion = _properties.liveRegion!;
}
if (_properties.maxValueLength != null) {
config.maxValueLength = _properties.maxValueLength;
}
if (_properties.currentValueLength != null) {
config.currentValueLength = _properties.currentValueLength;
}
if (textDirection != null) {
config.textDirection = textDirection;
}
if (_properties.sortKey != null) {
config.sortKey = _properties.sortKey;
}
if (_properties.tagForChildren != null) {
config.addTagForChildren(_properties.tagForChildren!);
}
if (properties.role != null) {
config.role = _properties.role!;
}
if (_properties.controlsNodes != null) {
config.controlsNodes = _properties.controlsNodes;
}
if (config.validationResult != _properties.validationResult) {
config.validationResult = _properties.validationResult;
}
if (_properties.inputType != null) {
config.inputType = _properties.inputType!;
}
// Registering _perform* as action handlers instead of the user provided
// ones to ensure that changing a user provided handler from a non-null to
// another non-null value doesn't require a semantics update.
if (_properties.onTap != null) {
config.onTap = _performTap;
}
if (_properties.onLongPress != null) {
config.onLongPress = _performLongPress;
}
if (_properties.onDismiss != null) {
config.onDismiss = _performDismiss;
}
if (_properties.onScrollLeft != null) {
config.onScrollLeft = _performScrollLeft;
}
if (_properties.onScrollRight != null) {
config.onScrollRight = _performScrollRight;
}
if (_properties.onScrollUp != null) {
config.onScrollUp = _performScrollUp;
}
if (_properties.onScrollDown != null) {
config.onScrollDown = _performScrollDown;
}
if (_properties.onIncrease != null) {
config.onIncrease = _performIncrease;
}
if (_properties.onDecrease != null) {
config.onDecrease = _performDecrease;
}
if (_properties.onCopy != null) {
config.onCopy = _performCopy;
}
if (_properties.onCut != null) {
config.onCut = _performCut;
}
if (_properties.onPaste != null) {
config.onPaste = _performPaste;
}
if (_properties.onMoveCursorForwardByCharacter != null) {
config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
}
if (_properties.onMoveCursorBackwardByCharacter != null) {
config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
}
if (_properties.onMoveCursorForwardByWord != null) {
config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
}
if (_properties.onMoveCursorBackwardByWord != null) {
config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
}
if (_properties.onSetSelection != null) {
config.onSetSelection = _performSetSelection;
}
if (_properties.onSetText != null) {
config.onSetText = _performSetText;
}
if (_properties.onDidGainAccessibilityFocus != null) {
config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
}
if (_properties.onDidLoseAccessibilityFocus != null) {
config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
}
if (_properties.onFocus != null) {
config.onFocus = _performFocus;
}
if (_properties.customSemanticsActions != null) {
config.customSemanticsActions = _properties.customSemanticsActions!;
}
}
void _performTap() {
_properties.onTap?.call();
}
void _performLongPress() {
_properties.onLongPress?.call();
}
void _performDismiss() {
_properties.onDismiss?.call();
}
void _performScrollLeft() {
_properties.onScrollLeft?.call();
}
void _performScrollRight() {
_properties.onScrollRight?.call();
}
void _performScrollUp() {
_properties.onScrollUp?.call();
}
void _performScrollDown() {
_properties.onScrollDown?.call();
}
void _performIncrease() {
_properties.onIncrease?.call();
}
void _performDecrease() {
_properties.onDecrease?.call();
}
void _performCopy() {
_properties.onCopy?.call();
}
void _performCut() {
_properties.onCut?.call();
}
void _performPaste() {
_properties.onPaste?.call();
}
void _performMoveCursorForwardByCharacter(bool extendSelection) {
_properties.onMoveCursorForwardByCharacter?.call(extendSelection);
}
void _performMoveCursorBackwardByCharacter(bool extendSelection) {
_properties.onMoveCursorBackwardByCharacter?.call(extendSelection);
}
void _performMoveCursorForwardByWord(bool extendSelection) {
_properties.onMoveCursorForwardByWord?.call(extendSelection);
}
void _performMoveCursorBackwardByWord(bool extendSelection) {
_properties.onMoveCursorBackwardByWord?.call(extendSelection);
}
void _performSetSelection(TextSelection selection) {
_properties.onSetSelection?.call(selection);
}
void _performSetText(String text) {
_properties.onSetText?.call(text);
}
void _performDidGainAccessibilityFocus() {
_properties.onDidGainAccessibilityFocus?.call();
}
void _performDidLoseAccessibilityFocus() {
_properties.onDidLoseAccessibilityFocus?.call();
}
void _performFocus() {
_properties.onFocus?.call();
}
}

View File

@ -481,3 +481,30 @@ class RenderSliverConstrainedCrossAxis extends RenderProxySliver {
);
}
}
/// Add annotations to the [SemanticsNode] for this subtree.
class RenderSliverSemanticsAnnotations extends RenderProxySliver with SemanticsAnnotationsMixin {
/// Creates a render object that attaches a semantic annotation.
///
/// If the [SemanticsProperties.attributedLabel] is not null, the [textDirection] must also not be null.
RenderSliverSemanticsAnnotations({
RenderSliver? child,
required SemanticsProperties properties,
bool container = false,
bool explicitChildNodes = false,
bool excludeSemantics = false,
bool blockUserActions = false,
Locale? localeForSubtree,
TextDirection? textDirection,
}) : super(child) {
initSemanticsAnnotations(
properties: properties,
container: container,
explicitChildNodes: explicitChildNodes,
excludeSemantics: excludeSemantics,
blockUserActions: blockUserActions,
localeForSubtree: localeForSubtree,
textDirection: textDirection,
);
}
}

View File

@ -3845,6 +3845,489 @@ class SliverPadding extends SingleChildRenderObjectWidget {
}
}
/// An abstract class for building widgets that annotate their subtree with a
/// description of the meaning of the widgets.
///
/// {@template flutter.widgets.SemanticsBase}
/// Used by assistive technologies, search engines, and other semantic analysis
/// software to determine the meaning of the application.
///
/// See also:
///
/// * [SemanticsProperties], which contains a complete documentation for each
/// of the constructor parameters that belongs to semantics properties.
/// * [RenderObject.describeSemanticsConfiguration], the rendering library API
/// through which the [Semantics] widget and [SliverSemantics] sliver are
/// actually implemented.
/// * [SemanticsNode], the object used by the rendering library to represent
/// semantics in the semantics tree.
/// * [SemanticsDebugger], an overlay to help visualize the semantics tree. Can
/// be enabled using [WidgetsApp.showSemanticsDebugger],
/// [MaterialApp.showSemanticsDebugger], or [CupertinoApp.showSemanticsDebugger].
/// * [MergeSemantics], a widget which marks a subtree as being a single node for
/// accessibility purposes.
/// * [ExcludeSemantics], a widget which excludes a subtree from the semantics tree
/// (which might be useful if it is, e.g., totally decorative and not
/// important to the user).
/// {@endtemplate}
@immutable
sealed class _SemanticsBase extends SingleChildRenderObjectWidget {
/// Creates a semantic annotation.
///
/// To create a `const` instance of [_SemanticsBase], use the
/// [_SemanticsBase.fromProperties] constructor.
///
/// {@template flutter.widgets.SemanticsBase.constructor}
/// See also:
///
/// * [SemanticsProperties], which contains a complete documentation for each
/// of the constructor parameters that belongs to semantics properties.
/// * [SemanticsSortKey] for a class that determines accessibility traversal
/// order.
/// {@endtemplate}
// Properties added to this constructor should be marked required
// to enforce its subclasses add it to their constructors.
_SemanticsBase({
Key? key,
Widget? child,
required bool container,
required bool explicitChildNodes,
required bool excludeSemantics,
required bool blockUserActions,
required bool? enabled,
required bool? checked,
required bool? mixed,
required bool? selected,
required bool? toggled,
required bool? button,
required bool? slider,
required bool? keyboardKey,
required bool? link,
required Uri? linkUrl,
required bool? header,
required int? headingLevel,
required bool? textField,
required bool? readOnly,
required bool? focusable,
required bool? focused,
required bool? inMutuallyExclusiveGroup,
required bool? obscured,
required bool? multiline,
required bool? scopesRoute,
required bool? namesRoute,
required bool? hidden,
required bool? image,
required bool? liveRegion,
required bool? expanded,
required bool? isRequired,
required int? maxValueLength,
required int? currentValueLength,
required String? identifier,
required String? label,
required AttributedString? attributedLabel,
required String? value,
required AttributedString? attributedValue,
required String? increasedValue,
required AttributedString? attributedIncreasedValue,
required String? decreasedValue,
required AttributedString? attributedDecreasedValue,
required String? hint,
required AttributedString? attributedHint,
required String? tooltip,
required String? onTapHint,
required String? onLongPressHint,
required TextDirection? textDirection,
required SemanticsSortKey? sortKey,
required SemanticsTag? tagForChildren,
required VoidCallback? onTap,
required VoidCallback? onLongPress,
required VoidCallback? onScrollLeft,
required VoidCallback? onScrollRight,
required VoidCallback? onScrollUp,
required VoidCallback? onScrollDown,
required VoidCallback? onIncrease,
required VoidCallback? onDecrease,
required VoidCallback? onCopy,
required VoidCallback? onCut,
required VoidCallback? onPaste,
required VoidCallback? onDismiss,
required MoveCursorHandler? onMoveCursorForwardByCharacter,
required MoveCursorHandler? onMoveCursorBackwardByCharacter,
required SetSelectionHandler? onSetSelection,
required SetTextHandler? onSetText,
required VoidCallback? onDidGainAccessibilityFocus,
required VoidCallback? onDidLoseAccessibilityFocus,
required VoidCallback? onFocus,
required Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
required SemanticsRole? role,
required Set<String>? controlsNodes,
required SemanticsValidationResult validationResult,
required ui.SemanticsInputType? inputType,
required Locale? localeForSubtree,
}) : this.fromProperties(
key: key,
child: child,
container: container,
explicitChildNodes: explicitChildNodes,
excludeSemantics: excludeSemantics,
blockUserActions: blockUserActions,
localeForSubtree: localeForSubtree,
properties: SemanticsProperties(
enabled: enabled,
checked: checked,
mixed: mixed,
expanded: expanded,
toggled: toggled,
selected: selected,
button: button,
slider: slider,
keyboardKey: keyboardKey,
link: link,
linkUrl: linkUrl,
header: header,
headingLevel: headingLevel,
textField: textField,
readOnly: readOnly,
focusable: focusable,
focused: focused,
inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
obscured: obscured,
multiline: multiline,
scopesRoute: scopesRoute,
namesRoute: namesRoute,
hidden: hidden,
image: image,
liveRegion: liveRegion,
isRequired: isRequired,
maxValueLength: maxValueLength,
currentValueLength: currentValueLength,
identifier: identifier,
label: label,
attributedLabel: attributedLabel,
value: value,
attributedValue: attributedValue,
increasedValue: increasedValue,
attributedIncreasedValue: attributedIncreasedValue,
decreasedValue: decreasedValue,
attributedDecreasedValue: attributedDecreasedValue,
hint: hint,
attributedHint: attributedHint,
tooltip: tooltip,
textDirection: textDirection,
sortKey: sortKey,
tagForChildren: tagForChildren,
onTap: onTap,
onLongPress: onLongPress,
onScrollLeft: onScrollLeft,
onScrollRight: onScrollRight,
onScrollUp: onScrollUp,
onScrollDown: onScrollDown,
onIncrease: onIncrease,
onDecrease: onDecrease,
onCopy: onCopy,
onCut: onCut,
onPaste: onPaste,
onMoveCursorForwardByCharacter: onMoveCursorForwardByCharacter,
onMoveCursorBackwardByCharacter: onMoveCursorBackwardByCharacter,
onDidGainAccessibilityFocus: onDidGainAccessibilityFocus,
onDidLoseAccessibilityFocus: onDidLoseAccessibilityFocus,
onFocus: onFocus,
onDismiss: onDismiss,
onSetSelection: onSetSelection,
onSetText: onSetText,
customSemanticsActions: customSemanticsActions,
hintOverrides: onTapHint != null || onLongPressHint != null
? SemanticsHintOverrides(onTapHint: onTapHint, onLongPressHint: onLongPressHint)
: null,
role: role,
controlsNodes: controlsNodes,
validationResult: validationResult,
inputType: inputType,
),
);
/// {@template flutter.widgets.SemanticsBase.fromProperties}
/// Creates a semantic annotation using [SemanticsProperties].
/// {@endtemplate}
// Properties added to this constructor should be marked required
// to enforce its subclasses add it to their constructors.
const _SemanticsBase.fromProperties({
super.key,
super.child,
required this.container,
required this.explicitChildNodes,
required this.excludeSemantics,
required this.blockUserActions,
required this.localeForSubtree,
required this.properties,
}) : assert(
localeForSubtree == null || container,
'To assign locale for subtree, this widget needs to be a '
'container',
);
/// Contains properties used by assistive technologies to make the application
/// more accessible.
final SemanticsProperties properties;
/// If [container] is true, this widget will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors (if the ancestor allows that).
///
/// Whether descendants of this widget can add their semantic information to the
/// [SemanticsNode] introduced by this configuration is controlled by
/// [explicitChildNodes].
final bool container;
/// Whether descendants of this widget are allowed to add semantic information
/// to the [SemanticsNode] annotated by this widget.
///
/// When set to false descendants are allowed to annotate [SemanticsNode]s of
/// their parent with the semantic information they want to contribute to the
/// semantic tree.
/// When set to true the only way for descendants to contribute semantic
/// information to the semantic tree is to introduce new explicit
/// [SemanticsNode]s to the tree.
///
/// If the semantics properties of this node include
/// [SemanticsProperties.scopesRoute] set to true, then [explicitChildNodes]
/// must be true also.
///
/// This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
/// to create semantic boundaries that are either writable or not for children.
final bool explicitChildNodes;
/// The [Locale] for widgets in the subtree.
///
/// If null, the subtree will inherit the locale form ancestor widget.
final Locale? localeForSubtree;
/// Whether to replace all child semantics with this node.
///
/// Defaults to false.
///
/// When this flag is set to true, all child semantics nodes are ignored.
/// This can be used as a convenience for cases where a child is wrapped in
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
final bool excludeSemantics;
/// Whether to block user interactions for the rendering subtree.
///
/// Setting this to true will prevent users from interacting with The
/// rendering object configured by this widget and its subtree through
/// pointer-related [SemanticsAction]s in assistive technologies.
///
/// The [SemanticsNode] created from this widget is still focusable by
/// assistive technologies. Only pointer-related [SemanticsAction]s, such as
/// [SemanticsAction.tap] or its friends, are blocked.
///
/// If this widget is merged into a parent semantics node, only the
/// [SemanticsAction]s of this widget and the widgets in the subtree are
/// blocked.
///
/// For example using [Semantics]:
/// ```dart
/// void _myTap() { }
/// void _myLongPress() { }
///
/// Widget build(BuildContext context) {
/// return Semantics(
/// onTap: _myTap,
/// child: Semantics(
/// blockUserActions: true,
/// onLongPress: _myLongPress,
/// child: const Text('label'),
/// ),
/// );
/// }
/// ```
///
/// The result semantics node will still have `_myTap`, but the `_myLongPress`
/// will be blocked.
///
/// and similarly using [SliverSemantics]:
/// ```dart
/// void _myTap() { }
/// void _myLongPress() { }
///
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverSemantics(
/// onTap: _myTap,
/// sliver: SliverSemantics(
/// blockUserActions: true,
/// onLongPress: _myLongPress,
/// sliver: const SliverToBoxAdapter(
/// child: Text('label'),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
///
/// The result semantics node will still have `_myTap`, but the `_myLongPress`
/// will be blocked.
final bool blockUserActions;
TextDirection? _getTextDirection(BuildContext context) {
if (properties.textDirection != null) {
return properties.textDirection;
}
final bool containsText =
properties.label != null ||
properties.attributedLabel != null ||
properties.value != null ||
properties.attributedValue != null ||
properties.increasedValue != null ||
properties.attributedIncreasedValue != null ||
properties.decreasedValue != null ||
properties.attributedDecreasedValue != null ||
properties.hint != null ||
properties.attributedHint != null ||
properties.tooltip != null;
if (!containsText) {
return null;
}
return Directionality.maybeOf(context);
}
}
/// A sliver that annotates its subtree with a description of the meaning of
/// the slivers.
///
/// {@macro flutter.widgets.SemanticsBase}
/// * [Semantics], the widget variant of this sliver.
@immutable
class SliverSemantics extends _SemanticsBase {
/// Creates a semantic annotation.
///
/// To create a `const` instance of [SliverSemantics], use the
/// [SliverSemantics.fromProperties] constructor.
///
/// {@macro flutter.widgets.SemanticsBase.constructor}
SliverSemantics({
super.key,
required Widget sliver,
super.container = false,
super.explicitChildNodes = false,
super.excludeSemantics = false,
super.blockUserActions = false,
super.enabled,
super.checked,
super.mixed,
super.selected,
super.toggled,
super.button,
super.slider,
super.keyboardKey,
super.link,
super.linkUrl,
super.header,
super.headingLevel,
super.textField,
super.readOnly,
super.focusable,
super.focused,
super.inMutuallyExclusiveGroup,
super.obscured,
super.multiline,
super.scopesRoute,
super.namesRoute,
super.hidden,
super.image,
super.liveRegion,
super.expanded,
super.isRequired,
super.maxValueLength,
super.currentValueLength,
super.identifier,
super.label,
super.attributedLabel,
super.value,
super.attributedValue,
super.increasedValue,
super.attributedIncreasedValue,
super.decreasedValue,
super.attributedDecreasedValue,
super.hint,
super.attributedHint,
super.tooltip,
super.onTapHint,
super.onLongPressHint,
super.textDirection,
super.sortKey,
super.tagForChildren,
super.onTap,
super.onLongPress,
super.onScrollLeft,
super.onScrollRight,
super.onScrollUp,
super.onScrollDown,
super.onIncrease,
super.onDecrease,
super.onCopy,
super.onCut,
super.onPaste,
super.onDismiss,
super.onMoveCursorForwardByCharacter,
super.onMoveCursorBackwardByCharacter,
super.onSetSelection,
super.onSetText,
super.onDidGainAccessibilityFocus,
super.onDidLoseAccessibilityFocus,
super.onFocus,
super.customSemanticsActions,
super.role,
super.controlsNodes,
super.validationResult = SemanticsValidationResult.none,
super.inputType,
super.localeForSubtree,
}) : super(child: sliver);
/// {@macro flutter.widgets.SemanticsBase.fromProperties}
const SliverSemantics.fromProperties({
super.key,
super.child,
super.container = false,
super.explicitChildNodes = false,
super.excludeSemantics = false,
super.blockUserActions = false,
super.localeForSubtree,
required super.properties,
}) : super.fromProperties();
@override
RenderSliverSemanticsAnnotations createRenderObject(BuildContext context) {
return RenderSliverSemanticsAnnotations(
container: container,
explicitChildNodes: explicitChildNodes,
excludeSemantics: excludeSemantics,
blockUserActions: blockUserActions,
properties: properties,
localeForSubtree: localeForSubtree,
textDirection: _getTextDirection(context),
);
}
@override
void updateRenderObject(BuildContext context, RenderSliverSemanticsAnnotations renderObject) {
renderObject
..container = container
..explicitChildNodes = explicitChildNodes
..excludeSemantics = excludeSemantics
..blockUserActions = blockUserActions
..properties = properties
..textDirection = _getTextDirection(context)
..localeForSubtree = localeForSubtree;
}
}
// LAYOUT NODES
/// Returns the [AxisDirection] in the given [Axis] in the current
@ -7289,294 +7772,108 @@ class MetaData extends SingleChildRenderObjectWidget {
/// A widget that annotates the widget tree with a description of the meaning of
/// the widgets.
///
/// Used by assistive technologies, search engines, and other semantic analysis
/// software to determine the meaning of the application.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=NvtMt_DtFrQ}
///
/// See also:
///
/// * [SemanticsProperties], which contains a complete documentation for each
/// of the constructor parameters that belongs to semantics properties.
/// * [MergeSemantics], which marks a subtree as being a single node for
/// accessibility purposes.
/// * [ExcludeSemantics], which excludes a subtree from the semantics tree
/// (which might be useful if it is, e.g., totally decorative and not
/// important to the user).
/// * [RenderObject.describeSemanticsConfiguration], the rendering library API
/// through which the [Semantics] widget is actually implemented.
/// * [SemanticsNode], the object used by the rendering library to represent
/// semantics in the semantics tree.
/// * [SemanticsDebugger], an overlay to help visualize the semantics tree. Can
/// be enabled using [WidgetsApp.showSemanticsDebugger],
/// [MaterialApp.showSemanticsDebugger], or [CupertinoApp.showSemanticsDebugger].
/// {@macro flutter.widgets.SemanticsBase}
/// * [SliverSemantics], the sliver variant of this widget.
@immutable
class Semantics extends SingleChildRenderObjectWidget {
class Semantics extends _SemanticsBase {
/// Creates a semantic annotation.
///
/// To create a `const` instance of [Semantics], use the
/// [Semantics.fromProperties] constructor.
///
/// See also:
///
/// * [SemanticsProperties], which contains a complete documentation for each
/// of the constructor parameters that belongs to semantics properties.
/// * [SemanticsSortKey] for a class that determines accessibility traversal
/// order.
/// {@macro flutter.widgets.SemanticsBase}
Semantics({
Key? key,
Widget? child,
bool container = false,
bool explicitChildNodes = false,
bool excludeSemantics = false,
bool blockUserActions = false,
bool? enabled,
bool? checked,
bool? mixed,
bool? selected,
bool? toggled,
bool? button,
bool? slider,
bool? keyboardKey,
bool? link,
Uri? linkUrl,
bool? header,
int? headingLevel,
bool? textField,
bool? readOnly,
bool? focusable,
bool? focused,
bool? inMutuallyExclusiveGroup,
bool? obscured,
bool? multiline,
bool? scopesRoute,
bool? namesRoute,
bool? hidden,
bool? image,
bool? liveRegion,
bool? expanded,
bool? isRequired,
int? maxValueLength,
int? currentValueLength,
String? identifier,
String? label,
AttributedString? attributedLabel,
String? value,
AttributedString? attributedValue,
String? increasedValue,
AttributedString? attributedIncreasedValue,
String? decreasedValue,
AttributedString? attributedDecreasedValue,
String? hint,
AttributedString? attributedHint,
String? tooltip,
String? onTapHint,
String? onLongPressHint,
TextDirection? textDirection,
SemanticsSortKey? sortKey,
SemanticsTag? tagForChildren,
VoidCallback? onTap,
VoidCallback? onLongPress,
VoidCallback? onScrollLeft,
VoidCallback? onScrollRight,
VoidCallback? onScrollUp,
VoidCallback? onScrollDown,
VoidCallback? onIncrease,
VoidCallback? onDecrease,
VoidCallback? onCopy,
VoidCallback? onCut,
VoidCallback? onPaste,
VoidCallback? onDismiss,
MoveCursorHandler? onMoveCursorForwardByCharacter,
MoveCursorHandler? onMoveCursorBackwardByCharacter,
SetSelectionHandler? onSetSelection,
SetTextHandler? onSetText,
VoidCallback? onDidGainAccessibilityFocus,
VoidCallback? onDidLoseAccessibilityFocus,
VoidCallback? onFocus,
Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
SemanticsRole? role,
Set<String>? controlsNodes,
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
ui.SemanticsInputType? inputType,
Locale? localeForSubtree,
}) : this.fromProperties(
key: key,
child: child,
container: container,
explicitChildNodes: explicitChildNodes,
excludeSemantics: excludeSemantics,
blockUserActions: blockUserActions,
localeForSubtree: localeForSubtree,
properties: SemanticsProperties(
enabled: enabled,
checked: checked,
mixed: mixed,
expanded: expanded,
toggled: toggled,
selected: selected,
button: button,
slider: slider,
keyboardKey: keyboardKey,
link: link,
linkUrl: linkUrl,
header: header,
headingLevel: headingLevel,
textField: textField,
readOnly: readOnly,
focusable: focusable,
focused: focused,
inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
obscured: obscured,
multiline: multiline,
scopesRoute: scopesRoute,
namesRoute: namesRoute,
hidden: hidden,
image: image,
liveRegion: liveRegion,
isRequired: isRequired,
maxValueLength: maxValueLength,
currentValueLength: currentValueLength,
identifier: identifier,
label: label,
attributedLabel: attributedLabel,
value: value,
attributedValue: attributedValue,
increasedValue: increasedValue,
attributedIncreasedValue: attributedIncreasedValue,
decreasedValue: decreasedValue,
attributedDecreasedValue: attributedDecreasedValue,
hint: hint,
attributedHint: attributedHint,
tooltip: tooltip,
textDirection: textDirection,
sortKey: sortKey,
tagForChildren: tagForChildren,
onTap: onTap,
onLongPress: onLongPress,
onScrollLeft: onScrollLeft,
onScrollRight: onScrollRight,
onScrollUp: onScrollUp,
onScrollDown: onScrollDown,
onIncrease: onIncrease,
onDecrease: onDecrease,
onCopy: onCopy,
onCut: onCut,
onPaste: onPaste,
onMoveCursorForwardByCharacter: onMoveCursorForwardByCharacter,
onMoveCursorBackwardByCharacter: onMoveCursorBackwardByCharacter,
onDidGainAccessibilityFocus: onDidGainAccessibilityFocus,
onDidLoseAccessibilityFocus: onDidLoseAccessibilityFocus,
onFocus: onFocus,
onDismiss: onDismiss,
onSetSelection: onSetSelection,
onSetText: onSetText,
customSemanticsActions: customSemanticsActions,
hintOverrides: onTapHint != null || onLongPressHint != null
? SemanticsHintOverrides(onTapHint: onTapHint, onLongPressHint: onLongPressHint)
: null,
role: role,
controlsNodes: controlsNodes,
validationResult: validationResult,
inputType: inputType,
),
);
super.key,
super.child,
super.container = false,
super.explicitChildNodes = false,
super.excludeSemantics = false,
super.blockUserActions = false,
super.enabled,
super.checked,
super.mixed,
super.selected,
super.toggled,
super.button,
super.slider,
super.keyboardKey,
super.link,
super.linkUrl,
super.header,
super.headingLevel,
super.textField,
super.readOnly,
super.focusable,
super.focused,
super.inMutuallyExclusiveGroup,
super.obscured,
super.multiline,
super.scopesRoute,
super.namesRoute,
super.hidden,
super.image,
super.liveRegion,
super.expanded,
super.isRequired,
super.maxValueLength,
super.currentValueLength,
super.identifier,
super.label,
super.attributedLabel,
super.value,
super.attributedValue,
super.increasedValue,
super.attributedIncreasedValue,
super.decreasedValue,
super.attributedDecreasedValue,
super.hint,
super.attributedHint,
super.tooltip,
super.onTapHint,
super.onLongPressHint,
super.textDirection,
super.sortKey,
super.tagForChildren,
super.onTap,
super.onLongPress,
super.onScrollLeft,
super.onScrollRight,
super.onScrollUp,
super.onScrollDown,
super.onIncrease,
super.onDecrease,
super.onCopy,
super.onCut,
super.onPaste,
super.onDismiss,
super.onMoveCursorForwardByCharacter,
super.onMoveCursorBackwardByCharacter,
super.onSetSelection,
super.onSetText,
super.onDidGainAccessibilityFocus,
super.onDidLoseAccessibilityFocus,
super.onFocus,
super.customSemanticsActions,
super.role,
super.controlsNodes,
super.validationResult = SemanticsValidationResult.none,
super.inputType,
super.localeForSubtree,
});
/// Creates a semantic annotation using [SemanticsProperties].
/// {@macro flutter.widgets.SemanticsBase.fromProperties}
const Semantics.fromProperties({
super.key,
super.child,
this.container = false,
this.explicitChildNodes = false,
this.excludeSemantics = false,
this.blockUserActions = false,
this.localeForSubtree,
required this.properties,
}) : assert(
localeForSubtree == null || container,
'To assign locale for subtree, this widget needs to be a '
'container',
);
/// Contains properties used by assistive technologies to make the application
/// more accessible.
final SemanticsProperties properties;
/// If [container] is true, this widget will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors (if the ancestor allows that).
///
/// Whether descendants of this widget can add their semantic information to the
/// [SemanticsNode] introduced by this configuration is controlled by
/// [explicitChildNodes].
final bool container;
/// Whether descendants of this widget are allowed to add semantic information
/// to the [SemanticsNode] annotated by this widget.
///
/// When set to false descendants are allowed to annotate [SemanticsNode]s of
/// their parent with the semantic information they want to contribute to the
/// semantic tree.
/// When set to true the only way for descendants to contribute semantic
/// information to the semantic tree is to introduce new explicit
/// [SemanticsNode]s to the tree.
///
/// If the semantics properties of this node include
/// [SemanticsProperties.scopesRoute] set to true, then [explicitChildNodes]
/// must be true also.
///
/// This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
/// to create semantic boundaries that are either writable or not for children.
final bool explicitChildNodes;
/// The [Locale] for widgets in the subtree.
///
/// If null, the subtree will inherit the locale form ancestor widget.
final Locale? localeForSubtree;
/// Whether to replace all child semantics with this node.
///
/// Defaults to false.
///
/// When this flag is set to true, all child semantics nodes are ignored.
/// This can be used as a convenience for cases where a child is wrapped in
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
final bool excludeSemantics;
/// Whether to block user interactions for the rendering subtree.
///
/// Setting this to true will prevent users from interacting with The
/// rendering object configured by this widget and its subtree through
/// pointer-related [SemanticsAction]s in assistive technologies.
///
/// The [SemanticsNode] created from this widget is still focusable by
/// assistive technologies. Only pointer-related [SemanticsAction]s, such as
/// [SemanticsAction.tap] or its friends, are blocked.
///
/// If this widget is merged into a parent semantics node, only the
/// [SemanticsAction]s of this widget and the widgets in the subtree are
/// blocked.
///
/// For example:
/// ```dart
/// void _myTap() { }
/// void _myLongPress() { }
///
/// Widget build(BuildContext context) {
/// return Semantics(
/// onTap: _myTap,
/// child: Semantics(
/// blockUserActions: true,
/// onLongPress: _myLongPress,
/// child: const Text('label'),
/// ),
/// );
/// }
/// ```
///
/// The result semantics node will still have `_myTap`, but the `_myLongPress`
/// will be blocked.
final bool blockUserActions;
super.container = false,
super.explicitChildNodes = false,
super.excludeSemantics = false,
super.blockUserActions = false,
super.localeForSubtree,
required super.properties,
}) : super.fromProperties();
@override
RenderSemanticsAnnotations createRenderObject(BuildContext context) {
@ -7591,31 +7888,6 @@ class Semantics extends SingleChildRenderObjectWidget {
);
}
TextDirection? _getTextDirection(BuildContext context) {
if (properties.textDirection != null) {
return properties.textDirection;
}
final bool containsText =
properties.label != null ||
properties.attributedLabel != null ||
properties.value != null ||
properties.attributedValue != null ||
properties.increasedValue != null ||
properties.attributedIncreasedValue != null ||
properties.decreasedValue != null ||
properties.attributedDecreasedValue != null ||
properties.hint != null ||
properties.attributedHint != null ||
properties.tooltip != null;
if (!containsText) {
return null;
}
return Directionality.maybeOf(context);
}
@override
void updateRenderObject(BuildContext context, RenderSemanticsAnnotations renderObject) {
renderObject

File diff suppressed because it is too large Load Diff