Reputation: 185
Background
I have two SliverPersistentHeaders
. One is located inside NestedScrollView
and has a TabBar
inside of it which should be always visible on scroll, so I made it pinned. Also I have another SliverPersistentHeader
which is located inside CustomScrollView
in one of the tabs. It should be always visible on scroll when this tab is opened, so I made it pinned also.
Here is what I have visually. The SliverPersistentHeaders
I write about are white and blue:
The Problem
I expect both of the SliverPersistentHeaders
to be pinned and not go up on scroll. But it turns out the SliverPersistentHeader
inside CustomScrollView
scrolls behind the SliverPersistentHeader
in NestedScrollView
and only then gets pinned like so:
https://s2.gifyu.com/images/untitled446dfde99b45261c.gif
Let's jump to the code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
var _tabController;
@override
void initState() {
_tabController = TabController(
length: 2,
vsync: this,
);
super.initState();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: NestedScrollView(
physics: BouncingScrollPhysics(),
floatHeaderSlivers: true,
headerSliverBuilder: (context, value) {
return [
// here we have the first SliverPersistentHeader
// with TabBar as content
// inside of NestedScrollView
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: _TabBarAsSliverPersistentHeader(_tabController),
),
];
},
body:
// BlocProvider might be here in the real project...
CustomScrollView(
slivers: [
// here we have the second SliverPersistentHeader
// inside of CustomScrollView
// it has unexpected behaviour as shown in gif above
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: PersistentHeaderDateFilter(),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(
height: 100,
margin: EdgeInsets.all(10),
color: Colors.red,
),
childCount: 10,
),
)
],
),
),
),
);
}
}
class _TabBarAsSliverPersistentHeader extends SliverPersistentHeaderDelegate {
final TabController _tabController;
_TabBarAsSliverPersistentHeader(this._tabController);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
// TODO: implement build
return Container(
// color: Theme.of(context).backgroundColor,
child: Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0,
),
),
),
),
),
Container(
color: Colors.transparent,
child: TabBar(
isScrollable: false,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
width: 2,
color: Colors.blue,
),
),
indicatorSize: TabBarIndicatorSize.tab,
indicatorWeight: 2,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
tabs: [
Tab(text: 'by rating'),
Tab(text: 'by date'),
],
controller: _tabController,
),
),
],
),
);
}
@override
double get maxExtent => 46.0;
@override
double get minExtent => 46.0;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
class PersistentHeaderDateFilter extends SliverPersistentHeaderDelegate {
PersistentHeaderDateFilter();
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
// TODO: implement build
return Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.blue.shade200,
border: Border(
bottom: BorderSide(
color: Colors.green,
width: 1.0,
),
),
),
alignment: Alignment.center,
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 20),
// shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 10,
// ignore: missing_return
itemBuilder: (context, index) {
return Container(
color: Colors.amber,
margin: EdgeInsets.all(5),
width: 50,
child: Text(index.toString()),
);
},
),
);
}
@override
double get maxExtent => 110.0;
@override
double get minExtent => 110.0;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
What I tried
So far I have experimented with SliverOverlapAbsorber
and SliverOverlapInjector
which are mentioned in flutter doc of NestedScrollView
but without any good results. Moving TabBar
to SliverAppBar
bottom property also was not successful.
Upvotes: 3
Views: 2715
Reputation: 13
I had a similar problem of having a scroll view inside of the body of a nestedScrollView. This answer is very late but NestedScrollView has a setting called floatHeaderSilvers that might have not been available during the time of posting.
So
NestedScrollView(
floatHeaderSlivers: True,
headerSliverBuilder: etc...,
body: etc...
);
Hopefully this helps anyone else who is searching for this question in the future.
Upvotes: 0
Reputation: 126
I had very similar problem with:
SliverPersistentHeader was scrolled and then pinned under SliverAppBar. SliverPinnedOverlapInjector together with SliverOverlapAbsorber helped.
Upvotes: 0
Reputation: 61
What worked for me:
I've wrapped the whole SliverPersistentHeader (my _buildTabNavigation())
in SliverOverlapAbsorver
:
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: _buildTabNavigation(),
),
Doing this, your list will be stuck under the TabBar. Then you can wrap your NestedScrollView
body in a Container and add a margin top.
Something like this:
return NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) {
return [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: _buildTabNavigation(),
),
];
},
body: Container(
margin: EdgeInsets.only(top: 50),
child: TabBarView(
controller: _tabController,
children: _tabs,
),
),
);
Upvotes: 1