diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 4920f2339e..c23e973c36 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -3136,6 +3136,18 @@ class SemanticsConfiguration { _setFlag(SemanticsFlag.isObscured, value); } + /// Whether the platform can scroll the semantics node when the user attempts + /// to move focus to an offscreen child. + /// + /// For example, a [ListView] widget has implicit scrolling so that users can + /// easily move to the next visible set of children. A [TabBar] widget does + /// not have implicit scrolling, so that users can navigate into the tab + /// body when reaching the end of the tab bar. + bool get hasImplicitScrolling => _hasFlag(SemanticsFlag.hasImplicitScrolling); + set hasImplicitScrolling(bool value) { + _setFlag(SemanticsFlag.hasImplicitScrolling, value); + } + /// The currently selected text (or the position of the cursor) within [value] /// if this node represents a text field. TextSelection get textSelection => _textSelection; diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 563ccc035b..719394122a 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -508,6 +508,7 @@ class ScrollableState extends State with TickerProviderStateMixin key: _excludableScrollSemanticsKey, child: result, position: position, + allowImplicitScrolling: widget?.physics?.allowImplicitScrolling ?? false, ); } @@ -539,25 +540,39 @@ class _ExcludableScrollSemantics extends SingleChildRenderObjectWidget { const _ExcludableScrollSemantics({ Key key, @required this.position, + @required this.allowImplicitScrolling, Widget child }) : assert(position != null), super(key: key, child: child); final ScrollPosition position; + final bool allowImplicitScrolling; @override - _RenderExcludableScrollSemantics createRenderObject(BuildContext context) => new _RenderExcludableScrollSemantics(position: position); + _RenderExcludableScrollSemantics createRenderObject(BuildContext context) { + return new _RenderExcludableScrollSemantics( + position: position, + allowImplicitScrolling: allowImplicitScrolling, + ); + } + + @override void updateRenderObject(BuildContext context, _RenderExcludableScrollSemantics renderObject) { - renderObject.position = position; + renderObject + ..allowImplicitScrolling = allowImplicitScrolling + ..position = position; } } class _RenderExcludableScrollSemantics extends RenderProxyBox { _RenderExcludableScrollSemantics({ @required ScrollPosition position, + @required bool allowImplicitScrolling, RenderBox child, - }) : _position = position, assert(position != null), super(child) { + }) : _position = position, + _allowImplicitScrolling = allowImplicitScrolling, + assert(position != null), super(child) { position.addListener(markNeedsSemanticsUpdate); } @@ -574,12 +589,23 @@ class _RenderExcludableScrollSemantics extends RenderProxyBox { markNeedsSemanticsUpdate(); } + /// Whether this node can be scrolled implicitly. + bool get allowImplicitScrolling => _allowImplicitScrolling; + bool _allowImplicitScrolling; + set allowImplicitScrolling(bool value) { + if (value == _allowImplicitScrolling) + return; + _allowImplicitScrolling = value; + markNeedsSemanticsUpdate(); + } + @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); config.isSemanticBoundary = true; if (position.haveDimensions) { config + ..hasImplicitScrolling = allowImplicitScrolling ..scrollPosition = _position.pixels ..scrollExtentMax = _position.maxScrollExtent ..scrollExtentMin = _position.minScrollExtent; diff --git a/packages/flutter/test/widgets/custom_painter_test.dart b/packages/flutter/test/widgets/custom_painter_test.dart index 4f50cff599..444fce52cc 100644 --- a/packages/flutter/test/widgets/custom_painter_test.dart +++ b/packages/flutter/test/widgets/custom_painter_test.dart @@ -428,6 +428,7 @@ void _defineTests() { flags ..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.hasToggledState) + ..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.isToggled); TestSemantics expectedSemantics = new TestSemantics.root( children: [ @@ -473,6 +474,7 @@ void _defineTests() { flags ..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.hasCheckedState) + ..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.isChecked); expectedSemantics = new TestSemantics.root( diff --git a/packages/flutter/test/widgets/scrollable_semantics_test.dart b/packages/flutter/test/widgets/scrollable_semantics_test.dart index bfb3fc3b4e..42840aa5c6 100644 --- a/packages/flutter/test/widgets/scrollable_semantics_test.dart +++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart @@ -342,6 +342,9 @@ void main() { new TestSemantics.rootChild( children: [ new TestSemantics( + flags: [ + SemanticsFlag.hasImplicitScrolling, + ], actions: [SemanticsAction.scrollUp], children: [ new TestSemantics( diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart index 5b1c5114c7..0064558209 100644 --- a/packages/flutter/test/widgets/semantics_test.dart +++ b/packages/flutter/test/widgets/semantics_test.dart @@ -486,9 +486,9 @@ void main() { ); final List flags = SemanticsFlag.values.values.toList(); flags - ..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.hasToggledState) - ..remove(SemanticsFlag.isToggled); + ..remove(SemanticsFlag.isToggled) + ..remove(SemanticsFlag.hasImplicitScrolling); TestSemantics expectedSemantics = new TestSemantics.root( children: [ diff --git a/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart b/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart index 0cffd37ff7..db07298da4 100644 --- a/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart +++ b/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart @@ -116,6 +116,7 @@ void _tests() { children: [ new TestSemantics( id: 6, + flags: [SemanticsFlag.hasImplicitScrolling], children: [ new TestSemantics( id: 4, diff --git a/packages/flutter/test/widgets/sliver_semantics_test.dart b/packages/flutter/test/widgets/sliver_semantics_test.dart index 3671aacff0..b3f89c5464 100644 --- a/packages/flutter/test/widgets/sliver_semantics_test.dart +++ b/packages/flutter/test/widgets/sliver_semantics_test.dart @@ -384,6 +384,9 @@ void _tests() { new TestSemantics( children: [ new TestSemantics( + flags: [ + SemanticsFlag.hasImplicitScrolling, + ], children: [ new TestSemantics( label: 'Item 4',