Related
I want to hide the app bar and the widget at the bottom right away as soon as the screen scrolls. Also, I want to show what I hid as soon as I scroll to the top.
Also, because I have to put a widget under the Appbar, the widget is located in the bottom, and I put the tabbar under the Sliver Persistent Header widget.
The way I did it is to scroll as high as the app bar to hide it.
The code I wrote
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
bottom: false,
child: NestedScrollView(
key: _nestedScrollViewGlobalKey,
controller: _scrollController,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
backgroundColor: Colors.red.withOpacity(0.5),
elevation: 0.0,
title: Text('123123213'),
floating: true,
pinned: false,
snap: true,
// forceElevated: innerBoxIsScrolled,
expandedHeight: (BDSStyle.appBarHeight * 2),
toolbarHeight: (BDSStyle.appBarHeight * 2),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: Container(
height: BDSStyle.appBarHeight,
color: Colors.yellow.withOpacity(0.4),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: TabBarDelegate(_tabController),
),
];
},
body: _tabBarView(),
),
),
);
}
class TabBarDelegate extends SliverPersistentHeaderDelegate {
final TabController tabController;
const TabBarDelegate(this.tabController);
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return BDSTabBar(
controller: tabController,
tabs: List.generate(PlaceCategory.values.length, (index) {
return PlaceCategory.values[index].koreanName;
}),
isScrollable: true,
);
}
#override
double get maxExtent => BDSStyle.tabBarHeight;
#override
double get minExtent => BDSStyle.tabBarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
video link
Code Execution Image
The way I want to implement it
May be this code may help you.
DefaultTabController(
length: 2,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text("Application"),
floating: true,
pinned: true,
snap: true,
bottom: TabBar(
tabs: <Tab>[
Tab(text: "T"),
Tab(text: "B"),
], // <-- total of 2 tabs
),
),
];
},
body: TabBarView(
children: <Widget>[
Center(
child: Text(
'T Tab',
style: TextStyle(fontSize: 30),
)),
Center(
child: Text(
'B Tab',
style: TextStyle(fontSize: 30),
)),
],
),
),
),
)
Here is the case, the first sliverAppBar pins to the top correctly when scrolling. Within customScrollView are two tabs that have its' own scroll view. Inside the first tab, is another sliverAppBar that is supposed to pin under the first one. However, it slides beneath the first sliverAppBar. Now since our complex view does not allow only one customScrollView to fix the problem, is there any other way?
See image here (Notice how first AppBar overlaps the second AppBar)
Desired Effect Before Scroll
Desired Effect After Scroll
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Test(),
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final List<String> _tabs = ['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
title: const Text('1st App Bar'),
pinned: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
];
},
body: TabBarView(
children: [
CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 3,
),
),
),
const SliverAppBar(
toolbarHeight: 150,
pinned: true,
backgroundColor: Colors.purple,
title: Text('2nd App Bar'),
),
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: 20,
),
),
),
],
),
Placeholder(),
],
),
),
),
);
}
}
We can use Scaffold to hold 1st appBar. And inside tab we can make 2nd appBar as pinned. Let me know if you wish any changes.
Output
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> with SingleTickerProviderStateMixin {
static const List<Tab> myTabs = <Tab>[
Tab(text: 'Tab1'),
Tab(text: 'Tab2'),
];
late TabController controller;
#override
void initState() {
super.initState();
controller = TabController(length: myTabs.length, vsync: this);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Appbar 1"),
bottom: TabBar(
controller: controller,
tabs: myTabs,
),
),
body: TabBarView(
controller: controller,
children: [
CustomScrollView(
slivers: [
SliverAppBar(
title: Text("AppBar 2"),
pinned: true,
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 213,
),
),
),
],
),
Container(
color: Colors.pink,
),
],
),
);
}
}
I think u can use SliverPersistentHeader and SliverAppBar.
I am trying to implement a collapsing toolbar with tabs. While scrolling through the content of the tab are not sticking to the bottom of the tab, rather it's scrolling up until the status bar.
I am not sure what exactly I am doing wrong. I'd like to get your help to solve this issue.
Here is the code snippet:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mobile/src/data/model/order_response.dart';
import 'package:mobile/src/widget/order_view.dart';
class OrderDetailPage extends StatefulWidget {
final OrderResponse item;
OrderDetailPage(this.item);
#override
_OrderDetailPageState createState() => _OrderDetailPageState();
}
class _OrderDetailPageState extends State<OrderDetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.black12,
elevation: 0.0,
expandedHeight: 230,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: Align(
alignment: Alignment.bottomCenter,
child: Container(
child: Column(
children: [
SizedBox(
height: 90,
),
OrderListItemView(widget.item, null)
],
),
),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
TabBar(
labelColor: Colors.black87,
unselectedLabelColor: Colors.grey,
tabs: [
new Tab(text: "Instant Match"),
new Tab(text: "Requests"),
],
),
),
)
];
},
body: new TabBarView(
children: <Widget>[
Text(
'This content should stick to the bottom of tab bar',
style: TextStyle(fontSize: 24),
),
Text('This content should stick to the bottom of tab bar')
],
)),
),
);
}
void _onClickMenu(String value) {}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
Current output:
In the desired output the content should stick to the bottom of tab bar.
Iam trying to implement a appbar like this
When scrolling down I need to hide the search bar alone and pin the row and the tabs on the device top. Which is like
And when we scroll down the all the three rows needs to be displayed.
Using SliverAppBar with bottom property tabs are placed and pinned when scrolling, but a row above it should be pinned at the top above the tabbar. Im not able to add a column with the row and tabbar because of preferedSizeWidget in bottom property. Flexible space bar also hides with the appbar so I cannot use it. Does anyone know how to make this layout in flutter.
Please try this.
body: Container(
child: Column(
children: <Widget>[
Container(
// Here will be your AppBar/Any Widget.
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
// All your scroll views
Container(),
Container(),
],
),
),
),
],
),
),
You could create your own SliverAppBar or you can divide them in 2 items, a SliverAppBar and a SliverPersistentHeader
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>
with SingleTickerProviderStateMixin {
TabController controller;
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
controller = TabController(
length: 3,
vsync: this,
);
}
#override
void dispose(){
super.dispose();
controller.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(color: Colors.black.withOpacity(.5), fontSize: 16),
border: InputBorder.none
)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
//This is Where you create the row and your tabBar
SliverPersistentHeader(
delegate: MyHeader(
top: Row(
children: [
for(int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
)
)
]
),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
controller: controller,
),
),
pinned: true,
),
SliverFillRemaining(
child: TabBarView(
controller: controller,
children: <Widget>[
Center(child: Text("Tab one")),
Center(child: Text("Tab two")),
Center(child: Text("Tab three")),
],
),
),
],
),
);
}
}
//Your class should extend SliverPersistentHeaderDelegate to use
class MyHeader extends SliverPersistentHeaderDelegate {
final TabBar bottom;
final Widget top;
MyHeader({this.bottom, this.top});
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).accentColor,
height: math.max(minExtent, maxExtent - shrinkOffset),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if(top != null)
SizedBox(
height: kToolbarHeight,
child: top
),
if(bottom != null)
bottom
]
)
);
}
/*
kToolbarHeight = 56.0, you override the max and min extent with the height of a
normal toolBar plus the height of the tabBar.preferredSize
so you can fit your row and your tabBar, you give them the same value so it
shouldn't shrink when scrolling
*/
#override
double get maxExtent => kToolbarHeight + bottom.preferredSize.height;
#override
double get minExtent => kToolbarHeight + bottom.preferredSize.height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false;
}
UPDATE
A NestedScollView let you have 2 ScrollViews so you can control the inner scroll with the outer (just like you want with a TabBar)
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController textController = TextEditingController();
List<String> _tabs = ['Tab 1', 'Tab 2', 'Tab 3'];
// Your tabs, or you can ignore this and build your list
// on TabBar and the TabView like my previous example.
// I don't create a TabController now because I wrap the whole widget with a DefaultTabController
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
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(
elevation: 0.0,
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(
color: Colors.black.withOpacity(.5),
fontSize: 16),
border: InputBorder.none)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
),
SliverPersistentHeader(
delegate: MyHeader(
top: Row(children: [
for (int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
))
]),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
pinned: true,
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
// You can ignore it if you're going to build your
// widgets in another Stateless/Stateful class.
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),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
onTap: () => print('$name at index $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
));
}
}
import 'dart:io';
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(
primaryColor: Colors.white,
),
home: NewsScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class NewsScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
bottom: Platform.isIOS ? false : true,
sliver: SliverAppBar(
title: Text('Tab Demo'),
elevation: 0.0,
floating: true,
pinned: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
},
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
),
);
}
}
I am using a SliverAppBar in Flutter, with a background widget.
The thing is When it's expanded, the title and icons (leading and actions) should be white in order to be seen correctly, and when it's collapsed, they should be changed to black.
Any ideas on how I can get a bool out of it? Or other ways of resolving this problem.
Thank you.
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.body,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
// I need something like this
// To determine if SliverAppBar is expanded or not.
bool isAppBarExpanded = false;
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: itemColor, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: itemColor, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: itemColor, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.body),
],
),
);
}
}
You can use LayoutBuilder to get sliver AppBar biggest height. When biggest.height = MediaQuery.of(context).padding.top + kToolbarHeight, it actually means sliver appbar is collapsed.
Here is a full example, in this example MediaQuery.of(context).padding.top + kToolbarHeight = 80.0:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var top = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// print('constraints=' + constraints.toString());
top = constraints.biggest.height;
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedOpacity(
duration: Duration(milliseconds: 300),
//opacity: top == MediaQuery.of(context).padding.top + kToolbarHeight ? 1.0 : 0.0,
opacity: 1.0,
child: Text(
top.toString(),
style: TextStyle(fontSize: 12.0),
)),
background: Image.network(
"https://images.unsplash.com/photo-1542601098-3adb3baeb1ec?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=5bb9a9747954cdd6eabe54e3688a407e&auto=format&fit=crop&w=500&q=60",
fit: BoxFit.cover,
));
})),
];
},body: ListView.builder(
itemCount: 100,
itemBuilder: (context,index){
return Text("List Item: " + index.toString());
},
),
));
}
}
Final result:
You need to use ScrollController to achieve the desired effect
try this 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: SliverExample(
bodyWidgets: Text(
'Hello Body gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg'),
backgroundWidget: Text('Hello Background'),
),
);
}
}
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.bodyWidgets,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
ScrollController _scrollController;
Color _theme ;
#override
void initState() {
super.initState();
_theme = Colors.black;
_scrollController = ScrollController()
..addListener(
() => _isAppBarExpanded ?
_theme != Colors.white ?
setState(
() {
_theme = Colors.white;
print(
'setState is called');
},
):{}
: _theme != Colors.black ?
setState((){
print(
'setState is called');
_theme = Colors.black;
}):{},
);
}
bool get _isAppBarExpanded {
return _scrollController.hasClients &&
_scrollController.offset > (200 - kToolbarHeight);
}
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
//Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: _theme, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: _theme, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: _theme, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.bodyWidgets),
],
),
);
}
}
if you are not familiar with ? : notation you can use the following
_scrollController = ScrollController()
..addListener(
(){
if(_isAppBarExpanded){
if(_theme != Colors.white){
setState(() {
_theme = Colors.white;
print('setState is called with white');
});
}
}else{
if(_theme != Colors.black){
setState(() {
_theme = Colors.black;
print('setState is called with black');
});
}
}
}
Use NestedScrollView that have innerBoxIsScrolled boolean flag that will be a decent solution for your problem something like this below
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
print("INNEER SCROLLED VI=======>$innerBoxIsScrolled");
return <Widget>[
SliverAppBar(),
]},
body:Center(child:Text("Test IT")),
);
You can use SliverLayoutBuilder to get the current SliverConstraints and read its value, to easily detect how much scrolling has occurred. This is very similar to LayoutBuilder except it's operating in the sliver-world.
If constraints.scrollOffset > 0, that means the user has scrolled at least a little bit. Using this method, if you want to do some animation/transition when scrolling, it's easy too - just get the current scrollOffset and generate your animation frame based on that.
For example, this SliverAppBar changes color when user scrolls:
SliverLayoutBuilder(
builder: (BuildContext context, constraints) {
final scrolled = constraints.scrollOffset > 0;
return SliverAppBar(
title: Text('Sliver App Bar'),
backgroundColor: scrolled ? Colors.blue : Colors.red,
pinned: true,
);
},
)
This works for me
check this line
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black:Colors.white),),
change your title to
title: innerBoxIsScrolled? Text("hello world") : Text("Good Morning",)
class _ProductsState extends State<Products> {
String image;
String title;
_ProductsState(this.image,this.title);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
leading: InkWell(
onTap: (){
},
child: Icon(
Icons.arrow_back_ios,
color: Colors.black,
),
),
backgroundColor: Colors.white,
pinned: true,
//floating: true,
stretch: true,
expandedHeight: 300.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black: Colors.white),),
background: CachedNetworkImage(imageUrl:image,fit: BoxFit.fitWidth,alignment: Alignment.topCenter,
placeholder: (context, url) => const CircularProgressIndicator(color: Colors.black,),
errorWidget: (context, url, error) => const Icon(Icons.error),),
),
),
),
];
},
body: Builder(
builder:(BuildContext context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverToBoxAdapter(
child: Container(
height: 90,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
color: Colors.green,
),
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
],
);
}
),
),
);
}
}