Reputation: 63
I'm new to flutter and I want to implement something like this: (klook app)
It's basically a button being shown when the user scrolls a bit.
I tried different things with a SliverAppBar and SliverStickyHeader, but I can't make it work like this. I also played with Opacity and Visibility but it moves my hole view and does not 'overlap' my banner/searchby widget.
My code so far:
class _ExplorePageState extends State<ExplorePage> {
ScrollController _scrollController;
bool lastStatus = true;
_scrollListener() {
if (isShrink != lastStatus) {
print("listen");
setState(() {
lastStatus = isShrink;
});
}
}
bool get isShrink {
return _scrollController.hasClients &&
_scrollController.offset > (400 - kToolbarHeight);
}
@override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener());
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverStickyHeader(
header: Visibility(
child: Container(
color: Colors.red,
height: isShrink ? 100 : 0,
child: Text('Header 1'),
),
visible: isShrink ? true : false,
maintainState: true,
maintainSize: true,
maintainAnimation: true,
),
sliver: SliverList(
delegate: SliverChildListDelegate(
[
BannerWidget(),
ButtonWidget(),
],
),
),
),
],
),
);
}
}
The BannerWidget and ButtomWidget are two Containers similar to the app shown above.
I hope you can help me or tell me maybe what this behaviour is called. Thank you!
Upvotes: 1
Views: 2236
Reputation: 9883
If you're ok with using CustomScrollView
, you could use SliverPersistentHeader
with your own delegate. It will allow you to access current header scroll state and make your own layout depending on how much space you have left.
const double _kSearchHeight = 50.0;
const double _kHeaderHeight = 250.0;
class _ExplorePageState extends State<ExplorePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: DelegateWithSearchBar(),
pinned: true,
),
SliverList(
delegate: SliverChildListDelegate(
[
for (int i = 0; i < 4; i++)
Container(
height: 200,
child: Text('test'),
color: Colors.black26
),
],
),
)
],
),
),
);
}
}
class DelegateWithSearchBar extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final showSearchBar = shrinkOffset > _kHeaderHeight - _kSearchHeight;
return Stack(
children: <Widget>[
AnimatedOpacity(
opacity: !showSearchBar ? 1 : 0,
duration: Duration(milliseconds: 100),
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('xxx'),
fit: BoxFit.cover
)
),
height: constraints.maxHeight,
child: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 20, bottom: 20),
alignment: Alignment.bottomLeft,
child: Text(
'Sample Text',
style: TextStyle(color: Colors.white, fontSize: 22)
),
),
),
);
}
),
),
AnimatedOpacity(
opacity: showSearchBar ? 1 : 0,
duration: Duration(milliseconds: 100),
child: Container(
height: _kSearchHeight,
color: Colors.white,
alignment: Alignment.center,
child: Text('search bar')
),
),
],
);
}
@override
bool shouldRebuild(SliverPersistentHeaderDelegate _) => true;
@override
double get maxExtent => _kHeaderHeight;
@override
double get minExtent => _kSearchHeight;
}
Upvotes: 3