Reputation: 13
I have a layout with three Slivers:
SliverAppBar
(not pinned).SliverAppBar
(pinned, containing a SearchBar
).SliverList
.The issue is with the second SliverAppBar
. I want it to be pinned below the status bar, respecting the safe area, but currently, it goes under the status bar, as shown in the second image:
The first drawing represents the default state on launch.
When scrolling, the first SliverAppBar
disappears as expected, and the second SliverAppBar
(with the SearchBar
) gets pinned at the top.
However, I want the second SliverAppBar
to stop just below the status bar instead of going behind it.
Wrapping the entire CustomScrollView
in a SafeArea
:
SliverAppBar
, which I don't want.Wrapping only the second SliverAppBar
in a SafeArea
or SliverSafeArea
:
SliverAppBars
, which is not the desired behavior.Dynamic padding using a SliverPersistentHeader
and working with its shrinkOffset
:
shrinkOffset
starts growing only when the header reaches the top of the screen, so it doesn't solve the problem.Using only 1 SliverAppBar
with a flexibleSpace
and a bottom
:
class MyPage extends StatefulWidget {
const MyPage({super.key});
@override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
late ScrollController _scrollController;
@override
void initState() {
_scrollController = ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PrimaryScrollController(
controller: _scrollController,
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: false,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://picsum.photos/250?image=9',
fit: BoxFit.cover,
),
),
),
const SliverAppBar(
pinned: true,
flexibleSpace: Padding(
padding: EdgeInsets.only(top: 12.0),
child: SearchBar(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 20,
),
),
],
),
),
);
}
}
How can I achieve this behavior? Is there a proper way to pin a SliverAppBar
while respecting the safe area for just that Sliver?
Upvotes: 0
Views: 75
Reputation: 74
Let both the shrinkage area and the search bar be implemented with SliverPersistentHeader:
Widget buildPage1() {
MediaQueryData mediaQuery = MediaQuery.of(context);
var min = mediaQuery.viewPadding.top;
final max = 200.0;
return Scaffold(
body: CustomScrollView(
slivers: [
NSliverPersistentHeaderBuilder(
pinned: true,
min: min,
max: max,
builder: (BuildContext context, double shrinkOffset, bool overlapsContent) {
final double opacity = 1 - (shrinkOffset / (max - min));
return Container(
alignment: Alignment.bottomCenter,
decoration: BoxDecoration(
color: Colors.blue,
image: DecorationImage(
opacity: opacity,
image: ExtendedNetworkImageProvider(
'https://picsum.photos/250?image=9',
),
fit: BoxFit.cover,
),
),
// child: Text(
// "Pinned Header ${{
// "shrinkOffset": shrinkOffset.toStringAsFixed(2),
// }} ",
// style: TextStyle(color: Colors.white, fontSize: 20),
// ),
// child: SearchBar(
// hintText: "search",
// ),
);
},
),
NSliverPersistentHeaderBuilder(
pinned: true,
min: 40,
max: 40,
builder: (BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.green,
),
child: SearchBar(
backgroundColor: MaterialStateProperty.all(Colors.white),
hintText: "search",
),
);
},
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('Item #$index'),
);
},
childCount: 20,
),
),
],
),
);
}
Upvotes: 0