SliverAppBar.medium and SliverAppBar.large always collapses in a NestedScrollView
- Dominant language
- Dart
- Stars
- 179k
- Forks
- 31.1k
- PR merge metrics
- PR metrics pending
Description
### Steps to reproduce
Take the **widgets.NestedScrollView.1** example from the [NestedScrollView docs](https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html), change the `childCount` on L116 to a lower number, so the scrollable content is not longer than the screen height and remove the `expandedHeight` on L45.
_Everything works as expected so far. Now:_
1. Replace `SliverAppBar` with `SliverAppBar.medium` or `SliverAppBar.large` in this modified example
### Expected results
The medium or large SliverAppBar should behave the same in this configuration, as the small SliverAppBar, and if there's not enough content to scroll, the NestedScrollView [should not scroll at all](https://github.com/user-attachments/assets/96539542-ab10-49c0-85f8-75ae7b08b478).
### Actual results
There's an unnecessary scroll, probably due to the medium/large's AppBar collapse animation. Observed on web, Android and iOS.
On all platforms the title also disappears. Probably related to a different problem with the title transition animation being impferfect, but whatever the reason, the result is that it looks quite buggy.
**Either the problem is with the SliverAppBar itself, or the presented example in the documentation does not represent the intended use of these widgets.**
### Code sample
Code sample
```dart
import 'package:flutter/material.dart';
/// Flutter code sample for [NestedScrollView].
void main() => runApp(const NestedScrollViewExampleApp());
class NestedScrollViewExampleApp extends StatelessWidget {
const NestedScrollViewExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: NestedScrollViewExample(),
);
}
}
class NestedScrollViewExample extends StatelessWidget {
const NestedScrollViewExample({super.key});
@override
Widget build(BuildContext context) {
final List tabs = ['Tab 1', 'Tab 2'];
return DefaultTabController(
length: tabs.length, // This is the number of tabs.
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return [
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar.medium(
title:
const Text('Books'), // This is the title in the app bar.
pinned: true,
// The "forceElevated" property causes the SliverAppBar to show
// a shadow. The "innerBoxIsScrolled" parameter is true when the
// inner scroll view is scrolled beyond its "zero" point, i.e.
// when it appears to be scrolled below the SliverAppBar.
// Without this, there are cases where the shadow would appear
// or not appear inappropriately, because the SliverAppBar is
// not actually aware of the precise position of the inner
// scroll views.
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey(name),
slivers: [
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return ListTile(
title: Text('Item $index'),
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 3,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
```
### Screenshots or Video
Screenshots / Video demonstration
Web | iOS | Android
--- | --- | ---
 |  | 
### Flutter Doctor output
Doctor output
Issue observed in the current's dartpad.dev on 3.27.3, as well as on my local machine with 3.27.1
```console
[✓] Flutter (Channel stable, 3.27.1, on macOS 14.6.1 23G93 darwin-arm64, locale en-CZ)
• Flutter version 3.27.1 on channel stable at /opt/homebrew/Caskroom/flutter/3.27.1/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 17025dd882 (8 weeks ago), 2024-12-17 03:23:09 +0900
• Engine revision cb4b5fff73
• Dart version 3.6.0
• DevTools version 2.40.2
```
Contributor guide
Assessment
This issue has not been assessed yet.