Skip to content

Commit fe04647

Browse files
authored
Include forceElevated for scrolledUnder in new SliverAppBar variants (#104536)
1 parent 2e7cea6 commit fe04647

File tree

2 files changed

+169
-2
lines changed

2 files changed

+169
-2
lines changed

packages/flutter/lib/src/material/app_bar.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
12841284
final double extraToolbarHeight = math.max(minExtent - _bottomHeight - topPadding - (toolbarHeight ?? kToolbarHeight), 0.0);
12851285
final double visibleToolbarHeight = visibleMainHeight - _bottomHeight - extraToolbarHeight;
12861286

1287-
final bool isScrolledUnder = overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent);
1287+
final bool isScrolledUnder = overlapsContent || forceElevated || (pinned && shrinkOffset > maxExtent - minExtent);
12881288
final bool isPinnedWithOpacityFade = pinned && floating && bottom != null && extraToolbarHeight == 0.0;
12891289
final double toolbarOpacity = !pinned || isPinnedWithOpacityFade
12901290
? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0)
@@ -1308,7 +1308,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
13081308
)
13091309
: flexibleSpace,
13101310
bottom: bottom,
1311-
elevation: forceElevated || isScrolledUnder ? elevation : 0.0,
1311+
elevation: isScrolledUnder ? elevation : 0.0,
13121312
scrolledUnderElevation: scrolledUnderElevation,
13131313
shadowColor: shadowColor,
13141314
surfaceTintColor: surfaceTintColor,

packages/flutter/test/widgets/nested_scroll_view_test.dart

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2552,8 +2552,175 @@ void main() {
25522552
expect(scrollStarted, 2);
25532553
expect(scrollEnded, 2);
25542554
});
2555+
2556+
testWidgets('SliverAppBar.medium collapses in NestedScrollView', (WidgetTester tester) async {
2557+
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
2558+
const double collapsedAppBarHeight = 64;
2559+
const double expandedAppBarHeight = 112;
2560+
2561+
await tester.pumpWidget(MaterialApp(
2562+
home: Scaffold(
2563+
body: NestedScrollView(
2564+
key: nestedScrollView,
2565+
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
2566+
return <Widget>[
2567+
SliverOverlapAbsorber(
2568+
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
2569+
sliver: SliverAppBar.medium(
2570+
title: const Text('AppBar Title'),
2571+
),
2572+
),
2573+
];
2574+
},
2575+
body: Builder(
2576+
builder: (BuildContext context) {
2577+
return CustomScrollView(
2578+
slivers: <Widget>[
2579+
SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
2580+
SliverFixedExtentList(
2581+
itemExtent: 50.0,
2582+
delegate: SliverChildBuilderDelegate(
2583+
(BuildContext context, int index) => ListTile(title: Text('Item $index')),
2584+
childCount: 30,
2585+
),
2586+
),
2587+
],
2588+
);
2589+
},
2590+
),
2591+
),
2592+
),
2593+
));
2594+
2595+
// There are two widgets for the title.
2596+
final Finder expandedTitle = find.text('AppBar Title').last;
2597+
final Finder expandedTitleClip = find.ancestor(
2598+
of: expandedTitle,
2599+
matching: find.byType(ClipRect),
2600+
);
2601+
2602+
// Default, fully expanded app bar.
2603+
expect(nestedScrollView.currentState?.outerController.offset, 0);
2604+
expect(nestedScrollView.currentState?.innerController.offset, 0);
2605+
expect(find.byType(SliverAppBar), findsOneWidget);
2606+
expect(appBarHeight(tester), expandedAppBarHeight);
2607+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
2608+
2609+
// Scroll the expanded app bar partially out of view.
2610+
final Offset point1 = tester.getCenter(find.text('Item 5'));
2611+
await tester.dragFrom(point1, const Offset(0.0, -45.0));
2612+
await tester.pump();
2613+
expect(nestedScrollView.currentState?.outerController.offset, 45.0);
2614+
expect(nestedScrollView.currentState?.innerController.offset, 0.0);
2615+
expect(find.byType(SliverAppBar), findsOneWidget);
2616+
expect(appBarHeight(tester), expandedAppBarHeight - 45);
2617+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight - 45);
2618+
2619+
// Scroll so that it is completely collapsed.
2620+
await tester.dragFrom(point1, const Offset(0.0, -555.0));
2621+
await tester.pump();
2622+
expect(nestedScrollView.currentState?.outerController.offset, 48.0);
2623+
expect(nestedScrollView.currentState?.innerController.offset, 552.0);
2624+
expect(find.byType(SliverAppBar), findsOneWidget);
2625+
expect(appBarHeight(tester), collapsedAppBarHeight);
2626+
expect(tester.getSize(expandedTitleClip).height, 0);
2627+
2628+
// Scroll back to fully expanded.
2629+
await tester.dragFrom(point1, const Offset(0.0, 600.0));
2630+
await tester.pump();
2631+
expect(nestedScrollView.currentState?.outerController.offset, 0);
2632+
expect(nestedScrollView.currentState?.innerController.offset, 0);
2633+
expect(find.byType(SliverAppBar), findsOneWidget);
2634+
expect(appBarHeight(tester), expandedAppBarHeight);
2635+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
2636+
});
2637+
2638+
testWidgets('SliverAppBar.large collapses in NestedScrollView', (WidgetTester tester) async {
2639+
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
2640+
const double collapsedAppBarHeight = 64;
2641+
const double expandedAppBarHeight = 152;
2642+
2643+
await tester.pumpWidget(MaterialApp(
2644+
home: Scaffold(
2645+
body: NestedScrollView(
2646+
key: nestedScrollView,
2647+
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
2648+
return <Widget>[
2649+
SliverOverlapAbsorber(
2650+
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
2651+
sliver: SliverAppBar.large(
2652+
title: const Text('AppBar Title'),
2653+
forceElevated: innerBoxIsScrolled,
2654+
),
2655+
),
2656+
];
2657+
},
2658+
body: Builder(
2659+
builder: (BuildContext context) {
2660+
return CustomScrollView(
2661+
slivers: <Widget>[
2662+
SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
2663+
SliverFixedExtentList(
2664+
itemExtent: 50.0,
2665+
delegate: SliverChildBuilderDelegate(
2666+
(BuildContext context, int index) => ListTile(title: Text('Item $index')),
2667+
childCount: 30,
2668+
),
2669+
),
2670+
],
2671+
);
2672+
},
2673+
),
2674+
),
2675+
),
2676+
));
2677+
2678+
// There are two widgets for the title.
2679+
final Finder expandedTitle = find.text('AppBar Title').last;
2680+
final Finder expandedTitleClip = find.ancestor(
2681+
of: expandedTitle,
2682+
matching: find.byType(ClipRect),
2683+
);
2684+
2685+
// Default, fully expanded app bar.
2686+
expect(nestedScrollView.currentState?.outerController.offset, 0);
2687+
expect(nestedScrollView.currentState?.innerController.offset, 0);
2688+
expect(find.byType(SliverAppBar), findsOneWidget);
2689+
expect(appBarHeight(tester), expandedAppBarHeight);
2690+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
2691+
2692+
// Scroll the expanded app bar partially out of view.
2693+
final Offset point1 = tester.getCenter(find.text('Item 5'));
2694+
await tester.dragFrom(point1, const Offset(0.0, -45.0));
2695+
await tester.pump();
2696+
expect(nestedScrollView.currentState?.outerController.offset, 45.0);
2697+
expect(nestedScrollView.currentState?.innerController.offset, 0);
2698+
expect(find.byType(SliverAppBar), findsOneWidget);
2699+
expect(appBarHeight(tester), expandedAppBarHeight - 45);
2700+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight - 45);
2701+
2702+
// Scroll so that it is completely collapsed.
2703+
await tester.dragFrom(point1, const Offset(0.0, -555.0));
2704+
await tester.pump();
2705+
expect(nestedScrollView.currentState?.outerController.offset, 88.0);
2706+
expect(nestedScrollView.currentState?.innerController.offset, 512.0);
2707+
expect(find.byType(SliverAppBar), findsOneWidget);
2708+
expect(appBarHeight(tester), collapsedAppBarHeight);
2709+
expect(tester.getSize(expandedTitleClip).height, 0);
2710+
2711+
// Scroll back to fully expanded.
2712+
await tester.dragFrom(point1, const Offset(0.0, 600.0));
2713+
await tester.pump();
2714+
expect(nestedScrollView.currentState?.outerController.offset, 0);
2715+
expect(nestedScrollView.currentState?.innerController.offset, 0);
2716+
expect(find.byType(SliverAppBar), findsOneWidget);
2717+
expect(appBarHeight(tester), expandedAppBarHeight);
2718+
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
2719+
});
25552720
}
25562721

2722+
double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
2723+
25572724
class TestHeader extends SliverPersistentHeaderDelegate {
25582725
const TestHeader({ this.key });
25592726
final Key? key;

0 commit comments

Comments
 (0)