Unverified Commit 8059aea3 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

TextFields should only have one SemanticsNode (#14219)

* scrolling node eleminated

* remove second node

* fix ids
parent 773902c5
...@@ -648,6 +648,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -648,6 +648,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
FocusScope.of(context).reparentIfNeeded(widget.focusNode); FocusScope.of(context).reparentIfNeeded(widget.focusNode);
super.build(context); // See AutomaticKeepAliveClientMixin. super.build(context); // See AutomaticKeepAliveClientMixin.
return new Scrollable( return new Scrollable(
excludeFromSemantics: true,
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right, axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
controller: _scrollController, controller: _scrollController,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
......
...@@ -79,8 +79,10 @@ class Scrollable extends StatefulWidget { ...@@ -79,8 +79,10 @@ class Scrollable extends StatefulWidget {
this.controller, this.controller,
this.physics, this.physics,
@required this.viewportBuilder, @required this.viewportBuilder,
this.excludeFromSemantics: false,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(viewportBuilder != null), assert(viewportBuilder != null),
assert(excludeFromSemantics != null),
super (key: key); super (key: key);
/// The direction in which this widget scrolls. /// The direction in which this widget scrolls.
...@@ -147,6 +149,19 @@ class Scrollable extends StatefulWidget { ...@@ -147,6 +149,19 @@ class Scrollable extends StatefulWidget {
/// slivers and sizes itself based on the size of the slivers. /// slivers and sizes itself based on the size of the slivers.
final ViewportBuilder viewportBuilder; final ViewportBuilder viewportBuilder;
/// Whether the scroll actions introduced by this [Scrollable] are exposed
/// in the semantics tree.
///
/// Text fields with an overflow are usually scrollable to make sure that the
/// user can get to the beginning/end of the entered text. However, these
/// scrolling actions are generally not exposed to the semantics layer.
///
/// See also:
///
/// * [GestureDetector.excludeFromSemantics], which is used to accomplish the
/// exclusion.
final bool excludeFromSemantics;
/// The axis along which the scroll view scrolls. /// The axis along which the scroll view scrolls.
/// ///
/// Determined by the [axisDirection]. /// Determined by the [axisDirection].
...@@ -490,27 +505,33 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -490,27 +505,33 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(position != null); assert(position != null);
// TODO(ianh): Having all these global keys is sad. // TODO(ianh): Having all these global keys is sad.
final Widget result = new _ExcludableScrollSemantics( Widget result = new RawGestureDetector(
key: _excludableScrollSemanticsKey, key: _gestureDetectorKey,
child: new RawGestureDetector( gestures: _gestureRecognizers,
key: _gestureDetectorKey, behavior: HitTestBehavior.opaque,
gestures: _gestureRecognizers, excludeFromSemantics: widget.excludeFromSemantics,
behavior: HitTestBehavior.opaque, child: new Semantics(
child: new Semantics( explicitChildNodes: !widget.excludeFromSemantics,
explicitChildNodes: true, child: new IgnorePointer(
child: new IgnorePointer( key: _ignorePointerKey,
key: _ignorePointerKey, ignoring: _shouldIgnorePointer,
ignoring: _shouldIgnorePointer, ignoringSemantics: false,
ignoringSemantics: false, child: new _ScrollableScope(
child: new _ScrollableScope( scrollable: this,
scrollable: this, position: position,
position: position, child: widget.viewportBuilder(context, position),
child: widget.viewportBuilder(context, position),
),
), ),
), ),
), ),
); );
if (!widget.excludeFromSemantics) {
result = new _ExcludableScrollSemantics(
key: _excludableScrollSemanticsKey,
child: result,
);
}
return _configuration.buildViewportChrome(context, result, widget.axisDirection); return _configuration.buildViewportChrome(context, result, widget.axisDirection);
} }
......
...@@ -164,6 +164,10 @@ void main() { ...@@ -164,6 +164,10 @@ void main() {
return endpoints[0].point + const Offset(0.0, -2.0); return endpoints[0].point + const Offset(0.0, -2.0);
} }
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('TextField has consistent size', (WidgetTester tester) async { testWidgets('TextField has consistent size', (WidgetTester tester) async {
final Key textFieldKey = new UniqueKey(); final Key textFieldKey = new UniqueKey();
String textFieldValue; String textFieldValue;
...@@ -1747,4 +1751,120 @@ void main() { ...@@ -1747,4 +1751,120 @@ void main() {
expect(tester.getBottomLeft(find.byKey(keyB)).dy, rowBottomY); expect(tester.getBottomLeft(find.byKey(keyB)).dy, rowBottomY);
}); });
testWidgets('TextField semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final TextEditingController controller = new TextEditingController();
final Key key = new UniqueKey();
await tester.pumpWidget(
overlay(
child: new TextField(
key: key,
controller: controller,
)
),
);
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
],
),
],
), ignoreTransform: true, ignoreRect: true));
controller.text = 'Guten Tag';
await tester.pump();
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
value: 'Guten Tag',
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
],
),
],
), ignoreTransform: true, ignoreRect: true));
await tester.tap(find.byKey(key));
await tester.pump();
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
value: 'Guten Tag',
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused,
],
),
],
), ignoreTransform: true, ignoreRect: true));
controller.selection = const TextSelection.collapsed(offset: 4);
await tester.pump();
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
value: 'Guten Tag',
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused,
],
),
],
), ignoreTransform: true, ignoreRect: true));
controller.text = 'Schönen Feierabend';
controller.selection = const TextSelection.collapsed(offset: 0);
await tester.pump();
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
value: 'Schönen Feierabend',
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorForwardByCharacter,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused,
],
),
],
), ignoreTransform: true, ignoreRect: true));
semantics.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