have slivers start behind SliverAppBar - flutter

I'm looking for a way to start my slivers start behind my SliverAppBar. The standard way (not slivers) would be to use extendBody: true, and extendBodyBehindAppBar: true,in the Scaffold widget. This doesn't work with slivers because the appbar is part of the scaffold body I suppose. Here's minimum example.. I'd like the Sliverlist to start being the appbar....
import 'package:flutter/material.dart';
void main() => runApp(SilverAppBarExample());
class SilverAppBarExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: IconButton(
icon: Icon(Icons.filter_1),
onPressed: () {
// Do something
}
),
floating: true,
pinned: true,
snap: true,
elevation: 50,
backgroundColor: Colors.transparent,
),
new SliverList(
delegate: new SliverChildListDelegate(_buildList(50))
),
],
),
),
);
}
List _buildList(int count) {
List<Widget> listItems = List();
for (int i = 0; i < count; i++) {
listItems.add(new Padding(padding: new EdgeInsets.all(20.0),
child: new Text(
'Item ${i.toString()}',
style: new TextStyle(fontSize: 25.0)
)
));
}
return listItems;
}
}

I solved it using NestedScrollView without SliverOverlapInjector
So use it as normal except remove this part
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
and make sure your app bar is pinned, and not floating

Related

Flutter AppBar make bottom widget slowly fade on scroll

I'm trying to achieve a behavior that is similar to the SliverAppBar coupled with a TabBar, where the AppBar disappears on scroll but the TabBar stays, but in reverse, i.e. The TabBar slowly disappears but the AppBar stays visible. The TabBar (or any other bottom widget) should also reappear when scrolling up again.
I couldn't manage to achieve this behavior with the SliverAppBar, does anyone have an idea how this could be achieved?
This is how I tried, but I don't know how to reverse the behavior or the actual AppBar and the bottom widget.
import 'package:flutter/material.dart';
void main() {
runApp(const TestWidget());
}
class TestWidget extends StatelessWidget {
const TestWidget({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(
useMaterial3: true,
),
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
floating: true,
pinned: true,
snap: true,
title: const Text("My App Title"),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: SizedBox(
height: kToolbarHeight,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 25,
itemBuilder: (context, index) => FilterChip(
label: Text("Chip $index"),
onSelected: (_) {},
),
),
),
),
),
SliverList(
delegate: SliverChildListDelegate(
List.generate(
100,
(index) => ListTile(
title: Text("Item $index"),
),
),
),
)
],
),
),
);
}
}
Thanks in advance!
Class for ScrollListener
class ScrollListener extends ChangeNotifier {
double bottom = 0;
double _last = 0;
ScrollListener.initialise(ScrollController controller, [double height = 56]) {
controller.addListener(() {
final current = controller.offset;
bottom += _last - current;
if (bottom <= -height) bottom = -height;
if (bottom >= 0) bottom = 0;
_last = current;
if (bottom <= 0 && bottom >= -height) notifyListeners();
});
}
}
Use of ScrollListener to Hide/Show BottomBar:
class HomePage extends StatelessWidget {
final ScrollController _controller = ScrollController();
final double bottomNavBarHeight = 56;
late final ScrollListener _model;
HomePage({super.key}) {
_model = ScrollListener.initialise(_controller);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBuilder(
animation: _model,
builder: (context, child) {
return Stack(
children: [
ListView.builder(
controller: _controller,
itemCount: 20,
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
Positioned(
left: 0,
right: 0,
bottom: _model.bottom,
child: bottomBar(),
),
],
);
},
),
);
}
Widget bottomBar() {
return SizedBox(
height: bottomNavBarHeight,
child: BottomNavigationBar(
backgroundColor: Colors.amber[800],
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
}
more reference also checkout this link

flutter - Hide sliverappbar when scrolling starts

I want to hide the app bar and the widget at the bottom right away as soon as the screen scrolls. Also, I want to show what I hid as soon as I scroll to the top.
Also, because I have to put a widget under the Appbar, the widget is located in the bottom, and I put the tabbar under the Sliver Persistent Header widget.
The way I did it is to scroll as high as the app bar to hide it.
The code I wrote
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
bottom: false,
child: NestedScrollView(
key: _nestedScrollViewGlobalKey,
controller: _scrollController,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.red.withOpacity(0.5),
elevation: 0.0,
title: Text('123123213'),
floating: true,
pinned: false,
snap: true,
// forceElevated: innerBoxIsScrolled,
expandedHeight: (BDSStyle.appBarHeight * 2),
toolbarHeight: (BDSStyle.appBarHeight * 2),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: Container(
height: BDSStyle.appBarHeight,
color: Colors.yellow.withOpacity(0.4),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: TabBarDelegate(_tabController),
),
];
},
body: _tabBarView(),
),
),
);
}
class TabBarDelegate extends SliverPersistentHeaderDelegate {
final TabController tabController;
const TabBarDelegate(this.tabController);
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return BDSTabBar(
controller: tabController,
tabs: List.generate(PlaceCategory.values.length, (index) {
return PlaceCategory.values[index].koreanName;
}),
isScrollable: true,
);
}
#override
double get maxExtent => BDSStyle.tabBarHeight;
#override
double get minExtent => BDSStyle.tabBarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
video link
Code Execution Image
The way I want to implement it
May be this code may help you.
DefaultTabController(
length: 2,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text("Application"),
floating: true,
pinned: true,
snap: true,
bottom: TabBar(
tabs: <Tab>[
Tab(text: "T"),
Tab(text: "B"),
], // <-- total of 2 tabs
),
),
];
},
body: TabBarView(
children: <Widget>[
Center(
child: Text(
'T Tab',
style: TextStyle(fontSize: 30),
)),
Center(
child: Text(
'B Tab',
style: TextStyle(fontSize: 30),
)),
],
),
),
),
)

Make SliverAppBar scrollable when TabBarView child is scrolled

I have home screen with bottom navigation consist of two items which all have ListView inside with infinite list and I want SliverAppBar to be scrollable when user scrolls in one of the list.
Here is what I have so far
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: new Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
snap: false,
floating: false,
pinned: true,
expandedHeight: 160.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Title'),
),
),
SliverFillRemaining(
child: TabBarView(
children: <Widget>[Items(), Activities()], //THESE HAVE LIST VIEW IN EACH
),
)
],
),
));
}
}
And here is code of one of TabBarView children.
class Items extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<Widget> itemsWidgets = List.from(getItemsList()
.map((Item item) => createItemWidget(context, item)));
return Scaffold(
body: Center(
child: ListView(
children: itemsWidgets,
),
),
);
}
ListTile createItemWidget(BuildContext context, Item item) {
return new ListTile(
title: Text(item.sender.name),
subtitle: Text('10:30 am'),
);
}
}
How can SilverAppBar be scrollable when user scrolls in one of the list? Any help/suggestion will be appreciated.
Use NestedScrollView. There is an example in the api

Multiple SliverAppBar in a CustomScrollView

I require to have multiple SliverAppBar, each with its own SliverList in a single view. Currently only the first SliverAppBar is responding correctly.
I have of course, done extended searching on SO and Google, but have not found a solution yet!
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details'),
),
body: CustomScrollView(
slivers: <Widget>[
new SliverAppBar(
floating: true,
automaticallyImplyLeading: false,
title: Text('1'),
),
new SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Text 1')),
childCount: 20,
),
),
new SliverAppBar(
automaticallyImplyLeading: false,
title: Text('2'),
floating: true,
),
new SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Text 2')),
childCount: 20,
),
),
],
),
);
}
If you do scroll, I expect to see the title "2" floating as well, when you are scrolling the list.
This seems to be a limitation of CustomScrollView. It's possible to work around that, but it's very tricky, unless you have fixed-height items and fixed-length lists. If so, you can assume the height of your whole session (AppBar height + height of each list item).
Take a look:
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
static const double listItemHeight = 50;
static const int listItemCount = 15;
static const double sessionHeight = kToolbarHeight + (listItemCount * listItemHeight);
int floatingAppBarIndex;
ScrollController controller;
#override
void initState() {
super.initState();
floatingAppBarIndex = 0;
controller = ScrollController()..addListener(onScroll);
}
void onScroll() {
double scrollOffset = controller.offset;
int sessionsScrolled = 0;
while (scrollOffset > sessionHeight) {
scrollOffset -= sessionHeight;
sessionsScrolled++;
}
if (sessionsScrolled != floatingAppBarIndex) {
setState(() {
floatingAppBarIndex = sessionsScrolled;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details'),
),
body: CustomScrollView(
controller: controller,
slivers: <Widget>[
new SliverAppBar(
floating: floatingAppBarIndex == 0,
automaticallyImplyLeading: false,
title: Text('1'),
),
new SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SizedBox(
height: listItemHeight,
child: ListTile(
title: Text('Text 1'),
),
);
},
childCount: listItemCount,
),
),
new SliverAppBar(
floating: floatingAppBarIndex == 1,
automaticallyImplyLeading: false,
title: Text('2'),
),
new SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SizedBox(
height: listItemHeight,
child: ListTile(
title: Text('Text 2'),
),
);
},
childCount: listItemCount,
),
),
],
),
);
}
}
As I said, you're still able to do that in a list with variable values (item height and list length), but it would be very very tricky. If this is your case, I recommend using one of these plugins:
https://pub.dartlang.org/packages/sticky_headers
https://pub.dartlang.org/packages/flutter_sticky_header

Flutter How to build a CustomScrollView with a non scrollable part

I want to have a view with on top a non scrollable part like an image for example with at the bottom a tab bar that i can scroll to the top to let appear a list of item and be able to scroll inside the list of item.
For that i used a CustomScrollView, with a sliver grid in place of the image for the moment, and a sliver app bar for the tabbar and a sliverFixedExtentList for the list.
Widget build(BuildContext context) {
return new Scaffold(
body: new CustomScrollView(
slivers: <Widget>[
new SliverGrid(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.58,
crossAxisCount: 1,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
color: Colors.red,
child: new Container(
color: Colors.green,
child: new Text('IMG HERE'),
)
);
},
childCount: 1,
),
),
new SliverAppBar(
title: new Text("title"),
floating: false,
pinned: true,
primary: true,
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () {
},
),
],
bottom: new TabBar(
controller: _tabController,
isScrollable: true,
tabs: _bars,
),
),
new SliverFixedExtentList(
itemExtent: 100.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.lightGreen[100 * (index % 9)],
child: new Text('list item $index'),
);
},
),
),
],
)
);
}
But i have 3 problems :
I can't figure out how to make a sliver non scrollable for the slivergrid here.
I don't know how to make the appBar be placed exactly at the botom of the screen on launch.
I have a problem with the list when the appbar reach the top the list jump some items, it seems it represents the size of the sliverGrid element.
Thanks
I've tried your code and it seems that there are some missing essential parts there. I can't see what's the code behind _tabController and _bars, so I just made my own _tabController and _bars. I've run it and this is what I've got so far:
On launch:
Browsing till the AppBar goes to the top.
So I made some changes in your code for presentation purposes:
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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
List<Widget> _bars = [
Tab(icon: Icon(Icons.image)),
Tab(icon: Icon(Icons.image)),
];
int _selectedIndex = 0;
#override
void initState() {
super.initState();
_tabController = TabController(length: _bars.length, vsync: this);
_tabController.addListener(() {
setState(() {
_selectedIndex = _tabController.index;
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new CustomScrollView(
slivers: <Widget>[
new SliverGrid(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: .69,
crossAxisCount: 1,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return SafeArea(
child: new Container(
color: Colors.green,
child: new Text('IMG HERE'),
),
);
},
childCount: 1,
),
),
new SliverAppBar(
title: new Text("title"),
floating: false,
pinned: true,
primary: true,
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () {},
),
],
bottom: new TabBar(
controller: _tabController,
isScrollable: true,
tabs: _bars,
),
),
new SliverFixedExtentList(
itemExtent: 100.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.lightGreen[100 * (index % 9)],
child: new Text('list item $index'),
);
},
),
),
],
));
}
}
Here is the output:
As you can see, I've played around with the value of childAspectRatio so that you can set the AppBar` at the bottom of the screen by default, that's how I understood your question number 2.
For question number 3, it seems that your code is working fine. I am able to properly see the ascending list item from 0 sequenced properly.
And for question number 1, I am quiet confused of how you want it to happen. You don't want the SliverGrid to be scrollable but you are expecting the AppBar to be on the top of the screen after scrolling. I guess giving more context on this part could give clarity for everyone.