flutter / flutter/flutter

SliverAppBar.medium and SliverAppBar.large always collapses in a NestedScrollView

Open
#162,864 3 comments 0 reactions 0 assignees View on GitHub
f: scrolling found in release: 3.27 found in release: 3.29 framework has reproducible steps P2 team-design triaged-design
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
--- | --- | ---
![Image](https://github.com/user-attachments/assets/86c6a350-4f61-472b-8194-8bb954ef6428) | ![Image](https://github.com/user-attachments/assets/fe45ff2b-565c-48db-839a-4711bf626fb6) | ![Image](https://github.com/user-attachments/assets/8ca72833-3b47-4a28-b8f0-dd17eff03527)

### 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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.