Fix semantics for floating pinned sliver app bar (#35574)
diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 169b98c..ce1deb1 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
@@ -476,7 +476,7 @@ } else { _effectiveScrollOffset = constraints.scrollOffset; } - final bool overlapsContent = _effectiveScrollOffset < constraints.scrollOffset; + final bool overlapsContent = _effectiveScrollOffset <= constraints.scrollOffset; excludeFromSemanticsScrolling = overlapsContent; layoutChild(_effectiveScrollOffset, maxExtent, overlapsContent: overlapsContent); _childPosition = updateGeometry();
diff --git a/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart b/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart index 84e2e26..d3bab38 100644 --- a/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart +++ b/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart
@@ -6,7 +6,203 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import 'semantics_tester.dart'; + void main() { + testWidgets('Sliver appbars - floating and pinned - correct semantics', (WidgetTester tester) async { + await tester.pumpWidget( + Localizations( + locale: const Locale('en', 'us'), + delegates: const <LocalizationsDelegate<dynamic>>[ + DefaultWidgetsLocalizations.delegate, + DefaultMaterialLocalizations.delegate, + ], + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: CustomScrollView( + slivers: <Widget>[ + const SliverAppBar( + title: Text('Hello'), + pinned: true, + floating: true, + expandedHeight: 200.0, + ), + SliverFixedExtentList( + itemExtent: 100.0, + delegate: SliverChildBuilderDelegate( + (BuildContext _, int index) { + return Container( + height: 100.0, + color: index % 2 == 0 ? Colors.red : Colors.yellow, + child: Text('Tile $index'), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + + + final SemanticsTester semantics = SemanticsTester(tester); + + TestSemantics expectedSemantics = TestSemantics.root( + children: <TestSemantics>[ + TestSemantics.rootChild( + textDirection: TextDirection.ltr, + children: <TestSemantics>[ + TestSemantics( + children: <TestSemantics>[ + TestSemantics( + thickness: 4, + children: <TestSemantics>[ + TestSemantics( + label: 'Hello', + elevation: 4, + flags: <SemanticsFlag>[SemanticsFlag.isHeader, SemanticsFlag.namesRoute], + textDirection: TextDirection.ltr, + ), + ], + ), + TestSemantics( + actions: <SemanticsAction>[SemanticsAction.scrollUp], + flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], + scrollIndex: 0, + children: <TestSemantics>[ + TestSemantics( + label: 'Tile 0', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 1', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 2', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 3', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 4', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 5', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 6', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + ], + ) + ], + ), + ], + ), + ], + ); + expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true)); + + await tester.fling(find.text('Tile 2'), const Offset(0, -600), 2000); + await tester.pumpAndSettle(); + + expectedSemantics = TestSemantics.root( + children: <TestSemantics>[ + TestSemantics.rootChild( + textDirection: TextDirection.ltr, + children: <TestSemantics>[ + TestSemantics( + children: <TestSemantics>[ + TestSemantics( + children: <TestSemantics>[ + TestSemantics( + label: 'Hello', + flags: <SemanticsFlag>[SemanticsFlag.isHeader, SemanticsFlag.namesRoute], + textDirection: TextDirection.ltr, + ), + ], + ), + TestSemantics( + actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown], + flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], + scrollIndex: 10, + children: <TestSemantics>[ + TestSemantics( + label: 'Tile 7', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 8', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 9', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 10', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 11', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 12', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 13', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 14', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 15', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 16', + textDirection: TextDirection.ltr, + ), + TestSemantics( + label: 'Tile 17', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + TestSemantics( + label: 'Tile 18', + textDirection: TextDirection.ltr, + flags: <SemanticsFlag>[SemanticsFlag.isHidden], + ), + ], + ) + ], + ), + ], + ), + ], + ); + expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true)); + }); + testWidgets('Sliver appbars - floating and pinned - second app bar stacks below', (WidgetTester tester) async { final ScrollController controller = ScrollController(); await tester.pumpWidget(