Reputation: 129
The SliverAppbar will stretch and zoom if it is in a customScrollView but it does not when it is in NestedScrollView. Only the body of NestedScrollView has a BouncingScrollPhysics, how can the header of NestedScrollView also have the BouncingScrollPhysics so it can stretch and zoom the SliverAppBar.
However, if i add scrollController in the body/inner scrollView then the header SliverAppBar stretches, but then they become two separate scroll views and the header does not scroll up when the body is scrolled. Check the Screen recording
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 3,
child: NestedScrollView(
physics: BouncingScrollPhysics(),
controller: mainScroller,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
stretch: true,
pinned: true,
floating: false,
elevation: 0,
onStretchTrigger: () {
print('stretch');
return;
},
title: isFlexibleSpaceVisible ? null : Text(widget.name),
expandedHeight: containerHeight - kToolbarHeight,
flexibleSpace: FlexibleSpaceBar(
stretchModes: <StretchMode>[
StretchMode.zoomBackground,
StretchMode.blurBackground,
],
collapseMode: CollapseMode.pin,
background: Container(
color: Theme.of(context).canvasColor,
child: Stack(
children: <Widget>[
Container(
height: headerImageHeight,
width: double.infinity,
decoration: BoxDecoration(
color: kBackgroundColor,
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black54,
BlendMode.darken,
),
image: NetworkImage(
'https://picsum.photos/seed/${Random().nextInt(100)}/${MediaQuery.of(context).size.width.toInt()}'),
),
),
),
Container(
height: headerSpace,
margin: EdgeInsets.only(
top: headerImageHeight - bringImageUpMargin),
padding: EdgeInsets.only(left: 8, right: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
shape: BoxShape.circle),
padding: EdgeInsets.all(6),
child: CircleAvatar(
backgroundColor: kCardColor,
backgroundImage: NetworkImage(
'https://picsum.photos/seed/${Random().nextInt(100)}/200'),
radius: profileImageRadius,
),
),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(
horizontal: 4, vertical: 12),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Text(
widget.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700),
),
SizedBox(height: 2),
Wrap(
alignment: WrapAlignment.spaceBetween,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius:
BorderRadius.circular(4)),
child: Text(
widget.skill,
style: TextStyle(
fontSize: 12,
color: Colors.white),
),
),
Container(
padding: EdgeInsets.symmetric(
vertical: 2, horizontal: 6),
decoration: BoxDecoration(
color: kCardColor,
borderRadius:
BorderRadius.circular(4)),
child: Text(
'\$${widget.rate} / ${widget.per}',
style: TextStyle(
color: kPrimaryColor,
fontSize: 12),
),
),
],
),
],
),
),
)
],
),
),
],
),
),
),
),
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 22,
height: 18,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: kSecondaryColor,
),
child: Icon(
Icons.star,
color: Colors.white,
size: 14,
)),
SizedBox(
width: 6,
),
Text('Do you recommend ${widget.name} ?'),
],
),
Row(
children: <Widget>[
Expanded(
child: OutlineButton(
highlightedBorderColor: kSecondaryColor,
color: kSecondaryColor,
borderSide: BorderSide(color: kSecondaryColor),
textColor: kSecondaryColor,
onPressed: () => null,
child: Text('NO'),
),
),
SizedBox(
width: 8,
),
Expanded(
child: RaisedButton(
onPressed: () => null,
child: Text('YES'),
),
),
],
)
],
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: SliverProfileTabs(),
)
];
},
body: ProfileTabsView(data: widget),
),
),
);
}
}
Upvotes: 5
Views: 11789
Reputation: 6729
As mentioned in the comment, there is an open bug in GitHub for this.
The reason for this behavior was discussed in this thread:
The reason the stretch does not happen is because the
_NestedScrollViewCoodinator
currently prioritizes the inner scrollable for any overscroll from the user drag. I am thinking we can add a flag like,NestedScrollView.stretchHeaderSlivers
, which will flip that to send overscroll to the outer scrollable. That part should be easy.The
_NestedScrollViewCoordinator.createOuterBallisticScrollActivity
will need to be refactored since it currently assumes that the outer scrollable will never overscroll, which may or may not be as easy.
And they have mentioned that this is a huge refactoring and there is no suitable solutions yet:
I've taken a crack at this a few different times and have not found a suitable solution yet. The NestedScrollView bases much more than I though on the assumption that the outer scroll view will never overscroll. I believe this will take a larger refactoring/re-design of the widget in order to support, maybe work we can schedule for in the new year. For now, I won't be actively working on this anymore. I am un-assigning myself for now in case some one else would like to try solving this in the meantime. :)
Since I'm not able to view your screen recording, I'll just post a sample view of how the bug looks like. It was taken from the GitHub issue thread.
Sample code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
theme: ThemeData.dark(),
home: Home(),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
body: NestedScrollView(
physics: BouncingScrollPhysics(),
headerSliverBuilder: (context, innerScrolled) => <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverAppBar(
pinned: true,
stretch: true,
title: Text('username'),
expandedHeight: 325,
flexibleSpace: FlexibleSpaceBar(
stretchModes: <StretchMode>[
StretchMode.zoomBackground,
StretchMode.blurBackground,
],
background: Image.network(
'https://i.imgur.com/QCNbOAo.png',
fit: BoxFit.cover)),
bottom: TabBar(
tabs: <Widget>[Text('test1'), Text('test2')])),
)
],
body: TabBarView(children: [
Center(
child: Builder(
builder: (context) => CustomScrollView(
slivers: <Widget>[
SliverOverlapInjector(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context)),
SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
(_, index) => Text('not working'),
childCount: 100),
itemExtent: 25)
],
),
),
),
Center(child: Text('working'))
])),
));
}
}
Output:
Upvotes: 4