Hide the TabBar like a SliverAppBar - flutter

So there are many examples on the web where you can use a SliverAppBar that hides on scroll, and the TabBar below is still showing. I can't find anything that does it the other way around: When I scroll up I want to hide only the TabBar, keeping the AppBar persistent showing at all times. Does anyone know how to achieve this?
Here is a example with AppBar hiding (This is not what I want, just helps understand better what I want).
UPDATE
This is what I tried so far, and I thought it works, but the problem is I can't get the AppBar in the Positioned field to have the correct height (e.g. iPhone X its height is way bigger and overlaps with the tab bar).
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one. However,
// I can´t figure out how to give it the correct height.
Container(
child: Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
iconTheme: IconThemeData(
color: Colors.red, //change your color here
),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);

Here is How you can do that, the idea is to use a postframecallback with the help of a GlobalKey to precalculate the appBar height and add an exapandedHeight like below,
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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 {
TabController _tabController;
GlobalKey _appBarKey;
double _appBarHight;
#override
void initState() {
_appBarKey = GlobalKey();
_tabController = TabController(length: 3, vsync: this);
SchedulerBinding.instance.addPostFrameCallback(_calculateAppBarHeight);
super.initState();
}
_calculateAppBarHeight(_){
final RenderBox renderBoxRed = _appBarKey.currentContext.findRenderObject();
setState(() {
_appBarHight = renderBoxRed.size.height;
});
print("AppbarHieght = $_appBarHight");
}
#override
Widget build(BuildContext context) {
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
expandedHeight: _appBarHight,
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one. However,
// I can¥t figure out how to give it the correct height.
Container(
key: _appBarKey,
child: Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
backgroundColor: Colors.red,
iconTheme: IconThemeData(
color: Colors.red, //change your color here
),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);
}
}
class MyScreen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 1"),
),
);
}
}
class MyScreen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 2"),
),
);
}
}
class MyScreen3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 3"),
),
);
}
}
Edit:
After more investigation I found a solution without keys or MediaQuery "stuff" by using just SafeArea Widget . please check the following Complete code :
import 'package:flutter/material.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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 {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 3, vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
primary: true,
floating: true,
backgroundColor: Colors.blue,//.withOpacity(0.3),
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one.
// by using SafeArea it will.
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: Container(
child: SafeArea(
top: false,
child: AppBar(
backgroundColor: Colors.blue,
// iconTheme: IconThemeData(
// color: Colors.red, //change your color here
// ),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title",),
centerTitle: true,
),
),
),
),
],
),
);
}
}
class MyScreen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
child: Center(
child: Text("My Screen 1"),
),
);
}
}
class MyScreen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 2"),
),
);
}
}
class MyScreen3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 3"),
),
);
}
}

Screenshot (Android)
Screenshot (iPhone X)
Your were very close, I have just modified couple of lines. I did it without using GlobalKey and other stuff (postFrameCallback etc). It is very simple and straightforward approach.
All you need to do is replace FlutterLogo with your own widgets which are MyScreen1, MyScreen2 and MyScreen3.
Code
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
snap: true,
pinned: true,
bottom: PreferredSize(
preferredSize: Size(0, kToolbarHeight),
child: TabBar(
controller: _tabController,
tabs: [
Tab(child: Text("1")),
Tab(child: Text("2")),
Tab(child: Text("3")),
],
),
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
FlutterLogo(size: 300, colors: Colors.blue), // use MyScreen1()
FlutterLogo(size: 300, colors: Colors.orange), // use MyScreen2()
FlutterLogo(size: 300, colors: Colors.red), // use MyScreen3()
],
physics: NeverScrollableScrollPhysics(),
),
),
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: AppBar(
iconTheme: IconThemeData(color: Colors.red),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);
}
}

I think its pretty easy using nested scaffolds. where you dont need to calculate any height. Just put the tabbar inside a SilverAppBar not below the SilverAppBar.
feel free to comment if that doesnt solve your problem.
Example:
return Scaffold(
appBar: AppBar(), //your appbar that doesnt need to hide
body: Scaffold(
appBar: SilverAppBar(
pinned: false,
floating: false,
flexibleSpace: new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
new TabBar() //your tabbar that need to hide when scrolling
])
)
body: //your content goes here
)
);

Related

Issue when I try creating Sliver app bar and pageview in the same widget

I am facing issues when it comes to logic while I am working with my app
I want a sliver appBar with and a page view in the same widget when i
do that and assign a custom scroll view for each page of my pageviews I get problems but if I declared a sliver app bar on each page of the pages it works fine and at the same time I should not have a nested scroll view in my pageview widget now I don't think that I should write an app bar for each one of them when I could just write it my page view widget
any thoughts
this is my code
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: true,
bottom: false,
child:
NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver:
SliverAppBar(
// toolbarHeight: 50,
backgroundColor: Color.fromRGBO(255, 255, 255, 1),
title: const Text(
'Partnerna',
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
color: linerColorUp),
),
actions: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 0, horizontal: 10),
child: Container(
alignment: Alignment.centerRight,
// color: Colors.amber,
// width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
CircelCntainerBackgroundWidget(
backGroundColor: buttonbackgroundcolor,
child: Padding(
padding: EdgeInsets.all(3),
child: FaIcon(
FontAwesomeIcons.squarePlus,
size: 18,
),
)),
SizedBox(
width: 20,
),
CircelCntainerBackgroundWidget(
backGroundColor: buttonbackgroundcolor,
child: Icon(
Icons.notification_add_rounded,
size: 21,
)),
SizedBox(
width: 20,
),
// SizedBox(width: 10,),
CircleAvatar(
radius: 14,
backgroundImage: NetworkImage(
"https://th.bing.com/th/id/OIP.2tWiaVWFJjvC1HhJQuTtCwHaHt?w=173&h=181&c=7&r=0&o=5&pid=1.7"),
),
],
),
),
)
],
// expandedHeight: 200,
floating: true,
pinned: false,
snap: true,
forceElevated: innerBoxIsScrolled,
elevation: 0,
),
)];
},
body:
PageView(
children: [
HomeScreen(),
ConnectScreen(),
ConnectRequestScreen(),
MessagScrenn(),
SettingScreen(),
],
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
onPageChanged: onPageChange,
),
),),
try to make a custom Scaffold same as your code, to wrap each of the PageView children.
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: PageView(
children: [
Page1(),
Page2(),
Page3(),
]
),
);
}
}
class Page1 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return CustomScaffold(
body: (...)
);
}
}
class Page2 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return CustomScaffold(
body: (...)
);
}
}
class Page3 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return CustomScaffold(
body: (...)
);
}
}
class CustomScaffold extends StatefulWidget {
final Widget body;
const CustomScaffold({ Key? key, required this.body}) : super(key: key);
#override
_CustomScaffoldState createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled){
(...)
},
body: widget.body,
),
);
}
}

How can I replace appBar with SliverAppBar in my code?

class _HomepageState extends State<Homepage> {
int pageNum = 0;
final pages = [
TodayPage(), /*HistoryPage()*/
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Today Medicine List'),
titleTextStyle: TextStyle(
fontSize: 30.0, fontWeight: FontWeight.w400, color: Colors.white),
elevation: 5.0,
),
body: pages[pageNum],
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blueAccent,
onPressed: () {
Addingpage();
},
child: const Icon(CupertinoIcons.add)),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
bottomNavigationBar: ButtonBottomAppBar(),
);
}
This is code what I have. I want to replace appBar with SliverAppBar.
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('Today Medicine List'),
// titleTextStyle: TextStyle(
// fontSize: 30.0, fontWeight: FontWeight.w400, color: Colors.white),
// elevation: 5.0,
// ),
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('Today Medicine List'),
floating: true,
flexibleSpace: Placeholder(),
expandedHeight: 200,
),
],
),
pages[pageNum],
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blueAccent,
onPressed: () {
Addingpage();
},
child: const Icon(CupertinoIcons.add)),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
bottomNavigationBar: ButtonBottomAppBar(),
);
}
So I put CustomScrollView slivers code at body part, and page[pageNum] doesn't work properly.
How can I make it work both CustomScrollView() and page[pageNum] at one body:
The floatingActionButton is used with the Scaffold.
new Scaffold(
body: new CustomScrollView(
slivers: <Widget>[
new SliverAppBar(...),
...
],
),
floatingActionButton: new FloatingActionButton(...),
);
You can also use a Stack above the CustomScrollView and the FloatingActionButton (in a Positioned widget) as well.
Try replacing the SliverAppBar this way
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Today Medicine List'),
background: FlutterLogo(),
),
expandedHeight: 200,
),
SliverToBoxAdapter(
child: Container(
height: 500,
color: Colors.red,
child: pages[pageNum],
),
),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blueAccent,
onPressed: () {
Addingpage();
},
child: const Icon(CupertinoIcons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
bottomNavigationBar: ButtonBottomAppBar(),
);
}
}

Flutter implementing instagram profile page

in this below code i want to make a simple Instagram profile page, my problem is adding tabs and TabBarView in side Sliver which i get some error
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: InstagramProfile(),
);
}
}
class InstagramProfile extends StatefulWidget {
#override
_InstagramProfileState createState() => _InstagramProfileState();
}
class _InstagramProfileState extends State<InstagramProfile> with TickerProviderStateMixin {
TabController tab;
#override
void initState() {
super.initState();
tab = TabController(length: 4, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
backgroundColor: Colors.grey[100],
title: Container(
color: Colors.grey[100],
height: 200,
width: MediaQuery.of(context).size.width,
child: Center(
child: Text(
"Head",
style: TextStyle(color: Colors.black),
),
),
),
),
SliverAppBar(
pinned: true,
backgroundColor: Colors.white,
title: Container(
color: Colors.white,
constraints: BoxConstraints.expand(height: 50),
child: TabBar(
controller: tab,
tabs: [
Tab(text: "Event"),
Tab(text: "History"),
Tab(text: "Page"),
Tab(text: "Group"),
]),
),
),
SliverPadding(
padding: const EdgeInsets.all(0),
sliver: SliverList(
delegate: SliverChildListDelegate([
TabBarView(
controller: tab,
children: [
Container(
child: Text("Articles Body"),
),
Container(
child: Text("User Body"),
),
Container(
child: Text("User Body"),
),
Container(
child: Text("User Body"),
),
],
),
]),
),
)
],
),
);
}
}
Error:
RenderBox was not laid out: _RenderScrollSemantics#6cf1a relayoutBoundary=up8 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1785 pos 12: 'hasSize'
'package:flutter/src/rendering/sliver_multi_box_adaptor.dart': Failed assertion: line 545 pos 12: 'child.hasSize': is not true.
The relevant error-causing widget was:
SliverList file:///C:/Users/mahdi/AndroidStudioProjects/social_calendar_pro/lib/tabbar.dart:57:21
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: InstagramProfile(),
);
}
}
class InstagramProfile extends StatefulWidget {
#override
_InstagramProfileState createState() => _InstagramProfileState();
}
class _InstagramProfileState extends State<InstagramProfile>
with TickerProviderStateMixin {
TabController tab;
#override
void initState() {
super.initState();
tab = TabController(length: 4, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: CustomScrollView(
shrinkWrap: true,
slivers: <Widget>[
SliverAppBar(
backgroundColor: Colors.grey[100],
title: Container(
color: Colors.grey[100],
height: 200,
width: MediaQuery.of(context).size.width,
child: Center(
child: Text(
"Head",
style: TextStyle(color: Colors.black),
),
),
),
),
SliverAppBar(
pinned: true,
title: Container(
// color: Colors.black12,
constraints: BoxConstraints.expand(height: 50),
child: TabBar(controller: tab, tabs: [
Tab(text: "Event"),
Tab(text: "History"),
Tab(text: "Page"),
Tab(text: "Group"),
]),
),
),
SliverPadding(
padding: const EdgeInsets.all(10),
sliver: SliverList(
delegate: SliverChildListDelegate([
Container(
child: TabBarView(
controller: tab,
children: [
Container(
child: Text("Articles Body"),
),
Container(
child: Text("User Body"),
),
Container(
child: Text("User Body"),
),
Container(
child: Text("User Body"),
),
],
),
height: 600,
)
]),
),
)
],
),
);
}
}
I hope this will work for you

How to stop pinned SliverAppBar from covering floating SliverPersistentHeader

I'm learning Flutter and I am currently trying to make a home page with a cool scrolling effect. I'm trying to implement a CustomScrollView with 3 elements: a SliverAppBar, a horizontal scrolling list and a SliverList. The first two were easy enough and after some struggling I managed to implement the horizontal scrolling list by using a SliverPersistentHeader.
However, I ran into an issue. I want the SliverAppBar to be pinned and the SliverPersistentHeader containing the horizontal scrolling list to be floating. Everything works fine, except the floating element gets covered by the pinned one when scrolling back up after scrolling down. I basically want the floating element to "know" there is another element above it and offset itself when scrolling up.
You can see the issue here, alongside my code:
https://dartpad.dev/32d3f2a890d4a676decb014744fcc9ba
Make sure you click and drag to scroll in order to see the issue!
How can I fix this? Is there anything I am missing that causes this issue?
Thank you for your time!
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
// I had to change this class to a StatefulWidget to be able to listen to the scroll event
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
// Here I declared the ScrollController for the CustomScrollView
ScrollController _controller;
// And here is a boolean to check when the user scrolls up or down the view
bool sliverPersistentHeader = false;
#override
void initState() {
super.initState();
// The ScrollController is initialized in the initState and listens for when the user starts scrolling up and changes the boolean value accordingly
_controller = ScrollController();
_controller.addListener(() {
if (_controller.position.userScrollDirection == ScrollDirection.reverse) {
setState(() {
sliverPersistentHeader = false;
});
} else {
setState(() {
sliverPersistentHeader = true;
});
}
});
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverAppBar(
floating: true,
pinned: true,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text('App Title'),
),
),
SliverPersistentHeader(
// The SliverPersisitentHeader checks the boolean value and either pins or unpins the the Header
pinned: sliverPersistentHeader ? true : false,
delegate: CustomSliver(
expandedHeight: 150.0,
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
height: 50.0,
color: Colors.amber,
),
),
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Tab1'),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Tab2'),
),
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Tab3'))
],
currentIndex: 0,
),
);
}
}
class CustomSliver extends SliverPersistentHeaderDelegate {
final double expandedHeight;
CustomSliver({#required this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Scrollbar(
child: Container(
color: Theme.of(context).canvasColor,
padding: EdgeInsets.fromLTRB(10.0, 15.0, 0, 5.0),
child: ListView.separated(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(right: 10.0, top: 10.0, bottom: 10.0),
child: Container(
width: 100,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.all(Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.16),
offset: Offset(0, 3.0),
blurRadius: 6.0),
]),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.navigation),
Text(
'Category',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(width: 5.0);
},
)),
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => 150.0;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
The only thing I didn't do was to animate the SliverPersistentHeader into view, hopefully, you can achieve this yourself. I'm sure there are other ways to achieve this, but this solution should work for you.
For anyone that is still looking for a solution. You can try to implement this using a NestedScrollView and SliverOverlapAbsorber.
The following code will demonstrate this
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, isScrolled) => [
SliverAppBar(
pinned: true,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text('App Title'),
),
),
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverPersistentHeader(
floating: true,
delegate: CustomSliver(
expandedHeight: 150.0,
),
),
),
],
body: ListView.builder(
itemCount: 100,
itemBuilder: (_, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Container(
height: 50.0,
color: Colors.amber,
),
),
),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Tab1'),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Tab2'),
),
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Tab3'))
],
currentIndex: 0,
),
);
}
}
class CustomSliver extends SliverPersistentHeaderDelegate {
final double expandedHeight;
CustomSliver({required this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Scrollbar(
child: Container(
color: Theme.of(context).canvasColor,
padding: EdgeInsets.fromLTRB(10.0, 15.0, 0, 5.0),
child: ListView.separated(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(right: 10.0, top: 10.0, bottom: 10.0),
child: Container(
width: 100,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.all(Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.16),
offset: Offset(0, 3.0),
blurRadius: 6.0),
]),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.navigation),
Text(
'Category',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(width: 5.0);
},
)),
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => 150.0;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
Notice the floatHeaderSlivers: true. On the NestedScrollView widget.
Notice the SliverOverlapAbsorber
Some resources to help
https://github.com/flutter/flutter/issues/62194#issuecomment-664625589
https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
https://api.flutter.dev/flutter/widgets/SliverOverlapAbsorber-class.html

My sliverappbar doesn't expand when i start scrolling back up, it only expands when i get to the top of the list again

I have a nestedsrcollview with a sliverappbar that shrinks when i scroll down as expected but the app bar doesn't expanded when i scroll back up, not until i get to the top(to the first item on the list) and i know this is no right after looking at examples from the docs. It should expand or contract at the moment you start scrolling and not when you're done scrolling up or down
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App>
with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(
child: Container(
child: Icon(//icon),
),
),
Tab(
child: Container(
child: Icon(//icon),
),
),
Tab(
child: Container(
child: Icon(
//icon,
),
),
),
Tab(
child: Container(
child: Icon(
//icon,
),
),
),
];
final List tabChildren = [
Child1(
key: PageStorageKey<String>('1'),
),
Child2(
key: PageStorageKey<String>('2'),
),
Child3(
key: PageStorageKey<String>('3'),
),
Child4(
key: PageStorageKey<String>('4'),
),
];
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: myTabs.length)
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
title: Text(
'Dummy text',
style: Theme.of(context)
.textTheme
.title
.copyWith(color: Colors.white),
),
pinned: true,
floating: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
SafeArea(
top: false,
bottom: false,
child:
tabChildren[0], // listView
),
SafeArea(
top: false,
bottom: false,
child:
tabChildren[1], // listView
),
SafeArea(
top: false,
bottom: false,
child:
tabChildren[2], // listView
),
SafeArea(
top: false,
bottom: false,
child:
tabChildren[3], // listView
),
],
),
),
);
}
}
Set the following attributes.
snap: true,
floating: true
Example:
new SliverAppBar(
title: Text(
'Dummy text',
style: Theme.of(context)
.textTheme
.title
.copyWith(color: Colors.white),
),
floating: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
Two attributes control the SliverAppBar's Action. pinned & floating
You have set both the pinned and floating set to true, even though it is not wrong, you can never see the actual Sliver Motion when both are set to true.
To actually get what you are calling as "expanded effect" set only floating to true. If that is not what you are looking for, probably a Image or GIF as to what is your expected output will help in achieving the correct code.