Custom Scroll View getting scrolled under Sliver Persistent Header - flutter

DefaultTabController(
length: _subCategory.tabLength,
initialIndex: 0,
child:
NestedScrollView(
physics: BouncingScrollPhysics(),
headerSliverBuilder: (headerCtx, innnerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
backgroundColor: _productColor.backgroundColor,
pinned: true,
elevation: 0,
forceElevated: innnerBoxIsScrolled,
flexibleSpace: FlexibleSpaceBar(
title: Text("${_subCategory.currentSubCategoryName()}"),
background: Container(
margin: const EdgeInsets.only(
top: 4,
bottom: 50.0,
),
child: Hero(
tag: _subCategory.currentSubCategoryId(),
child: Image.asset(
'asset/images/grocery.jpeg',
),
),
),
),
),
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(headerCtx),
sliver: SliverPersistentHeader(
pinned: true,
delegate: _ProductTabSliver(
TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black87,
tabs: [
..._subCategory.currentTab().map(
(tabValue) {
return Tab(text: "${tabValue.fullName}");
},
).toList()
],
),
),
),
),
];
},
body:CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, pdIndex) {
final heightVisible =
_subCategory.advanceCompanyProductCount(pdIndex);
return ProductLayout();
},
childCount: _subCategory.differentProductCount(),
),
),
],
);,
));
CustomScrollList getting scrolled under sliverPersistentHeader Tab.
DefaultTabController
NestedScrollView
SliverAppBar
SliverPersistentHeader
-body: CustomScrollView
- slivers: SliverChildBuilderDelegate
SliverPersistentHeader had all the tabs displayed on the top(TabBar)
Body of Nested ScrollView is CustomScrollView which has SliverChildBuilderDelegate has a child.
On scrolling the list, my list scroll behind the tabs of sliver persistent header. Seems like sliverPersistentHeader is transparent and list scrolls can be seen behind.
To solve this problem, I had tried SliverOverlapInjector and SliverOverlapAbsorber, but that didn't help.
CustomScrollView scroll problem image is 4th for better understanding. Sunflower oil card on scrolling reaches behind the tab bar.
Images:
Sliver Overlap Absorber
Sliver Overlap Injector
Custom Scroll View
Overlapping Problem

class ProductAppBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return NestedScrollView(
physics: BouncingScrollPhysics(),
headerSliverBuilder: (headerCtx, innnerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
backgroundColor: _productColor.backgroundColor,
pinned: true,
elevation: 0,
forceElevated: innnerBoxIsScrolled,
flexibleSpace: FlexibleSpaceBar(
title: Text("${_subCategory.currentSubCategoryName()}"),
background: Container(
margin: const EdgeInsets.only(
top: 4,
bottom: 50.0,
),
child: Hero(
tag: _subCategory.currentSubCategoryId(),
child: Image.asset(
'asset/images/grocery.jpeg',
),
),
),
),
),
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(headerCtx),
sliver: SliverPersistentHeader(
pinned: true,
delegate: _ProductTabSliver(
TabBar(
onTap: (index) {
_subCategory.updateTabIndex(index);
},
labelColor: Colors.white,
unselectedLabelColor: Colors.black87,
tabs: [
..._subCategory.currentTab().map(
(tabValue) {
return Tab(text: "${tabValue.fullName}");
},
).toList()
],
),
),
),
),
];
},
body: TabBarView(
children: _subCategory.currentTab().map((tabElement) {
return ProductScreenLayout();
}).toList(),
),
);
}
}
class _ProductTabSliver extends SliverPersistentHeaderDelegate {
final TabBar _tabBar;
_ProductTabSliver(this._tabBar);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final _productColor =
Provider.of<ColorConfig>(context, listen: false).randomProductColor();
return Container(
decoration: BoxDecoration(
color: _productColor.backgroundColor,
),
child: _tabBar);
}
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
Instead of returning just TabBar Widget from the SliverPersistentHeaderDelegate wrapping it with Container and setting backgroundColor solve my problem.
Inside class _ProductTabSliver build method I had wrapped the Container

Related

Content over appbar in flutter?

I just want to make UI like content over the appbar, but I couldn't. Here is my code that I got from web. how to make the listview top of appbar and remove the card.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Sample2(),
),
);
}
class Sample2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
child: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverAppBar(expandedHeight: 200),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) => ListTile(
title: Text("Index: $index"),
),
),
)
],
),
),
);
}
}
class MySliverAppBar extends SliverPersistentHeaderDelegate {
final double expandedHeight;
MySliverAppBar({#required this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
Image.network(
"https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
fit: BoxFit.cover,
),
Center(
child: Opacity(
opacity: shrinkOffset / expandedHeight,
child: Text(
"MySliverAppBar",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 23,
),
),
),
),
Positioned(
top: expandedHeight / 2 - shrinkOffset,
left: MediaQuery.of(context).size.width / 4,
child: Opacity(
opacity: (1 - shrinkOffset / expandedHeight),
child: Card(
elevation: 10,
child: SizedBox(
height: expandedHeight,
width: MediaQuery.of(context).size.width / 2,
child: FlutterLogo(),
),
),
),
),
],
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
Using stack is not the best way I think. May be I am wrong. but I think customScrollView and sliverappbar can do this. but I didn't get any tutorial or youtube videos. Most of the examples are listview below the appbar. I just want content over the appbar at first time, when it scrolls it should go below the appbar.
This will help you to hide appbar on scroll
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text('Sameple text'),
pinned: true,
floating: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: <Tab>[
Tab(text: 'ONE'),
Tab(text: 'TWO'),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Text(
"xyz",
style: TextStyle(fontSize: 60),
),
),
Text("xyz"),
],
),
),
);
}

Flutter NestedScrollView with TabBarView scrolls way too much when the body content is less

Here is the scenario -
Need TabBarView as user can swipe to change the screen.
Want to load more items when user scrolls to the bottom of the screen.
The first code is the NestedScrollView with TabBarView which has two tabs containing listview with 4 items. Even though the body height is less than screen height the body scrolls. I understand the default height is set to view port height but if I want achieve point number 2, I cant since the scroll is way too much. Is there a way to wrap the body to the height of the content?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.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: 'Nested Scroll Demo with TabBarView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NestedScrollViewTest(),
);
}
}
class NestedScrollViewTest extends StatelessWidget {
const NestedScrollViewTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
forceElevated: true,
elevation: 2.0,
primary: true,
pinned: true,
stretch: true,
backgroundColor: Colors.white,
expandedHeight: 500,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
background: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 4,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.red,
),
),
),
SizedBox(
height: 5,
),
Expanded(
flex: 5,
child: Container(
color: Colors.amber,
),
),
SizedBox(
height: 5,
)
],
),
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
// shrinkWrap: true, // even with this it is not working.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 4,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
In the second code, I am using a CustomScrollView instead. Here since there is no SliverTabBarView, I am using a SliverFillRemaining widget to wrap the TabBarView and place it in the CustomScrollView. Even here the body scrolls way too much since SliverFillRemaining default height is view port height. Without using the TabBarView the CustomScrollView wraps the body based on the height of the content but I need TabBarView.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.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: 'Nested Scroll Demo with TabBarView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CustomScrollViewTest(),
);
}
}
class CustomScrollViewTest extends StatelessWidget {
const CustomScrollViewTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: CustomScrollView(
slivers: [
SliverAppBar(
forceElevated: true,
elevation: 2.0,
primary: true,
pinned: true,
stretch: true,
backgroundColor: Colors.white,
expandedHeight: 500,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
background: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 4,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.red,
),
),
),
SizedBox(
height: 5,
),
Expanded(
flex: 5,
child: Container(
color: Colors.amber,
),
),
SizedBox(
height: 5,
)
],
),
),
),
SliverFillRemaining(
// hasScrollBody: false,
child: TabBarView(
children: _tabs.map((String name) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
itemCount: 4,
);
}).toList(),
),
)
],
),
),
);
}
}
Steps I have tried,
If I change the property hasScrollBody: false in SliverFillRemaining, I get the error -
RenderViewport does not support returning intrinsic dimensions.
If I use SliverToBoxAdapter instead of SliverFillRemaining then I get this error since TabBarView height is dependent on the parent.
Horizontal viewport was given unbounded height.
Is there a way to wrap the content based on the body height keeping TabBarView in mind.
Edit: adding images -
Initial
start scroll
end scroll

CustomScrollView: Body scrolls under SliverAppBar

Flutter DartPad
I have multiple SliverAppBar's within a CustomScrollView, the body of the screen is within the SliverFillRemaining.
The Top SliverAppBar is pinned
The Middle SliverAppBar is an image and will collapse as the user scrolls
Bottom SliverAppBar is a TabBar that is pinned and will stay under the First SliverAppBar after the Image has fully collapsed
The current experience is that when you scroll initially, the body scrolls under the lowest SliverAppBar. I have already tried to use SliverOverlapAbsorber/Injector, but that just adds a space to the top of the body so that the spaces get overlapped rather than the body, but this is not what I want.
I want the body and the SliverAppBars to scroll together until the Middle SliverAppBar has collapsed completely, then I want the body to scroll.
I have been working on this for hours, How do you stop the body from being overlapped on scroll?
To achieve this kind of scrolling behaviour it's easier to use NestedScrollView and note that the main appbar isn't in the slivers anymore
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('title'),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {},
),
),
body: NestedScrollView(
floatHeaderSlivers: true,
physics: const BouncingScrollPhysics(),
body: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: List.generate(
1000,
(index) => Text('Tab One: $index'),
),
),
),
SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: List.generate(
1000,
(index) => Text('Tab Two: $index'),
)),
)
],
),
headerSliverBuilder: (context, innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
pinned: true,
floating: false,
elevation: 0,
toolbarHeight: 0,
collapsedHeight: null,
automaticallyImplyLeading: false,
expandedHeight: MediaQuery.of(context).size.height * .4,
flexibleSpace: const FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: Placeholder()),
titleSpacing: 0,
primary: false,
),
SliverAppBar(
pinned: true,
forceElevated: true,
primary: false,
automaticallyImplyLeading: false,
expandedHeight: 50,
collapsedHeight: null,
toolbarHeight: 50,
titleSpacing: 0,
title: Align(
alignment: Alignment.topCenter,
child: TabBar(
controller: _tabController,
isScrollable: true,
tabs: [
const Text('Tab One'),
const Text('Tab Two'),
]),
),
),
];
},
),
);
}
}

How to stack Circle Avatar over SliverAppBar and SliverList?

I want to achieve the above design
I did somewhat like this using slivers
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: DefaultTabController(
length: 4,
child: NestedScrollView(
body: TabBarView(children: [
//pages of tabBar
]),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
//for the pinned appBar
elevation: 0.0,
backgroundColor: Colors.red,
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(backgroundColor: Colors.red.withOpacity(0.4),
child: Icon(Icons.arrow_back,color: Colors.white,),
),
),
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
"https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&h=350",
fit: BoxFit.cover,
),
),
expandedHeight: 150,
),
SliverList(delegate: SliverChildListDelegate([
// for bio and other stuff
])),
SliverAppBar(
toolbarHeight: 0.0,
primary: false,
pinned: true,
bottom: TabBar(tabs: [
// tab bars
],),
)
];
},
),
),
),
);
}
}
I tried adding stack inside my sliver list with positioned but Circle Avatar gets display behind the app bar.
How can we achieve such a design or what could be an alternative way to achieve it.

Flutter CustomScrollView scrolls when there are not enough items

Having an issue with scrolling when using slivers and a sliver appbar. How can I prevent the scrollview from scrolling when no scroll is needed like in the video. And if there are enough items for scrolling it should scroll (That works perfectly)
I followed this medium post. And you can see that he has the same problem.
https://medium.com/#diegoveloper/flutter-collapsing-toolbar-sliver-app-bar-14b858e87abe
https://youtu.be/l1EwM9GAfxw
class HomeScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
brightness: Brightness.dark,
backgroundColor: Colors.amber.withOpacity(0.5),
expandedHeight: 166,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 80,
width: 80,
child: Placeholder(),
),
Row(
children: [
Text(
'sdalkf',
),
],
),
],
),
),
),
),
SliverPersistentHeader(
delegate: SliverAppBarDelegate(
MediaQuery.of(context).padding.top,
Container(
color: Colors.amber.withOpacity(0.5),
child: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('sdfaklladsjfkladslkf\nsadjflkasjklfs\nsdkjlfjlkadslfjk'),
],
),
),
),
),
pinned: true,
),
],
body: GridView.builder(
itemCount: 3,
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 0.68),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
color: Colors.black54,
),
),
),
),
);
}
}
class SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final double topSafeArea;
_SliverAppBarDelegate(this.topSafeArea, this.child);
#override
double get minExtent => 105 + topSafeArea;
#override
double get maxExtent => 105 + topSafeArea;
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
child: child,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
Related to:
Flutter SliverAppBar with Tabs overlays content
The above link is not a sollution but a temp fix
Set physics behavior of ScrollView to AlwaysScrollableScrollPhysics
from documentation:
/// Scroll physics that always lets the user scroll.
///
/// This overrides the default behavior which is to disable scrolling
/// when there is no content to scroll. It does not override the
/// handling of overscrolling.