Design this animation using SliverAppBar flutter - flutter

Here's what I want to build but I am able to achieve this
by the following code,
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return [
SliverAppBar(
expandedHeight: 120,
floating: false,
pinned: false,
flexibleSpace: Container(
padding: EdgeInsets.all(10),
height: 160,
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: Text(
'Hello World',
)),
Padding(
padding: EdgeInsets.only(top: 16),
child: Image.asset(
'assets/images/banner.png',
),
),
],
),
),
),
];
},
body: ListView.builder(),
),
);
}
I have tried SliverAppBar using flexible and expanded, but was not able to achieve.
Update -
First element of list is going behind text field. I want to scroll only when the animation is completed

updated answer,
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
TransitionAppBar(
extent: 250,
avatar: Text("Rancho"),
title: Container(
margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.all(Radius.circular(5.0))),
child: Row(children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 20.0, right: 10.0),
child: Icon(Icons.search),
),
Expanded(
child: TextFormField(
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
cursorColor: Colors.black,
autofocus: false,
style: TextField_Style,
decoration: InputDecoration(
filled: true,
fillColor: Colors.transparent,
contentPadding:
EdgeInsets.symmetric(vertical: 10, horizontal: 15),
hintText: "Search",
border: InputBorder.none,
disabledBorder: OutlineInputBorder(
borderSide: new BorderSide(color: Colors.transparent),
borderRadius: new BorderRadius.circular(2),
),
focusedBorder: OutlineInputBorder(
borderSide: new BorderSide(color: Colors.transparent),
borderRadius: new BorderRadius.circular(2),
)),
),
)
]),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
color: Colors.blue,
child: ListTile(
title: Text("${index}a"),
));
}, childCount: 25))
],
),
);
}
.
class TransitionAppBar extends StatelessWidget {
final Widget avatar;
final Widget title;
final double extent;
TransitionAppBar({this.avatar, this.title, this.extent = 250, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SliverPersistentHeader(
pinned: true,
delegate: _TransitionAppBarDelegate(
avatar: avatar,
title: title,
extent: extent > 200 ? extent : 200
),
);
}
}
class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
final _avatarMarginTween = EdgeInsetsTween(
begin: EdgeInsets.only(bottom: 70, left: 30),
end: EdgeInsets.only(left: 0.0, top: 30.0));
final _avatarAlignTween =
AlignmentTween(begin: Alignment.bottomLeft, end: Alignment.topCenter);
final Widget avatar;
final Widget title;
final double extent;
_TransitionAppBarDelegate({this.avatar, this.title, this.extent = 250})
: assert(avatar != null),
assert(extent == null || extent >= 200),
assert(title != null);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double tempVal = 34 * maxExtent / 100;
final progress = shrinkOffset > tempVal ? 1.0 : shrinkOffset / tempVal;
print("Objechjkf === ${progress} ${shrinkOffset}");
final avatarMargin = _avatarMarginTween.lerp(progress);
final avatarAlign = _avatarAlignTween.lerp(progress);
return Stack(
children: <Widget>[
AnimatedContainer(
duration: Duration(milliseconds: 100),
height: shrinkOffset * 2,
constraints: BoxConstraints(maxHeight: minExtent),
color: Colors.redAccent,
),
Padding(
padding: avatarMargin,
child: Align(
alignment: avatarAlign,
child: avatar
),
),
Padding(
padding: EdgeInsets.only(bottom: 10),
child: Align(
alignment: Alignment.bottomCenter,
child: title,
),
)
],
);
}
#override
double get maxExtent => extent;
#override
double get minExtent => (maxExtent * 68) / 100;
#override
bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
return avatar != oldDelegate.avatar || title != oldDelegate.title;
}
}

Related

how to achieve this UI in flutter by using NestedScrollView.?

I tried to acheive from NestedScrollView by creating a sample code. But its not exactly which I want. Please help me.
I want to achieve this ui. When scrolling appbar should dock at the top. Image top should start from status bar to the fixed height 300.
After Image there would be some dynamic content like description.
Then I have to add TabBar, which will dock just below the Appbar when scroll.
Body will scroll on top of Header Image, like a overlay.
Image Attachments
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class StickyTabbarExample extends StatefulWidget {
#override
_StickyTabbarExampleState createState() => _StickyTabbarExampleState();
}
class _StickyTabbarExampleState extends State<StickyTabbarExample>
with SingleTickerProviderStateMixin {
// TabController? _tabController;
final List<String> _tabs = <String>[
"Featured",
"Popular",
];
#override
void initState() {
super.initState();
// _tabController = TabController(vsync: this, length: _tabs.length);
}
#override
void dispose() {
// _tabController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold(
body: DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) => <Widget>[
SliverAppBar(
pinned: true,
// floating: true,
toolbarHeight: 70,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.arrow_back_ios_new_sharp,
color: Colors.white,
size: 15,
),
Icon(
Icons.shop,
color: Colors.white,
size: 15,
)
]),
backgroundColor: Colors.green,
expandedHeight: 300,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
"https://images.unsplash.com/photo-1673942393203-fe61f45b4479?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80%20870w",
fit: BoxFit.cover,
width: double.maxFinite,
),
),
),
SliverToBoxAdapter(
child: Positioned.fill(
child: Transform.translate(
offset: Offset(0, -10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 30,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topRight: Radius.circular(100),
topLeft: Radius.circular(100),
),
),
),
Text("Challenge description"),
],
),
),
),
),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
TabBar(
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
width: 4,
color: Color(0xFF646464),
),
insets: EdgeInsets.only(left: 0, right: 8, bottom: 4)),
isScrollable: true,
labelPadding: EdgeInsets.only(left: 0, right: 0),
tabs: [
Tab(
child: Text(
"Tab 1",
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
"Tab 2",
style: TextStyle(color: Colors.black),
)),
],
),
),
pinned: true,
),
],
body: TabBarView(children: [TabA(), TabB()]),
),
),
);
}
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) =>
Container(
color: Colors.white,
child: _tabBar,
);
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) => false;
}
class TabA extends StatelessWidget {
const TabA({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => CustomScrollView(
// primary: false,
key: PageStorageKey<String>("Tab1"),
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
height: 80,
alignment: Alignment.center,
child: Text(
"Item $index",
style: const TextStyle(fontSize: 30),
),
),
),
// 40 list items
childCount: 40,
),
),
)
],
);
}
class TabB extends StatelessWidget {
const TabB({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => CustomScrollView(
key: PageStorageKey<String>("Tab2"),
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
height: 80,
alignment: Alignment.center,
child: Text(
"Item $index",
style: const TextStyle(fontSize: 30),
),
),
),
childCount: 40,
),
),
)
],
);
}

How to build an on tap Expandable container

So I was trying to build a user id page for my flutter app where you tap on a container and the containers height is increased and a different set of data is shown. On expanded I also wanted to add a scrollable tabview and that's second part of the problem.
the expected ui looks like thishttps://i.stack.imgur.com/62sro.gif.
I have tried Expanded and expansion tile, Can't quite achieve the output
Is there any other method to achieve this?
Welcome #Anand Pillai,
First add this line to your pubspec.yaml expandable: ^5.0.1
try this code
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late PageController _pageController;
final ExpandableController _controller = ExpandableController();
int activePage = 1;
int _counter = 0;
List<String> images = [
"https://images.pexels.com/photos/14686142/pexels-photo-14686142.jpeg",
"https://wallpaperaccess.com/full/2637581.jpg",
"https://uhdwallpapers.org/uploads/converted/20/01/14/the-mandalorian-5k-1920x1080_477555-mm-90.jpg"
];
List<Widget> indicators(imagesLength, currentIndex) {
return List<Widget>.generate(imagesLength, (index) {
return Container(
margin: const EdgeInsets.all(3),
width: 10,
height: 10,
decoration: BoxDecoration(
color: currentIndex == index ? Colors.white : Colors.blueGrey,
shape: BoxShape.circle),
);
});
}
AnimatedContainer slider(images, pagePosition, active) {
// double margin = active ? 10 : 20;
return AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
// margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(images[pagePosition]),
fit: BoxFit.cover,
)),
);
}
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
alignment: Alignment.center,
children: [imageSlider(), expandedWidget(context)],
),
),
],
));
}
Positioned expandedWidget(BuildContext context) {
return Positioned.fill(
bottom: _controller.expanded ? 0 : 60,
left: 0,
right: 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_controller.expanded
? const SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(images.length, activePage)),
ExpandableNotifier(
child: AnimatedContainer(
height: _controller.expanded ? 400 : 110.0,
width: double.infinity,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.all(15.0),
margin: _controller.expanded
? EdgeInsets.zero
: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 79, 77).withOpacity(0.8),
borderRadius: _controller.expanded
? const BorderRadius.only(
topRight: Radius.circular(15),
topLeft: Radius.circular(15),
)
: BorderRadius.circular(15.0),
),
duration: const Duration(milliseconds: 500),
child: Column(
children: <Widget>[
ScrollOnExpand(
scrollOnExpand: true,
scrollOnCollapse: false,
child: ExpandablePanel(
controller: _controller
..addListener(() {
setState(() {});
}),
theme: const ExpandableThemeData(
headerAlignment: ExpandablePanelHeaderAlignment.center,
tapBodyToCollapse: true,
iconColor: Colors.white,
),
header: Padding(
padding: const EdgeInsets.all(10),
child: Text(
"ExpandablePanel",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.white),
)),
collapsed: const Text(
"loremIpsum",
style: TextStyle(color: Colors.white),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
expanded: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (var _ in Iterable.generate(5))
const Padding(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"loremIpsum",
style: TextStyle(color: Colors.white),
softWrap: true,
overflow: TextOverflow.fade,
)),
],
),
builder: (_, collapsed, expanded) {
return Padding(
padding: const EdgeInsets.only(
left: 10, right: 10, bottom: 10),
child: Expandable(
collapsed: collapsed,
expanded: expanded,
theme: const ExpandableThemeData(crossFadePoint: 0),
),
);
},
),
),
],
),
)),
],
));
}
PageView imageSlider() {
return PageView.builder(
itemCount: images.length,
physics: _controller.expanded
? const NeverScrollableScrollPhysics()
: ScrollPhysics(),
padEnds: false,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
bool active = pagePosition == activePage;
return slider(images, pagePosition, active);
});
}
}
class _MyHomePageState extends State<MyHomePage> {
double _margin = 30, _height = 100, _width = 300;
final Text _widget1 = const Text('This is my Foo');
final Text _widget2 = const Text('This is Bar');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
// When the child is tapped, set state is called.
onTap: () {
setState(() {
_margin = _margin == 30 ? 0 : 30;
_height = _height == 100 ? 300 : 100;
_width = _width == 300 ? MediaQuery.of(context).size.width : 300;
});
},
// The custom button
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: _width,
height: _height,
curve: Curves.easeInExpo,
margin: EdgeInsets.fromLTRB(_margin, 0, _margin, _margin),
duration: Duration(milliseconds: 250),
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(8.0),
),
child: _margin == 30 ? _widget1 : _widget2,
),
),
)),
);
}
}
Simple logic is to animate the container when tapped and change the widget in it. On tap it calls setsate that sets the height, width, margin and child of the container.

In Flutter, How to make SliverAppBar respect the top safe area on Floating State when it is not primary

I have a SliverAppBar looks like this is normal state which is what I want:
but when scrolling down the app bar doesn't respect the top safe area on its floating state:
here is my build method code
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverSafeArea(
bottom: false,
sliver: SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 5),
sliver: SliverAppBar(
primary: false,
centerTitle: true,
actions: actions,
floating: true,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
title: const Text('title'),
),
),
),
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
margin: const EdgeInsets.all(20),
color: Colors.amber,
);
},
childCount: 130,
),
),
],
),
);
I think you need a custom app bar for your purpose.
something like this
class FloatingAppBar extends StatelessWidget {
const FloatingAppBar(
this.title, {
this.actions = const <Widget>[],
Key? key,
this.leading = const BackButton(),
this.height = 48,
}) : super(key: key);
final String? title;
final List<Widget> actions;
final Widget leading;
final double height;
#override
Widget build(BuildContext context) {
//final Color bgColor = isDark(context) ? Colors.grey.shade800 : Colors.white;
return Center(
child: Container(
height: height,
width: MediaQuery.of(context).size.width - 20,
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
color: isDark(context) ? Colors.grey.shade800 : Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: <BoxShadow>[
BoxShadow(
color: isDark(context) ? Colors.black54 : Colors.grey.shade500,
blurRadius: 1,
spreadRadius: 0.1,
offset: const Offset(0, 0.7),
),
],
),
padding: const EdgeInsets.all(0),
margin: const EdgeInsets.only(top: 5),
child: Row(
children: <Widget>[
leading,
Expanded(
child: Center(
child: Text(
title ?? '',
textDirection: getTextDirection(title ?? ''),
style: const TextStyle(
fontWeight: FontWeight.bold,
//color: Colors.black87,
),
),
),
),
if (actions.isEmpty)
const IconButton(
padding: EdgeInsets.all(0),
iconSize: 20,
icon: Icon(iconArrowLeft, color: Colors.transparent),
onPressed: null,
),
//
...actions
],
),
),
);
}
}

scrolling to the top when typing on TextField on Flutter

I added TextFormField on SliverPersistentHeaderDelegate. After scroll to the middle/bottom and then typing on to the textField, SliverList automatically come to the top. How do I disable this?
Full Code.
class MyWidget extends StatelessWidget {
static const String route = '/myWidget';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: MyDynamicHeader(),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
height: 200,
color: Color(Random().nextInt(0xffffffff)),
);
},
)
)
],
)
)
);
}
}
class MyDynamicHeader extends SliverPersistentHeaderDelegate {
int index = 0;
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(
builder: (context, constraints) {
final Color color = Colors.primaries[index];
final double percentage = (constraints.maxHeight - minExtent)/(maxExtent - minExtent);
if (++index > Colors.primaries.length-1)
index = 0;
return Container(
decoration: BoxDecoration(
boxShadow: [BoxShadow(blurRadius: 4.0, color: Colors.black45)],
gradient: LinearGradient(
colors: [Colors.blue, color]
)
),
height: constraints.maxHeight,
child: SafeArea(
child: Center(
child: Row(
children: <Widget>[
CircularProgressIndicator(
value: percentage,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
InkWell(
onTap: (){
print('is working');
},
child: Container(
height: 50,
width: 100,
color: Palette.orelPay,
),
),
Expanded(child: TextFormField(
//controller: searchController,
decoration: InputDecoration(
contentPadding:
EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
hintText: "Search on OrelBuy",
border: OutlineInputBorder(
borderSide:
BorderSide(color: Palette.background, width: 32.0),
borderRadius: BorderRadius.circular(50.0)),
fillColor: Palette.background,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Palette.background, width: 32.0),
borderRadius: BorderRadius.circular(50.0)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Palette.background, width: 32.0),
borderRadius: BorderRadius.circular(50.0))),
),)
],
),
)
),
);
}
);
}
#override
bool shouldRebuild(SliverPersistentHeaderDelegate old) => false;
#override
double get maxExtent => 250.0;
#override
double get minExtent => 80.0;
}
you can use physics of CustomScrollView widget to control scroll.
CustomScrollView(
physics: PageScrollPhysics(), // added
slivers: <Widget>[

Scroll animation like yelp app in flutter

I'm trying to make a CustomScrollView similar to this:
I'm using a SliverAppBar but it seems that it is not the right way. My code so far:
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 340,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: BackgroundImageWidget(),
centerTitle: true,
// title: Text("Search by cities or activitry in Kreis Kleve"),
title: SearchButtonWidget(),
),
),
),
SliverList(...)
What I get:
I cant figure out why the SearchButtonWidget is so huge at first. When it's shrinked it has the right size.
Is sliverAppBar with fliexibleSpace even the right approach?
Edit: can someone also tell me how to make the search button overlapping with the widget below? I guess sliverAppBar can't do that.
Edit 2: maybe it's a white background added under the image. I need to investigate this.
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: true,
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: MySliverAppBar(
expandedHeight: 250,
title: Container(
margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.all(Radius.circular(5.0))),
child: Row(children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 20.0, right: 10.0),
child: Icon(Icons.search),
),
Expanded(
child: TextFormField(
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
cursorColor: Colors.black,
autofocus: false,
decoration: InputDecoration(
filled: true,
fillColor: Colors.transparent,
contentPadding: EdgeInsets.symmetric(
vertical: 10, horizontal: 15),
hintText: "Search",
border: InputBorder.none,
disabledBorder: OutlineInputBorder(
borderSide:
new BorderSide(color: Colors.transparent),
borderRadius: new BorderRadius.circular(2),
),
focusedBorder: OutlineInputBorder(
borderSide:
new BorderSide(color: Colors.transparent),
borderRadius: new BorderRadius.circular(2),
)),
),
)
]),
)),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
child: ListTile(
title: Text("${index}a"),
));
}, childCount: 25))
],
),
),
);
}
.
class MySliverAppBar extends SliverPersistentHeaderDelegate {
final double expandedHeight;
final Widget title;
MySliverAppBar({#required this.expandedHeight, this.title});
final _avatarMarginTween = EdgeInsetsTween(
begin: EdgeInsets.only(bottom: 20),
end: EdgeInsets.only(
bottom: 0.0,
));
final _searchMarginTween = EdgeInsetsTween(
begin: EdgeInsets.only(bottom: 0),
end: EdgeInsets.only(bottom: 8,));
final _avatarAlignTween =
AlignmentTween(begin: Alignment.bottomCenter, end: Alignment.center);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double tempVal = 75 * maxExtent / 100;
final progress = shrinkOffset > tempVal ? 1.0 : shrinkOffset / tempVal;
final avatarMargin = _avatarMarginTween.lerp(progress);
final searchMargin = _searchMarginTween.lerp(progress);
final searchAlignment = progress > 0.8 ? _avatarAlignTween.lerp(progress) : _avatarAlignTween.lerp(0);
return Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
Padding(
padding: avatarMargin,
child: 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 > 0.7 ? shrinkOffset / expandedHeight : 0.0,
child: Container(
color: Colors.red,
)
),
),
Padding(
padding: searchMargin,
child: Align(
alignment: Alignment.bottomCenter,
child: title,
),
)
],
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}