How to stack Circle Avatar over SliverAppBar and SliverList? - flutter

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.

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'),
]),
),
),
];
},
),
);
}
}

Custom Scroll View getting scrolled under Sliver Persistent Header

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

Flutter: sliverAppBar resize issue on list scroll

I want to make sliverAppBar resized while scrolling list below it.
Now there are two issues:
1) sliverAppBar doesn't resize while I am scrolling list (screenshot)
2) I can't find example/solution how to resize child content of sliverAppBar when it change height (screenshot)
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar( // <-- how to resize on scrolling ListView?
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: SafeArea(
child: Column(
children: [
Row( // <-- how to make it flexible/resizable?
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/user.png',
fit: BoxFit.cover,
width: 120,
)
],
),
],
),
)
),
SliverFillRemaining(
child: ListView.builder(
shrinkWrap: false,
itemCount: widget.europeanCountries.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.europeanCountries[index]),
);
},
),
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Use [FlexibleSpaceBar] and [SliverList]
[SliverList] can change the size of [sliverAppBar] when scrolling the list
[FlexibleSpaceBar] prevents border overflow
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: true,
pinned: true,
flexibleSpace: LayoutBuilder(
builder: (context, bc) {
double size = min(
// bc.constrainHeight() - MediaQuery.of(context).padding.top,
bc.constrainHeight(),
120);
return FlexibleSpaceBar(
centerTitle: true,
// title: Container(
// width: size,
// height: size,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// image: DecorationImage(
// image: NetworkImage(
// 'https://i.loli.net/2019/08/09/OvVzMqpF3jmI8lE.jpg'),
// fit: BoxFit.cover,
// ),
// ),
// ),
background: Center(
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
'https://i.loli.net/2019/08/09/OvVzMqpF3jmI8lE.jpg'),
fit: BoxFit.cover,
),
),
),
),
);
},
),
),
SliverList(
//
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text("Text"),
);
},
childCount: 100,
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}