A NestedScrollView that supports outer scroller to top overscroll.
Web demo 👉 Click Here
Problem: NestedScrollView does not support outer scroller to top overscroll, so its SliverAppBar cannot be stretched.
Related issue: flutter/flutter#54059
Fixed by:
-
Override the applyUserOffset method of _NestedScrollCoordinator to allow over-scroll the top of _outerPosition.
-
Override the unnestOffset, nestOffset, _getMetrics methods of _NestedScrollCoordinator to fix the mapping between _innerPosition and _outerPosition to _NestedScrollPosition (Coordinator).
For more information, see:
example/lib/main.dart
lib/src/custom_nested_scroll_view.dart
dependencies:
...
custom_nested_scroll_view:
git:
url: https://github.com/idootop/custom_nested_scroll_view.git
# Which branch to use is based on your local flutter version
ref: flutter-3.0 # flutter-2.x flutter-3.0 flutter-3.4-pre
Git branch | Supported flutter versions |
---|---|
main | >=3.4.0-27.0.pre |
flutter-3.4-pre | >=3.4.0-17.0.pre <3.4.0-27.0.pre |
flutter-3.0 | >=2.12.0-4.0.pre <3.4.0-17.0.pre |
flutter-2.x | <2.12.0-4.0.pre |
import 'package:flutter/material.dart';
import 'package:custom_nested_scroll_view/custom_nested_scroll_view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example',
home: Home(),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: CustomNestedScrollView(
overscrollType: CustomOverscroll.outer,
// !important
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
headerSliverBuilder: (context, innerScrolled) => <Widget>[
MySliverAppBar(),
],
body: TabBarView(
children: [
CustomScrollView(
// !important
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
slivers: <Widget>[
TopOverlapInjector(),
// scroll view
SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
(_, index) => ListTile(
key: Key('$index'),
title: Center(
child: Text('ListTile ${index + 1}'),
),
),
childCount: 30,
),
itemExtent: 50,
),
],
),
CustomScrollView(
physics: NeverScrollableScrollPhysics(),
slivers: <Widget>[
TopOverlapInjector(),
// some widget
SliverFillRemaining(
child: Center(
child: Text('Test'),
),
),
],
),
],
),
),
),
);
}
}
class TopOverlapInjector extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) => CustomSliverOverlapInjector(
overscrollType: CustomOverscroll.outer,
handle: CustomNestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
);
}
}
class MySliverAppBar extends StatelessWidget {
///Header collapsed height
final minHeight = 120.0;
///Header expanded height
final maxHeight = 400.0;
final tabBar = TabBar(
tabs: <Widget>[Text('Tab1'), Text('Tab2')],
);
@override
Widget build(BuildContext context) {
final topHeight = MediaQuery.of(context).padding.top;
return CustomSliverOverlapAbsorber(
overscrollType: CustomOverscroll.outer,
handle: CustomNestedScrollView.sliverOverlapAbsorberHandleFor(
context,
),
sliver: SliverAppBar(
pinned: true,
stretch: true,
toolbarHeight: minHeight - tabBar.preferredSize.height - topHeight,
collapsedHeight: minHeight - tabBar.preferredSize.height - topHeight,
expandedHeight: maxHeight - topHeight,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Center(child: Text('Example')),
stretchModes: <StretchMode>[
StretchMode.zoomBackground,
StretchMode.blurBackground,
],
background: Image.network(
'https://pic1.zhimg.com/80/v2-fc35089cfe6c50f97324c98f963930c9_720w.jpg',
fit: BoxFit.cover,
),
),
bottom: tabBar,
),
);
}
}
For more examples, see https://github.com/idootop/scroll_master(Highly recommended)
Thanks to fluttercandies's extended_nested_scroll_view.