Related
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
_firstListView(),
_secondListView(),
],
),
);
}
Main class that calls other listView builders
Widget _firstListView() {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => RecCell(
index: index,
),
),
);
}
first listView builder
Widget _secondListView() {
return Container(
color: Colors.red,
child: ListView.builder(
itemCount: 200,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) => ListTile(title: Text("Second $index")),
),
);
}
the second listView builder
RecCell is another statefull widget that prints in init method when the widget is build and it build 200 times on restart and never recreated on scrolling
I want them to reuse each time parent listview scrolls any help will appreciated..
Well, wrapping them in a ListView removes the lazy loading benefit of ListView.builder, because the outer ListView wants to load all children at once. I think the best way is the combine the data of the two ListView.builders into a single one. Although, wrapping them in a colored container would not be possible then, but maybe you get the desired result by wrapping the items in it. So something like:
Widget builds(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 400,
shrinkWrap: true,
itemBuilder: (context, index) => index < 200
? Container(
color: Colors.green,
child: RecCell(
index: index,
),
)
: Container(
color: Colors.red,
child: ListTile(title: Text("Second ${index - 200}")))));
}
You could use NestedScrollView if you have nested listview in the body of the parent widget.
try this
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
-----------OR------------
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
children: <Widget>[
Container(height: 300, child: _firstListView()),
Container(height: 300, child: _secondListView()),
],
),
);
}
Use CustomScrollView instead ListViews for this case
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
),
),
],
);
because the ListView requires all available space and expands its own size as much as possible to understand how many child elements can be displayed
I did this and worked for me
Map<String, dynamic> d = {
0:..,listView widget,
1:[...list of widgets]
}
I am making a map containing all my widgets, and in the build method I am returning a listview with those widgets
return ListView(
children:[
d[0],
...d[1] // this will extract each widget from the list and insert it to list view
]
)
In this way my d[0] is horizontal listview builder and d1 are other widget that I am adding into parent list view directly now they all are rerendering when visible. Had to do some changing in my previous code
Final output:
output
I am building a RegisterPage in flutter and I devided my scaffold into two containers. One for the top half and the other for the bottom half so that the logo/headline can be in the top half and the form be in the bottom half.
Now I noticed after testing on android that the keyboard overlaps the input fields. So I added a SingleChildScrollView() view to fix this. But there is a problem. The content appears to be too big and you can now always scroll even when the keyboard is down. I added a video to demonstrate what I mean.
I think that this space is responsible for that
It looks like that space is reserved for the android navigation bar. How do I remove that? Or how can I size my containers in such a way so that they account for that? Just to be clear the thing I want to know most is how I can stop my SingleChildScrollView() scrolling when the keyboard is down, which is probably cause by the containers being to big.
the_register_page.dart
#override
Widget build(BuildContext context) {
final AuthenticationProvider authenticationProvider = Provider.of<AuthenticationProvider>(context);
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: ScrollConfiguration(
behavior: ScrollBehaviourWithoutGlow(),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Container(
decoration: BoxDecoration(color: Colors.red),
height: MediaQuery.of(context).size.height / 2,
child: _buildLogo(),
),
Container(
decoration: BoxDecoration(color: Colors.green),
height: MediaQuery.of(context).size.height / 2,
child: _buildRegisterForm(authenticationProvider),
),
],
),
),
),
),
);
}
Notice that I used the SystemChrome.setEnabledSystemUIOverlays([]); in my main.dart.
main.dart
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(
create: (_) => PreferencesProvider()),
ChangeNotifierProvider<AuthenticationProvider>(
create: (_) => AuthenticationProvider()),
Provider<GroupProvider>(create: (_) => GroupProvider()),
Provider<UserProvider>(
create: (_) => UserProvider(),
),
],
child: Consumer<PreferencesProvider>(
builder: (context, preferences, _) => MaterialApp(
home: TheSplashPage(),
routes: <String, WidgetBuilder>{
TheGroupPage.routeName: (BuildContext context) => TheGroupPage(),
TheSettingsPage.routeName: (BuildContext context) =>
TheSettingsPage(),
TheProfilePage.routeName: (BuildContext context) =>
TheProfilePage(),
TheGroupCreationPage.routeName: (BuildContext context) =>
TheGroupCreationPage(),
},
theme: preferences.isDarkMode
? DarkTheme.themeData
: LightTheme.themeData,
debugShowCheckedModeBanner: false,
),
),
);
}
}
Since this post didn't get any answers I decided to upload a video to show what I mean. Here is the video. As you can see you are able to swipe even when the keyboard is down which is not what I want.
https://www.youtube.com/watch?v=Rm91KMFqs60
Edit
This is tested on a android emulator
And my android device a Lenovo P2 with android 7
Edit
I can confirm that this bottom area is the cause. I looked at the pixel height on my pc screen and subtracted that from the container and now I can't scroll when the keyboard is down which is what I want.
So I would like to remove this area or get it's height any idea how?
You're very close. Setting the SystemChrome color to transparent just makes them see-through but won't draw your app over top of the navigation or status bars. The fix is a 2 step process.
Set SystemChrome.setEnabledSystemUIOverlays([SystemUIOverlay.top]) to display only the notification bar or SystemChrome.setEnabledSystemUIOverlays([]) to display your app in full screen.
In your Scaffold set the resizeToBottomInset property to false when you want the screen to display over top of the bottom system UI overlay and to true when the keyboard is up.
Full code example:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Display your app in full-screen mode
SystemChrome.setEnabledSystemUIOverlays([]);
// Show the notification bar but not the navigation bar
// SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool resizeToAvoidBottom;
#override
void initState() {
super.initState();
// Do not avoid bottom by default, this allows the display to paint over
// where the Android button bar would be.
resizeToAvoidBottom = false;
}
void setResize(bool resize) {
setState(() {
resizeToAvoidBottom = resize;
});
}
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height / 2;
return Scaffold(
resizeToAvoidBottomInset: resizeToAvoidBottom,
body: Container(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Placeholder(
fallbackHeight: height,
),
Container(
height: height,
color: Colors.green,
child: Column(
children: <Widget>[
// Text fields update the resize property so that you
// can still scroll the display when the keyboard is up
// You will need to change when setResize(false) is
// called to suit the needs of your app
TextField(
onTap: () {
setResize(true);
},
onSubmitted: (str) {
setResize(false);
},
),
TextField(
onTap: () {
setResize(true);
},
onSubmitted: (str) {
setResize(false);
},
),
],
))
],
),
),
),
);
}
}
This is actually a current Issue.
There is workaround which you could use, which is setting extendxBody: true in your Scaffold.
But if it is not extremely critical I would wait for the fix to be deployed.
To clarify: My answer is about the empty space at the bottom.
I'm developing a new Flutter app on Android Studio. One page of my app gets the information from a Future. With the future's result I'm creating a ListView.builder():
Here's the code:
Widget encabezados(BuildContext context, AsyncSnapshot snapshot)
{
ListView(
children: <Widget>[
new Card(
elevation: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(width: 0.0,),
Category(
backgroundColor: Colors.deepOrange,
val: clientesacobrar.toString(),
title: "Clientes a cobrar",
),
SizedBox(width: 10.0,),
Category(
backgroundColor: Colors.blue,
title: "Clientes cobrados",
val: clientescobrados.toString(),
),
SizedBox(width: 10.0,),
Category(
val: formatter.format(montoacobrar),
//val: records.length.toString(),
backgroundColor: Colors.green,
title: "Monto a cobrar",
)
],
),
),
new ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
//elevation: 3,
children: <Widget>[
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
//ShrinkWrappingViewport: true,
itemCount: records.length,
itemBuilder: (BuildContext context, int index) {
return records.isNotEmpty
? Card(
child: Padding (
padding: const EdgeInsets.all(16.00),
child: Text(records[index].nombre),
)
)
: CircularProgressIndicator();
}
)
],
)
],
);
}
As you can see in the listviews widgets I'm using shrinkWrap set to true, because if I don't set this I get:
I/flutter (22634): The following assertion was thrown during performResize():
I/flutter (22634): Vertical viewport was given unbounded height.
I/flutter (22634): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (22634): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (22634): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (22634): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (22634): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (22634): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (22634): the height of the viewport to the sum of the heights of its children.
But if I set the shrinkwrap property I'm not able to scroll the page on my app
I have tried putting the ListView.builder() into:
container widget , column widget, ListView widget but in the ListView.builder() I'm always asked to set the shrinkwrap property.
You need to constrain your parent widget so it knows where the bounds are.
For that scenario, wrapping in a Container alone, won’t have any effect unless you explicit constrain it (eg. by setting constrains or just giving it a explicit height).
Container(
height: 200.0, // or whatever
child: ListView(
....
)
)
As an alternative, you can also set your inner ListView physics to NeverScrollableScrollPhysics() which will prevent any scroll from happening in the inner one and only the top scroll view will control scroll.
If all of that doesn't help, you may need to use a CustomScrollView which lets you nest multiple scroll views, basically a Sliver.
Try to add physics: NeverScrollableScrollPhysics() to inner ListView.
Miguel Ruivo's answer is correct but rather than using a Container with an explicit height, you can instead wrap the ListView with Expanded to give it the remaining height and allow it to scroll if needed.
Note: Make sure the Expanded widget is a descendant of Row, Column, or Flex.
Expanded(
child: ListView(
...
)
)
I had the same issue and found out that the main thing is using the physics: NeverScrollableScrollPhysics property inside of your ListView.Builder() like so:
body: Column(
children: [
_buildHeader(),
Container(
// height: 40,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(), // <= This was the key for me
shrinkWrap: true,
itemCount: 1,
itemBuilder: (_, i) {
return _horizontalListView();
},
),
),
],
),
I tested and It works with Column, Container, Expanded widgets as a parent widget.
So something that seems odd is the fact that you have a whole heap of nested ListView's. I suggest try limiting it to just one.
It's a little difficult to see what the intended outcome should be... Is the "Category card" meant to be static with the ListView.builder part scrolling underneath? Or does the "Category card" scroll with the content?
Either way, I think I've got something that you should be able to use.
Included are ways of accomplishing either the static or scrolling head.
A note on the static header solution: I'm using a Column instead of a ListView This ensures we don't have top level scrolling going on. First child is your "header" with the second child the ListView responsible for the scrolling. This ListView is wrapped in Expanded to make sure the scrolling ListView takes up all of the remaining screen height.
Hope this helps
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SO Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: _buildViewWithStaticCategories());
// OR
// body: _buildViewWithScrollingCategories());
}
Widget _buildViewWithStaticCategories() {
return Column(
children: <Widget>[
_buildCategoriesCard(),
Expanded(
child: ListView.builder(
itemCount: 20, // records.length
itemBuilder: (BuildContext context, int index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.00),
child: Text(index.toString()),
),
);
}),
),
],
);
}
Widget _buildViewWithScrollingCategories() {
return ListView.builder(
itemCount: 21, // records.length + 1 for the Categories card
itemBuilder: (BuildContext context, int index) {
return index == 0
? _buildCategoriesCard()
: Card(
child: Padding(
padding: const EdgeInsets.all(16.00),
child: Text(index.toString()),
),
);
},
);
}
Widget _buildCategoriesCard() {
return Card(
elevation: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('one'), // Category( ...
SizedBox(width: 10.0),
Text('two'), // Category( ...
SizedBox(width: 10.0),
Text('three'), // Category( ...
],
),
);
}
}
I am trying to create an app with a top application bar and a tab bar below. When you scroll down, the bar should hide by moving off the screen (but tabs should stay), and when you scroll back up, the application bar should show again. This behaviour can be seen in WhatsApp. Please see this video for a demonstration. (Taken from Material.io). This is a similar behaviour, although the app bar and tab bar are hidden on scroll, so it is not exactly the behaviour I am looking for.
I have been able to achieve the autohiding, however, there are a few issues:
I have to set the snap of the SliverAppBar to true. Without this, the application bar will not show when I scroll back up.
Although this is works, it is not the behaviour I am looking for. I want the application bar to show smoothly (similar to WhatsApp) rather than coming into view even if you scroll very little.
To clarify, when I scroll all the way down, even if I scroll up very little, the app bar should come into view. I do not want to have to scroll all the way up to see the app bar.
When I scroll down and change tabs, a little bit of the content is cut out of view.
Below is a GIF showing the behaviour:
(See the part when I scroll down on the listView (tab1), then move back to tab2)
Here is the code for the DefaultTabController:
DefaultTabController(
length: 2,
child: new Scaffold(
body: new NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: Text("Application"),
floating: true,
pinned: true,
snap: true, // <--- this is required if I want the application bar to show when I scroll up
bottom: new TabBar(
tabs: [ ... ], // <-- total of 2 tabs
),
),
];
},
body: new TabBarView(
children: [ ... ] // <--- the array item is a ListView
),
),
),
),
In case it is needed, the full code is in this GitHub repository. main.dart is here.
I also found this related question: Hide Appbar on Scroll Flutter?. However, it did not provide the solution. The same problems persist, and when you scroll up, the SliverAppBar will not show. (So snap: true is required)
I also found this issue on Flutter's GitHub. (Edit: someone commented that they are waiting for the Flutter team to fix this. Is there a possibility that there is no solution?)
This is the output of flutter doctor -v: Pastebin. Certain issues are found, but from what I have learned, they should not have an impact.
Edit: There are two issues for this:
https://github.com/flutter/flutter/issues/29561 (closed)
https://github.com/flutter/flutter/issues/17518
You need to use SliverOverlapAbsorber/SliverOverlapInjector, the following code works for me (Full Code):
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is "inside"
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// find the NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
),
);
}
Update - Sliver App Bar Expanded
If you want to see Sliver App Bar expanded as soon as someone scrolls up i.e. not scrolling all the way to top but just little bit, Then just change snap: false to snap: true in code :)
Solution [Fixing All Points]
After surfing google, stackoverflow, github issues, reddit for hours. I could finally come up with a solution that addresses following:
Sliver App bar with title getting hidden and only tab bar visible after scrolling down. You would see title again when you reach top.
MAJOR : When you scroll in Tab 1 & then Navigate to Tab 2, you would not see any overlap. The content of Tab 2 will not get obstructed by Sliver App bar.
Sliver Padding for top most element in List is 0.
Preserves the Scroll Position in individual Tabs
below is the code, I would attempt to explain in a bit (dartpad preview) :
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
Test it out all you want in dartpad, once you are fine then lets try to understand what is happening here.
Most of the code is from flutter documentation of NestedScrollView
They have mentioned very nicely in comments. I am no expert so I would just highlight what I think solved most of the issues.
I believe Two things are critical here:
SliverOverlapAbsorber & SliverOverlapInjector
Use of SliverList instead of ListView
Whatever extra space we were seeing or the space which sliver app bar consumed and first list item was overlapped was mainly resolved with the use of above two points.
To remember the scroll position of tabs, they added PageStorageKey inside CustomScrollView:
key: PageStorageKey<String>(name),
name is just a string -> 'Tab 1'
They also mentioned in docs that we can use SliverFixedExtentList, SliverGrid, basically Sliver widgets. Now use of Sliver widgets should be done when needed. In one of the Flutter Youtube videos (official channel) they mentioned that ListView, GridView, are all high level implementation of Slivers. So Slivers is low level stuff if you looking to super customize scrolling or appearance behaviour.
Please let me know in comments if I missed something or said wrong.
I was able to make the floating Appbar with Tabbar similar to that of WhatsApp by using SliverAppbar with NestedScrollView.
Do add floatHeaderSlivers: true, in NestedScrollView and
pinned: true, floating: true, in SliverAppBar
Link to working code sample
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CustomSliverAppbar(),
);
}
}
class CustomSliverAppbar extends StatefulWidget {
#override
_CustomSliverAppbarState createState() => _CustomSliverAppbarState();
}
class _CustomSliverAppbarState extends State<CustomSliverAppbar>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(
initialIndex: 0,
length: 2,
vsync: this,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text(
"WhatsApp type sliver appbar",
),
centerTitle: true,
pinned: true,
floating: true,
bottom: TabBar(
indicatorColor: Colors.black,
labelPadding: const EdgeInsets.only(
bottom: 16,
),
controller: _tabController,
tabs: [
Text("TAB A"),
Text("TAB B"),
]),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
TabA(),
const Center(
child: Text('Display Tab 2',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
),
),
);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
}
class TabA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scrollbar(
child: ListView.separated(
separatorBuilder: (context, child) => Divider(
height: 1,
),
padding: EdgeInsets.all(0.0),
itemCount: 30,
itemBuilder: (context, i) {
return Container(
height: 100,
width: double.infinity,
color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
);
},
),
);
}
}
--- EDIT 1 --
Alright so I threw together something quick for you. I followed this article (written by Emily Fortuna who is one of the main Flutter devs) to better understand Slivers.
Medium: Slivers, Demystified
But then found this Youtube video that basically used your code so I opted for this one, rather than try to figure out every small detail about Slivers.
Youtube: Using Tab and Scroll Controllers and the NestedScrollView in Dart's Flutter Framework
Turns out you were on the right track with your code. You can use SliverAppBar within NestedScrollView (this wasn't the case last time I tried) but I made a few changes. That I will explain after my code:
import 'package:flutter/material.dart';
import 'dart:math';
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,
),
home: MyHomePage(title: 'Flutter Demo'),
);
}
}
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 /*<-- This is for the controllers*/ {
TabController _tabController; // To control switching tabs
ScrollController _scrollViewController; // To control scrolling
List<String> items = [];
List<Color> colors = [Colors.red, Colors.green, Colors.yellow, Colors.purple, Colors.blue, Colors.amber, Colors.cyan, Colors.pink];
Random random = new Random();
Color getRandomColor() {
return colors.elementAt(random.nextInt(colors.length));
}
#override
void initState() {
super.initState();
_tabController =TabController(vsync: this, length: 2);
_scrollViewController =ScrollController();
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
_scrollViewController.dispose();
}
#override
Widget build(BuildContext context) {
// Init the items
for (var i = 0; i < 100; i++) {
items.add('Item $i');
}
return SafeArea(
child: NestedScrollView(
controller: _scrollViewController,
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text("WhatsApp using Flutter"),
floating: true,
pinned: false,
snap: true,
bottom: TabBar(
tabs: <Widget>[
Tab(
child: Text("Colors"),
),
Tab(
child: Text("Chats"),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: <Widget>[
ListView.builder(
itemBuilder: (BuildContext context, int index) {
Color color = getRandomColor();
return Container(
height: 150.0,
color: color,
child: Text(
"Row $index",
style: TextStyle(
color: Colors.white,
),
),
);
},
//physics: NeverScrollableScrollPhysics(), //This may come in handy if you have issues with scrolling in the future
),
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Material(
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blueGrey,
),
title: Text(
items.elementAt(index)
),
),
);
},
//physics: NeverScrollableScrollPhysics(),
),
],
),
),
);
}
}
Alright so on to the explanation.
Use a StatefulWidget
Most widgets in Flutter are going to be stateful but it depends on the situation. I think in this case it is better because you are using a ListView which could change as users add or erase conversations/chats.
SafeArea because this widget is great.
Go read about it on Flutter Docs: SafeArea
The Controllers
I think this was the big issue at first but maybe it was something else. But you should usually make your own controllers if you are dealing with custom behavior in Flutter. So I made the _tabController and the _scrollViewController (I don't think I got every bit of functionality out of them, i.e. keeping track of scroll positions between tabs, but they work for the basics). The tab controller that you use for the TabBar and the TabView should be the same.
The Material Widget before the ListTile
You probably would have found this out sooner or later but the ListTile widget is a Material widget and therefore requires a "Material ancestor widget" according to the output I got while trying to render it at first. So I saved you a tiny headache with that. I think it is because I didn't use a Scaffold. (Just keep this in mind when you use Material widgets without Material ancestor widgets)
Hope this helps you get started, if you need any assistance with it just message me or add me to your Github repo and I'll see what I can do.
--- ORIGINAL ---
I answered you on Reddit as well, hopefully you see one of these two soon.
SliverAppBar Info
The key properties you want to have with the SliverAppBar are:
floating: Whether the app bar should become visible as soon as the user scrolls towards the app bar.
pinned: Whether the app bar should remain visible at the start of the scroll view. (This is the one you are asking about)
snap: If snap and floating are true then the floating app bar will "snap" into view.
All this came from the Flutter SliverAppBar Docs. They have lots of animated examples with different combos of floating, pinned and snap.
So for yours the following should work:
SliverAppBar(
title: Text("Application"),
floating: true, // <--- this is required if you want the appbar to come back into view when you scroll up
pinned: false, // <--- this will make the appbar disappear on scrolling down
snap: true, // <--- this is required if you want the application bar to 'snap' when you scroll up (floating MUST be true as well)
bottom: new TabBar(
tabs: [ ... ], // <-- total of 2 tabs
),
),
ScrollView with SliverAppBar
To answer the underlying question of the NestedScrollView. According to the docs (same as above) a SliverAppBar is:
A material design app bar that integrates with a CustomScrollView.
Therefore you cannot use a NestedScrollView you need to use a CustomScrollView. This is the intended use of Sliver classes but they can be used in the NestedScrollView Check out the docs.
I want to add a transparent appBar over a PageView.
The Pageview is implemented using Stacks in Flutter, So that all the content is placed over an image. Since it is different than how Tabs work, I need to place an appBar such that even during Scrolling b/w multiple pageViews, the appBar remains Fixed at given position.
I am using following Code to achieve this, but it is showing various kind of errors.
import 'package:flutter/material.dart';
import 'package:page_transformer/data.dart';
import 'package:page_transformer/intro_page_item.dart';
import 'package:page_transformer/page_transformer.dart';
class IntroPageView extends StatelessWidget{
#override
Widget build(BuildContext context) {
List<Widget> children = new List();
return
Stack(
fit: StackFit.expand,
children: [
_buildBackground(),
_buildforeground()
],
);
}
Widget _buildforeground() => new AppBar(
title: Text('Antara',
textDirection: TextDirection.rtl,),
);
Widget _buildBackground() => PageTransformer(
pageViewBuilder: (context, visibilityResolver) {
return PageView.builder(
controller: PageController(viewportFraction: 1.0),
itemCount: sampleItems.length,
itemBuilder: (context, index) {
final item = sampleItems[index];
final pageVisibility =
visibilityResolver.resolvePageVisibility(index);
return IntroPageItem(
item: item,
pageVisibility: pageVisibility,
);
},
);
},
);
}
Please tell me where i am wrong, Also, i forgot to mention that the pageviews are stateful widgets, and the PageTransformer widget is used to give parallax effect while scrolling b/w pageviews.
Thank You
You should apply two changes to your code to force the height
Remove the StackFit.expand because it makes the bar to occupy the whole screen
Wrap the AppBar with a Container or SizedBox and provide a height to constrain it
And finally set the transparent color and elevation to 0.0
#override
Widget build(BuildContext context) {
List<Widget> children = new List();
return Stack(
//fit: StackFit.expand,
children: [
_buildBackground(),
_buildforeground(),
],
);
}
Widget _buildforeground() => SizedBox(
height: 80.0,
child: new AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
title: Text(
'Antara',
textDirection: TextDirection.rtl,
style: TextStyle(color: Colors.grey),
),
),
);