Flutter animated SliverAppBar with Animated Positioned Image - flutter

I am trying to replicate this animation with my appBar:
I know I can use SliverAppBar and simply animate the textSize. But how would I implement the logic for the image? It moves to the right and slightly shrinks.
This is what I have for the text:
SliverAppBar(
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text('Test', textScaleFactor: 1),
),
pinned: true,
),
Any idea how I could solve this?

You play with SliverPersistentHeaderDelegate
class AppSliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final t = shrinkOffset / maxExtent;
return Stack(
children: [
Align(
alignment: Alignment(0, .7), //perhaps it should also use lerp
child: Text(
"Title",
style: TextStyle(fontSize: ui.lerpDouble(34, 14, t)),
),
),
Align(
alignment:
Alignment.lerp(Alignment.topCenter, Alignment.topRight, t)!,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.settings),
),
)
],
);
}
#override
double get maxExtent => 200;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
And used on
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: AppSliverPersistentHeaderDelegate(),
),
SliverToBoxAdapter(
child: Container(
height: 12222,
color: Colors.red,
),
)
],
),
),
);
}
}

You can try using AnimatedPositioned class which flutter already provide .
Check this link
https://api.flutter.dev/flutter/widgets/AnimatedPositioned-class.html
You can use it and the change the position and size depending on a specific action .

Related

how to make a stack widget take full screen on ios in flutter

I am trying to make an audio player app,
and I want to make the player screen fit the whole screen size.
However, the padding at the top and at the bottom doesn't help.
I tried to remove the SafeArea from bottomNavigationBar and other widgets and it didn't work.
How can I handle this?
Image of the player:
(the gray color padding doesn't let the image stretch to the end)
the code of the player:
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xff1c1c1e),
body: GetBuilder<OverlayHandler>(
builder: (getContext) {
if (!Get.find<OverlayHandler>().inPipMode) {
return Stack(
children:[
Container(...)
]
); // player at full screen
} else {
return Stack(...); // player at PiP mode
}
}
)
);
}
the code of the main screen widget:
Widget build(BuildContext context) {
return GetBuilder<NavigationController>(
builder: (controller) {
return Scaffold(
body: SafeArea(
// bottom option of this SafeArea doesn't affect the player size
child: IndexedStack(
index: controller.tabIndex,
children: const [
...
],
),
),
bottomNavigationBar: SafeArea(
// bottom option of this SafeArea doesn't affect the player size
child: SizedBox(
height: 80,
child: BottomNavigationBar(
items: [
...
],
),
),
),
);
}
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
TextEditingController controller = TextEditingController();
bool hasHash = false;
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(
"https://cdn.pixabay.com/photo/2016/09/10/11/11/musician-1658887_1280.jpg",
),
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 300,
width: double.infinity,
color: Colors.black.withOpacity(.7),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(
Icons.skip_previous_rounded,
size: 55,
color: Colors.white,
),
Icon(
Icons.play_circle_fill_rounded,
size: 110,
color: Colors.white,
),
Icon(
Icons.skip_next_rounded,
size: 55,
color: Colors.white,
),
],
),
),
),
],
),
);
}
}
Android screenshot
iOS screenshot
Try removing the Scaffold()'s background color and add extendBody: true, or set the height of the container to height: double.infinity, or inside the stack just add and empty container with height as height: double.infinity,

How to make Column scrollable when overflowed but use expanded otherwise

I am trying to achieve an effect where there is expandable content on the top end of a sidebar, and other links on the bottom of the sidebar. When the content on the top expands to the point it needs to scroll, the bottom links should scroll in the same view.
Here is an example of what I am trying to do, except that it does not scroll. If I wrap a scrollable view around the column, that won't work with the spacer or expanded that is needed to keep the bottom links on bottom:
import 'package:flutter/material.dart';
const 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
State<MyWidget> createState() {
return MyWidgetState();
}
}
class MyWidgetState extends State<MyWidget> {
List<int> items = [1];
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
items.add(items.last + 1);
});
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
if (items.length != 1) items.removeLast();
});
},
),
],
),
for (final item in items)
MyAnimatedWidget(
child: SizedBox(
height: 200,
child: Center(
child: Text('Top content item $item'),
),
),
),
Spacer(),
Container(
alignment: Alignment.center,
decoration: BoxDecoration(border: Border.all()),
height: 200,
child: Text('Bottom content'),
)
],
);
}
}
class MyAnimatedWidget extends StatefulWidget {
final Widget? child;
const MyAnimatedWidget({this.child, Key? key}) : super(key: key);
#override
State<MyAnimatedWidget> createState() {
return MyAnimatedWidgetState();
}
}
class MyAnimatedWidgetState extends State<MyAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController controller;
#override
initState() {
controller = AnimationController(
value: 0, duration: const Duration(seconds: 1), vsync: this);
controller.animateTo(1, curve: Curves.linear);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return SizedBox(height: 200 * controller.value, child: widget.child);
});
}
}
I have tried using a global key to get the size of the spacer and detect after rebuilds whether the spacer has been sized to 0, and if so, re-build the entire widget as a list view (without the spacer) instead of a column. You also need to listen in that case for if the size shrinks and it needs to become a column again, it seemed to make the performance noticeably worse, it was tricky to save the state when switching between column/listview, and it seemed not the best way to solve the problem.
Any ideas?
Try implementing this solution I've just created without the animation you have. Is a scrollable area at the top and a persistent footer.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
home: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("My AppBar"),
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
// Your scrollable widgets here
Container(
height: 100,
color: Colors.green,
),
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 100,
color: Colors.red,
),
],
),
),
),
Container(
child: Text(
'Your footer',
),
color: Colors.blueGrey,
height: 200,
width: double.infinity,
)
],
),
),
),
);
}
}

Tab bar content is scrolling up until the status bar while creating collapsing tool bar with tabs

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.

Is it possible to have both 'expand' and 'contract' effects with the slivers in Flutter?

I have implemented a screen with the CustomScrollView, SliverAppBar and FlexibleSpaceBar like the following:
Now, I'm stuck trying to further expand the functionality by trying to replicate the following effect:
Expand image to fullscreen on scroll
Can something like this be done by using the slivers in Flutter?
Basically, I want the image in it's initial size when screen opens, but depending on scroll direction, it should animate -> contract/fade (keeping the list scrolling functionality) or expand to fullscreen (maybe to new route?).
Please help as I'm not sure in which direction I should go.
Here's the code for the above screen:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const double bottomNavigationBarHeight = 48;
#override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
home: SliverPage(),
);
}
class SliverPage extends StatefulWidget {
#override
_SliverPageState createState() => _SliverPageState();
}
class _SliverPageState extends State<SliverPage> {
double appBarHeight = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
centerTitle: true,
expandedHeight: MediaQuery.of(context).size.height * 0.4,
pinned: true,
flexibleSpace: LayoutBuilder(builder: (context, boxConstraints) {
appBarHeight = boxConstraints.biggest.height;
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedOpacity(
duration: Duration(milliseconds: 200),
opacity: appBarHeight < 80 + MediaQuery.of(context).padding.top ? 1 : 0,
child: Padding(padding: EdgeInsets.only(bottom: 2), child: Text("TEXT"))),
background: Image.network(
'https://images.pexels.com/photos/443356/pexels-photo-443356.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
fit: BoxFit.cover,
),
);
}),
),
SliverList(delegate: SliverChildListDelegate(_buildList(40))),
],
),
);
}
List _buildList(int count) {
List<Widget> listItems = List();
for (int i = 0; i < count; i++) {
listItems.add(
new Padding(padding: new EdgeInsets.all(20.0), child: new Text('Item ${i.toString()}', style: new TextStyle(fontSize: 25.0))));
}
return listItems;
}
}
use CustomScrollView with SliverPersistentHeader
child: LayoutBuilder(
builder: (context, constraints) {
return CustomScrollView(
controller: ScrollController(initialScrollOffset: constraints.maxHeight * 0.6),
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: Delegate(constraints.maxHeight),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, i) => Container(height: 100, color: i.isOdd? Colors.green : Colors.green[700]),
childCount: 12,
),
),
],
);
},
),
the Delegate class used by SliverPersistentHeader looks like:
class Delegate extends SliverPersistentHeaderDelegate {
final double _maxExtent;
Delegate(this._maxExtent);
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
var t = shrinkOffset / maxExtent;
return Material(
elevation: 4,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset('images/bg.jpg', fit: BoxFit.cover,),
Opacity(
opacity: t,
child: Container(
color: Colors.deepPurple,
alignment: Alignment.bottomCenter,
child: Transform.scale(
scale: ui.lerpDouble(16, 1, t),
child: Text('scroll me down',
style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white)),
),
),
),
],
),
);
}
#override double get maxExtent => _maxExtent;
#override double get minExtent => 64;
#override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

Flutter How to check if Sliver AppBar is expanded or collapsed?

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