How to fix Column overflow in NestedScrollView body? - flutter

So basically I am using a NestedScrollView with a column body that holds a TabBar and TabBarView:
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
physics: BouncingScrollPhysics(),
headerSliverBuilder: (context, innerScrolled) {
return [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
stretch: true,
pinned: true,
collapsedHeight: 400,
backgroundColor: Colors.red,
),
),
SliverList(
delegate: SliverChildListDelegate(
[
SizedBox(height: 20),
Biography(),
SizedBox(height: 20),
],
),
),
];
},
body: Column(
children: [
Container(
child: TabBar(
controller: this.controller,
tabs: [
Tab(icon: Icon(CupertinoIcons.book)),
Tab(icon: Icon(CupertinoIcons.music_albums)),
],
),
),
Expanded(
child: TabBarView(
controller: this.controller,
children: [
// this is in reality a customscrollview
Container(color: Colors.red),
// this is in reality a customscrollview
Container(color: Colors.blue),
],
),
),
],
),
),
);
}
The error:
When I scroll up, i.e my thumb is going toward the bottom of the screen, and the Container that is in the Column reaches the bottom of the screen, it causes an overflow error. The Expanded TabBarView is fine, it's the Container that causes the overflow.
What I've tried:
I've tried to change the Column to a ListView as stated here but it results in another error(something about hasSize). I did set shrinkWrap: true.
Use Expanded on the Container but it results in a wonky UI.
Wrapping the Column in a SingleChildScrollView but it also results in the same error as ListView.
How can I make it so the Container doesn't cause an overflow when it reaches the bottom of the screen?
Thank you!

Related

Issues with tabbar / tabbarview in flutter

I have a page which i want to separate in 3 different 'pages' using the tabbar.
When i want to add other stuff to these pages (for example some text on top of my ListView.builder the contents of my listview.builder are gone.
My homepage is also responsible for my appbar and bottombar, this groceries page is the 3rd index of my bottom bar.
Im not really that experienced yet, im having some issues with my page buildup.
Below is the code for my groceries page where all other stuff dissapear if i wrap my listview.builder in a column for example so i can add a title for example on top of the listview.builder results. Since my homepage is responsible for my appbar i do not want that to be regenerated on every page, only the title changes when switching
Widget build(BuildContext context) {
return Column(
children: [
Container(
height: 50,
color: Colors.blue,
child: TabBar(
controller: _tabController,
labelColor: Colors.white,
tabs: const [
Tab(
text: ('Shopping list'),
),
Tab(
text: ('Menus'),
),
Tab(
text: ('Grocery Stores'),
)
]),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Container(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
itemCount: shoppingListItem.length,
itemBuilder: (context, index) {
return ShoppingItemTile(
text: shoppingListItem[index].title);
},
),
),
Container(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: menuItem.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return MenuItemTile(text: menuItem[index].menuName[0]);
},
//const Text('My Menus'),
),
),
const Text('My Grocery Stores')
],
),
),
I have tried wrapping my Listview.builder in other widgets. All have the same result (listview.builder contents gone).
Issue gas been solved by a complete redesign if my page. Tnx for thinking with me

CLOSED: NestedScrollView briefly stutters before properly scrolling

So basically I am using a NestedScrollView which has a TabBarView as its body. My setup works pretty much as desired however there is a stutter while scrolling. When the TabBar reaches the top of the page/touches the bottom of the SliverAppBar while scrolling, there is a brief pause in scrolling before scrolling is resumed as normal. This pause also happens when we scroll back down.
Here is the error:
I cannot seem to figure out how to fix this pause. It is brief yet annoyingly noticeable. How could I fix this?
Thank you!
You can use only one CustomScrollView in this case. and For inner scrollable physics: NeverScrollableScrollPhysics(),
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
const SliverAppBar(
pinned: true,
title: Text('AppBar'),
collapsedHeight: 100,
backgroundColor: Colors.blue,
),
SliverToBoxAdapter(
child: Container(
alignment: Alignment.center,
height: 100,
color: Colors.redAccent,
child: const Text('Container'),
),
),
SliverPinnedHeader(
child: Container(
color: Colors.white,
child: TabBar(
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.shopping_cart, color: Colors.black)),
Tab(icon: Icon(Icons.bookmark, color: Colors.black)),
],
),
),
),
SliverFillRemaining(
child: TabBarView(
// physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: [
ListView.builder(
itemCount: 44,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
tileColor: Colors.pinkAccent,
title: Text('index $index'),
);
},
),
ListView.builder(
itemCount: 44,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
tileColor: Colors.pinkAccent,
title: Text('index $index'),
);
},
),
],
)),
],
));
}

Positioned Widget overlapping my CustomScrollView

I have a CustomScrollView that I need a fixed text entry field at the bottom. A post here suggestion a Stack with a Positioned Widget which worked great:
Scaffold(
appBar: appBarBuilder == null ? null : appBarBuilder(context),
body: RefreshIndicator(
onRefresh: onRefresh,
child: Stack(
children: <Widget>[
CustomScrollView(
controller: controller,
slivers: <Widget>[
if (showImage)
SliverAppBar(
expandedHeight: showImage ? 100 : 50,
title: showImage ? image : null,
centerTitle: true,
floating: true,
pinned: false,
),
sliverList,
],
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: TextFormField(),
),
],
),
),
);
Except that the positioned widget overlaps my CustomScrollView. I could add a white background, but I'd rather the CustomScrollView stop short of my TextFormField. How do I do that? Below is the current rendering:
Screenshot:
I'm sharing with you a simple implementation of a Column with ListView (replace it with your CustomScrollView) and a TextField at the bottom. When you click on the TextField, the keyboard automatically slides up the ListView and all your contents remain visible.
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder( // <-- Replace it with your CustomScrollView
itemCount: 20,
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
),
Padding(
padding: const EdgeInsets.all(20),
child: TextField(decoration: InputDecoration(hintText: 'Enter a message')),
)
],
),
);
}

How to do animated switcher with slivers in flutter

So I have a CustomScrollView with a SliverAppBar(floating: true, snap: true), a SliverPersistentHeader(pinned: true) a SliverList.
What I am trying to do is to click a button in the header and just pop a widget in the space taken by the SliverPersistentHeade and SliverList. I tried putting them inside a nestedScrollView and nesting SliverPersistentHeader and SliverList inside another scrollView but since the SliverAppBar and SliverPersistentHeader are not together it messes up the supposed effect when it(SliverAppBar) snaps up and off.
Why do I need it like that: I want to perform some animations with the elements in the appBar as the lower part transitions between the two pages kinda like animtions shared axis package
problems
keep the persistantHeader on the first page
keep the snap floating appbar feature
transition the lower part
Note: Answers don't necessary need to depend on slivers. a possible way to achieve that will do
My code at the moment looks roughly like this
CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
SliverAppBar(
backgroundColor: Colors.white,
floating: true,
snap: true,
titleSpacing: 0,
elevation: 4,
title: Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.only(top: 10, bottom: 10, left: 10),
child: Container(color: Colors.grey),
),
),
),
Container(
child: LeadingButton(
color: lightShadeColor,
icon: Icons.qr_code_scanner_rounded,
iconColor: darkColor,
size: 37, // btnShadow: false
),
),
],
),
),
SliverPersistentHeader(
delegate: PersistentHeader(
widget: Row(
// Format this to meet your need
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Text('data'),
)
],
),
),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return ListTile(
title: Text('index $index'),
);
}),
)
],
),

Instagram Profile Header Layout In Flutter

I've been investigating SliverAppBar, CustomScrollView, NestedScrollView, SliverPersistentHeader, and more. I cannot find a way to build something like the Instagram user profile screen's header where only the tab bar is pinned. The main body of the screen is a TabBarView and each pane has a scrollable list.
With SliverAppBar, it is easy to add the TabBar in the bottom parameter. But I want to have an extra widget of unknown/variable height above that TabBar. The extra widget should scroll out of the way when the page is scrolled and and then the TabBar is what is pinned at the top of the screen.
All I could manage was a fixed content before the tab bar and a fixed tab bar. I cannot get the header to scroll up and stick the TabBar at the top just just below the AppBar.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("pabloaleko"),
),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverToBoxAdapter(
child: SafeArea(
child: Text("an unknown\namount of content\n goes here in the header"),
),
),
SliverToBoxAdapter(
child: TabBar(
tabs: [
Tab(child: Text('Days', style: TextStyle(color: Colors.black))),
Tab(child: Text('Months', style: TextStyle(color: Colors.black))),
],
),
),
SliverFillRemaining(
child: TabBarView(
children: [
ListView(
children: <Widget>[
ListTile(title: Text('Sunday 1')),
ListTile(title: Text('Monday 2')),
ListTile(title: Text('Tuesday 3')),
ListTile(title: Text('Wednesday 4')),
ListTile(title: Text('Thursday 5')),
ListTile(title: Text('Friday 6')),
ListTile(title: Text('Saturday 7')),
ListTile(title: Text('Sunday 8')),
ListTile(title: Text('Monday 9')),
ListTile(title: Text('Tuesday 10')),
ListTile(title: Text('Wednesday 11')),
ListTile(title: Text('Thursday 12')),
ListTile(title: Text('Friday 13')),
ListTile(title: Text('Saturday 14')),
],
),
ListView(
children: <Widget>[
ListTile(title: Text('January')),
ListTile(title: Text('February')),
ListTile(title: Text('March')),
ListTile(title: Text('April')),
ListTile(title: Text('May')),
ListTile(title: Text('June')),
ListTile(title: Text('July')),
ListTile(title: Text('August')),
ListTile(title: Text('September')),
ListTile(title: Text('October')),
ListTile(title: Text('November')),
ListTile(title: Text('December')),
],
),
],
),
),
],
),
),
);
}
}
You can achieve this behaviour using NestedScrollView with Scaffold.
As we need the widgets between the AppBar and TabBar to be dynamically built and scrolled until TabBar reaches AppBar, use the appBar property of the Scaffold to build your AppBar and use headerSliverBuilder to build other widgets of unknown heights. Use the body property of NestedScrollView to build your tab views.
This way the elements of the headerSliverBuilder would scroll away till the body reaches the bottom of the AppBar.
Might be a little confusing to understand with mere words, here is an example for you.
Code:
// InstaProfilePage
class InstaProfilePage extends StatefulWidget {
#override
_InstaProfilePageState createState() => _InstaProfilePageState();
}
class _InstaProfilePageState extends State<InstaProfilePage> {
double get randHeight => Random().nextInt(100).toDouble();
List<Widget> _randomChildren;
// Children with random heights - You can build your widgets of unknown heights here
// I'm just passing the context in case if any widgets built here needs access to context based data like Theme or MediaQuery
List<Widget> _randomHeightWidgets(BuildContext context) {
_randomChildren ??= List.generate(3, (index) {
final height = randHeight.clamp(
50.0,
MediaQuery.of(context).size.width, // simply using MediaQuery to demonstrate usage of context
);
return Container(
color: Colors.primaries[index],
height: height,
child: Text('Random Height Child ${index + 1}'),
);
});
return _randomChildren;
}
#override
Widget build(BuildContext context) {
return Scaffold(
// Persistent AppBar that never scrolls
appBar: AppBar(
title: Text('AppBar'),
elevation: 0.0,
),
body: DefaultTabController(
length: 2,
child: NestedScrollView(
// allows you to build a list of elements that would be scrolled away till the body reached the top
headerSliverBuilder: (context, _) {
return [
SliverList(
delegate: SliverChildListDelegate(
_randomHeightWidgets(context),
),
),
];
},
// You tab view goes here
body: Column(
children: <Widget>[
TabBar(
tabs: [
Tab(text: 'A'),
Tab(text: 'B'),
],
),
Expanded(
child: TabBarView(
children: [
GridView.count(
padding: EdgeInsets.zero,
crossAxisCount: 3,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
),
ListView(
padding: EdgeInsets.zero,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
)
],
),
),
],
),
),
),
);
}
}
Output:
Hope this helps!
Another solution is that you could use a pinned SliverAppBar with FlexibleSpaceBar within DefaultTabController. Sample codes:
Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
floating: true,
pinned: true,
bottom: TabBar(
tabs: [
Tab(text: "Posts"),
Tab(text: "Likes"),
],
),
expandedHeight: 450,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Profile(), // This is where you build the profile part
),
),
];
},
body: TabBarView(
children: [
Container(
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 40,
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
Container(
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 40,
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
],
),
),
),
),
Before scrolling:
After scrolling: