diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index 0b77a2e4804fd..444ea8856261b 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -279,6 +279,7 @@ class SemanticsFlag { static const int _kIsLiveRegionIndex = 1 << 15; static const int _kHasToggledStateIndex = 1 << 16; static const int _kIsToggledIndex = 1 << 17; + static const int _kHasImplicitScrollingIndex = 1 << 18; const SemanticsFlag._(this.index); @@ -465,6 +466,15 @@ class SemanticsFlag { /// * [SemanticsFlag.hasToggledState], which enables a toggled state. static const SemanticsFlag isToggled = const SemanticsFlag._(_kIsToggledIndex); + /// 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. + static const SemanticsFlag hasImplicitScrolling = const SemanticsFlag._(_kHasImplicitScrollingIndex); + /// The possible semantics flags. /// /// The map's key is the [index] of the flag and the value is the flag itself. @@ -487,6 +497,7 @@ class SemanticsFlag { _kIsLiveRegionIndex: isLiveRegion, _kHasToggledStateIndex: hasToggledState, _kIsToggledIndex: isToggled, + _kHasImplicitScrollingIndex: hasImplicitScrolling, }; @override @@ -528,6 +539,8 @@ class SemanticsFlag { return 'SemanticsFlag.hasToggledState'; case _kIsToggledIndex: return 'SemanticsFlag.isToggled'; + case _kHasImplicitScrollingIndex: + return 'SemanticsFlag.hasImplicitScrolling'; } return null; } diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 9c7f9bef0e02f..cdba27a589ce4 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -92,7 +92,8 @@ enum Flag { IS_IMAGE(1 << 14), IS_LIVE_REGION(1 << 15), HAS_TOGGLED_STATE(1 << 16), - IS_TOGGLED(1 << 17); + IS_TOGGLED(1 << 17), + HAS_IMPLICIT_SCROLLING(1 << 18); Flag(int value) { this.value = value; @@ -256,8 +257,15 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { || object.hasAction(Action.SCROLL_RIGHT) || object.hasAction(Action.SCROLL_DOWN)) { result.setScrollable(true); // This tells Android's a11y to send scroll events when reaching the end of - // the visible viewport of a scrollable. - result.setClassName("android.widget.ScrollView"); + // the visible viewport of a scrollable, unless the node itself does not + // allow implicit scrolling - then we leave the className as view.View. + if (object.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)) { + if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_RIGHT)) { + result.setClassName("android.widget.HorizontalScrollView"); + } else { + result.setClassName("android.widget.ScrollView"); + } + } // TODO(ianh): Once we're on SDK v23+, call addAction to // expose AccessibilityAction.ACTION_SCROLL_LEFT, _RIGHT, // _UP, and _DOWN when appropriate.