Make SliverAppBar scrollable when TabBarView child is scrolled - flutter

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

Related

have slivers start behind SliverAppBar

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

Can not fit image and text in AppBar Flutter

I want to place image and text into appbar, but it seems that i cant fit all i want into appbar.
I have this code:
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title:Column(
children: [
Container(
child: Image.asset(
'assets/vecteezy_circle-abstract_1191814.png',
fit: BoxFit.scaleDown,
height: 100,
),
),
Container(
padding: const EdgeInsets.only(top: 15),
child: Text('Text'))
],
),
bottom: TabBar(
tabs: [Tab(text: 'tab 1'), Tab(text: 'tab 2')],
),
),
body: TabBarView(children: [
Text("o"),
Text("kk"),
]),
));
}
and I have such result :
https://i.stack.imgur.com/jzt4o.png
how do I fit it right? so it looks like this:
https://i.stack.imgur.com/r0gum.png
You need to use CustomScrollView with SliverAppBar
look at this example from Flutter Dev
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Floating App Bar';
return MaterialApp(
title: title,
home: Scaffold(
// No appbar provided to the Scaffold, only a body with a
// CustomScrollView.
body: CustomScrollView(
slivers: <Widget>[
// Add the app bar to the CustomScrollView.
SliverAppBar(
// Provide a standard title.
title: Text(title),
// Allows the user to reveal the app bar if they begin scrolling
// back up the list of items.
floating: true,
// Display a placeholder widget to visualize the shrinking size.
flexibleSpace: Placeholder(),
// Make the initial height of the SliverAppBar larger than normal.
expandedHeight: 200,
),
// Next, create a SliverList
SliverList(
// Use a delegate to build items as they're scrolled on screen.
delegate: SliverChildBuilderDelegate(
// The builder function returns a ListTile with a title that
// displays the index of the current item.
(context, index) => ListTile(title: Text('Item #$index')),
// Builds 1000 ListTiles
childCount: 1000,
),
),
],
),
),
);
}
}

How to make the top of FlexibleSpaceBar start at the bottom of SliverAppBar

Is it possible to make the FlexibleSpaceBar begin at the bottom of the SliverAppBar?
I really like the effects that the FlexibleSpaceBar has but the only way I'm currently able to do this is by adding a some padding to the top, e.g. 50 pixels or so.
import 'package:flutter/material.dart';
class AccountScreen extends StatefulWidget {
static const routeName = '/account';
#override
_AccountScreenState createState() => _AccountScreenState();
}
class _AccountScreenState extends State<AccountScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[900],
drawer: Drawer(),
body: SafeArea(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Title'),
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: <Widget>[
Text('Some Text')
],
),
),
),
],
)),
);
}
}
As you can see the FlexibleSpaceBar begins at the top of the screen behind the SliverAppBar
I use as this. This makes the top of FlexibleSpaceBar start at the bottom of SilverAppBar.
SliverAppBar(
elevation: 0,
expandedHeight: 300, // your wanted height
flexibleSpace: FlexibleSpaceBar()
)

Sticky tabs in NestedScrollView with ListViews

The layout works as desired, except this:
When I scroll on one page, the second page scrolls too. Not as much but enough to obscure the first item.
I could imagine it'd have something to do with the NestedScrollView but I don't know how to go on.
import 'package:flutter/material.dart';
main(){
runApp(new MaterialApp(
home: new MyHomePage(),
));
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 2,
child: new Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: const Text('Tabs and scrolling'),
forceElevated: innerBoxIsScrolled,
pinned: true,
floating: true,
bottom: new TabBar(
tabs: <Tab>[
new Tab(text: 'Page 1'),
new Tab(text: 'Page 2'),
],
),
),
];
},
body: new TabBarView(
children: <Widget>[
_list(),
_list(),
],
),
),
),
);
}
Widget _list(){
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: 250,
itemBuilder: (context, index){
return Container(
color: Colors.grey[200].withOpacity((index % 2).toDouble()),
child: ListTile(
title: Text(index.toString()),
),
);
}
);
}
}
To be able to keep the two ListViews to scroll without affecting each other they need to have defined controllers.
To have the ListViews maintain their scroll position between tab switching you need to have them in a Widget with AutomaticKeepAliveClientMixin.
Here's an example of what you can do instead of your _list method. Defined a Stateful Widget that returns your lists using both controllers and the AutomaticKeepAliveClientMixin:
class ItemList extends StatefulWidget {
#override
_ItemListState createState() => _ItemListState();
}
class _ItemListState extends State<ItemList> with AutomaticKeepAliveClientMixin{
ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
super.build(context);
return ListView.builder(
controller: _scrollController,
padding: EdgeInsets.zero,
itemCount: 250,
itemBuilder: (context, index){
return Container(
color: Colors.grey[200].withOpacity((index % 2).toDouble()),
child: ListTile(
title: Text(index.toString()),
),
);
}
);
}
#override
bool get wantKeepAlive => true;
}
You can just call it normally like any other widget inside your TabBarView:
TabBarView(
children: <Widget>[
ItemList(),
ItemList(),
],
),

How to preserve the scroll position of tabView when using collapsible app bar (sliverAppBar)?

Problem:
Scroll position of the tabView is not restored correctly when one of the tabView is scrolled to the top (revealing the sliverAppBar). The other tabView will also scrolled to the top (losing its previous scroll position).
This problem will not show up if normal app bar is used (not collapsible app bar)
This problem only shows up when the tabView is scrolled to top
Question:
How to preserve the scroll position of the tabView when using collapsible app bar (sliverAppBar)?
Code:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 2,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text('Example'),
pinned: true,
floating: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: <Widget>[
Tab(text: 'One',),
Tab(text: 'Two'),
],
),
),
];
},
body: TabBarView(
children: <Widget>[
Center(
key: PageStorageKey<String>('one'),
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text('One Item $index'),
);
},
),
),
Center(
key: PageStorageKey<String>('two'),
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text('Two Item $index'),
);
},
),
),
],
),
),
)
);
}
}
This is actually a known issue and still open. Although there is a workaround as mentioned in this thread:
Here's a workaround that uses
extended_nested_scroll_view
package. With this, I haven't encountered any issues (at least for
now). So until flutter fixes this issue, this workaround seems
sufficient. Only tested on Android.
The basic gist of it is:
Use NestedScrollView from the package extended_nested_scroll_view
Wrap each tab view in a StatefulWidget with AutomaticKeepAliveClientMixin and wantKeepAlive => true
Inside that StatefulWidget(s), wrap the scrollable content in NestedScrollViewInnerScrollPositionKeyWidget
Key values used in innerScrollPositionKeyBuilder and NestedScrollViewInnerScrollPositionKeyWidget must match.