Flutter web: SliverPersistentHeader behavior - flutter

I'm following this tutorial, but using flutter web
I came across this issue with my code:
#override
Widget build(BuildContext context) {
final Size _size = MediaQuery.of(context).size;
return Center(
child: Container(
height: _size.height,
width: maxWidth(_size),
color: indigo,
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
floating: true,
delegate: SliverHeaderDelegate(
minExtent: _size.height*0.1,
maxExtent: _size.height*0.5),),
SliverFillRemaining()
],),),);}}
where the header delegate is:
class SliverHeaderDelegate implements SliverPersistentHeaderDelegate {
SliverHeaderDelegate({
this.minExtent,
#required this.maxExtent,
});
final double minExtent;
final double maxExtent;
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent)=>
Center(
child: Container(
child:Text('test',textScaleFactor: 5,),
);
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate)=> true;
#override
FloatingHeaderSnapConfiguration get snapConfiguration => null;
}
gives this result
but if I replace the SliverPersistentHeader with this:
SliverAppBar( pinned: true,
expandedHeight: _size.height*0.5,
flexibleSpace: FlexibleSpaceBar(
title: Text('test',textScaleFactor: 5,),
),),
the result changes drastically as you can see here
any idea why and how to correct it?
thanks

Related

How to make this sliver in flutter?

How to make this sliver flutter?
I want to do the same thing as in the following video
enter image description here
You can do like this.
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text("App bar"),
),
SliverPersistentHeader(
delegate: DelegateHeader(),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
return ListTile(
title: Text("new item $index"),
);
},
childCount: 50
)
)
],
),
);
}
and
class DelegateHeader extends SliverPersistentHeaderDelegate {
#override
double get maxExtent => 100;
#override
double get minExtent => 100;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.black26,
child: Center(child: Text("Always here")),
);
}
}

Add SafeArea into SliverAppBar

How to make FlexibleSpaceBar in SliverAppBar honour SafeArea?.
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
title: FittedBox(
fit: BoxFit.fitWidth,
child: Image.asset('assets/images/user.png')),
),
),
SliverList(
delegate: SliverChildListDelegate([
TextField(),
]),
)
],
)
I need the image be below os header at all time
I tried to wrap it with SafeArea widget but that didn't work and crashed
The following should work:
class TestSafeArea extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
pinned: true,
delegate: SafeAreaPersistentHeaderDelegate(
expandedHeight: 200,
child: Image.asset('assets/YOUR_IMAGE.png'))),
SliverList(
delegate: SliverChildListDelegate([
TextField(),
]),
)
],
),
);
}
}
class SafeAreaPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final double expandedHeight;
SafeAreaPersistentHeaderDelegate({this.child, this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SafeArea(bottom: false, child: SizedBox.expand(child: child));
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SafeAreaPersistentHeaderDelegate old) {
if (old.child != child) {
return true;
}
return false;
}
}
Sorry about the confusion!
EDIT #2 - Just saw you don't want the entire AppBar in the SafeArea
class SafeAreaPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget title;
final Widget flexibleSpace;
final double expandedHeight;
SafeAreaPersistentHeaderDelegate(
{this.title, this.flexibleSpace, this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final Widget appBar = FlexibleSpaceBar.createSettings(
minExtent: minExtent,
maxExtent: maxExtent,
currentExtent: max(minExtent, maxExtent - shrinkOffset),
toolbarOpacity: 1,
child: AppBar(
backgroundColor: Colors.blue,
automaticallyImplyLeading: false,
title: title,
flexibleSpace: (title == null && flexibleSpace != null)
? Semantics(child: flexibleSpace, header: true)
: flexibleSpace,
toolbarOpacity: 1,
bottomOpacity: 1.0),
);
return appBar;
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SafeAreaPersistentHeaderDelegate old) {
if (old.flexibleSpace != flexibleSpace) {
return true;
}
return false;
}
}
This will give your desired effect.
By using a SliverPersistentHeader with a custom SliverPersistentHeaderDelegate that returns an AppBar wrapped in the SafeArea widget.
class TestSafeArea extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: SafeAreaPersistentHeaderDelegate(
expandedHeight: 200,
flexibleSpace: SafeArea(
child: Container(
color: Colors.red,
),
)),
),
SliverList(
delegate: SliverChildListDelegate([
TextField(),
]),
)
],
),
);
}
}

SliverAppBar() not collapsing with ListView in Flutter

I'm trying to display tabs for each main tab (Nested Tab Bar) in SliverAppBar(). It's look like this:
See the image
See the GIF
The content of the exam tab it's in Container() widget (That the error in the image came from).
Now, with the Container() widget the SliverAppBar() will collapse when the user scroll the exam tab content (white screen in the image), everything is fine for now.
So, After I replaced the Container() with ListView.builder() to make the tab content scrollable, now I can't collapse SliverAppBar() from the tab content (white screen in the image). but I can from the SliverAppBar().
See this GIF after I added ListView.builder()
So, How I can make the SliverAppBar scrollable (collapsing ) with Listview?
Can anyone help me? please :(
This example (demo):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'SliverAppBar App Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, 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: Container(
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverSafeArea(
top: false,
sliver: SliverAppBar(
pinned: true,
title: Text(widget.title),
expandedHeight: 500,
),
),
),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
TabBar(tabs: [Tab(text: 'Tab A'), Tab(text: 'Tab B')]),
Colors.blue,
),
pinned: false,
),
];
},
body: TabBarView(
children: <Widget>[
NestedTabs('A'),
NestedTabs('B'),
],
),
),
),
),
);
}
}
// This class is to handle the main tabs (Tab A & Tab B)
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar, this._color);
TabBar _tabBar;
final Color _color;
#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(
color: _color,
alignment: Alignment.center,
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
class NestedTabs extends StatelessWidget {
final String mainTabName;
NestedTabs(this.mainTabName);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(20),
child: Container(
color: Colors.blue,
alignment: Alignment.bottomCenter,
child: TabBar(
tabs: [
Tab(text: 'Tab $mainTabName-1'),
Tab(text: 'Tab $mainTabName-2')
],
),
),
),
body: TabBarView(
children: [
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 500,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
width: 200,
color: Colors.black45,
child: Center(child: Text('Index ${index}')));
},
),
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 500,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
width: 200,
color: Colors.black45,
child: Center(child: Text('Index ${index}')));
},
)
],
),
);
}
}
Thank you :)
Use SliverList() instead of SliverFillRemaining for ListView

Allow GridView to overlap SliverAppBar

I am trying to reproduce the following example from the earlier Material design specifications (open for animated demo):
Until now I was able to produce the scrolling effect, but the overlap of the content is still missing. I couldn't find out how to do this properly.
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Title'),
expandedHeight: 200.0,
primary: true,
pinned: true,
),
SliverFixedExtentList(
itemExtent: 30.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) => Text('Item $i')
),
),
],
),
);
}
}
I managed to get this functionality, using the ScrollController and a couple of tricks:
Here's the code:
ScrollController _scrollController;
static const kHeaderHeight = 235.0;
double get _headerOffset {
if (_scrollController.hasClients) if (_scrollController.offset > kHeaderHeight)
return -1 * (kHeaderHeight + 50.0);
else
return -1 * (_scrollController.offset * 1.5);
return 0.0;
}
#override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(() => setState(() {}));
}
#override
Widget build(BuildContext context) {
super.build(context);
return StackWithAllChildrenReceiveEvents(
alignment: AlignmentDirectional.topCenter,
children: [
Positioned(
top: _headerOffset,
child: Container(
height: kHeaderHeight,
width: MediaQuery.of(context).size.width,
color: Colors.blue,
),
),
Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0),
child: Feed(controller: _scrollController, headerHeight: kHeaderHeight),
),
],
);
}
To make the Feed() not overlap the blue container, I simply made the first child of it a SizedBox with the required height property.
Note that I am using a modified Stack class. That is in order to let the first Widget in the stack (the blue container) to detect presses, so it will fit my uses; unfortunately at this point the default Stack widget has an issue with that, you can read more about it over https://github.com/flutter/flutter/issues/18450.
The StackWithAllChildrenReceiveEvents code can be found over https://github.com/flutter/flutter/issues/18450#issuecomment-575447316.
I had the same problem and could not solve it with slivers. This example from another stackoverflow question solved my problem.
flutter - App bar scrolling with overlapping content in Flexible space
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Scroll demo',
home: new Scaffold(
appBar: new AppBar(elevation: 0.0),
body: new CustomScroll(),
),
);
}
}
class CustomScroll extends StatefulWidget {
#override
State createState() => new CustomScrollState();
}
class CustomScrollState extends State<CustomScroll> {
ScrollController scrollController;
double offset = 0.0;
static const double kEffectHeight = 100.0;
#override
Widget build(BuildContext context) {
return new Stack(
alignment: AlignmentDirectional.topCenter,
children: <Widget> [
new Container(
color: Colors.blue,
height: (kEffectHeight - offset * 0.5).clamp(0.0, kEffectHeight),
),
new Positioned(
child: new Container(
width: 200.0,
child: new ListView.builder(
itemCount: 100,
itemBuilder: buildListItem,
controller: scrollController,
),
),
),
],
);
}
Widget buildListItem(BuildContext context, int index) {
return new Container(
color: Colors.white,
child: new Text('Item $index')
);
}
void updateOffset() {
setState(() {
offset = scrollController.offset;
});
}
#override
void initState() {
super.initState();
scrollController = new ScrollController();
scrollController.addListener(updateOffset);
}
#override
void dispose() {
super.dispose();
scrollController.removeListener(updateOffset);
}
}
Change the list to a grid and its what you want

Sticky headers on SliverList

I've seen new flutter video and seen some interesting. (It's not typical sticky header or expandable list, so I don't know how to name it)
Video - watch from 0:20
Does anybody know how can I create such type of list with headers using SliverList?
One way is to create a CustomScrollView and pass a SliverAppBar pinned to true and a SliverFixedExtentList object with your Widgets.
Example:
List<Widget> _sliverList(int size, int sliverChildCount) {
var widgetList = <Widget>[];
for (int index = 0; index < size; index++)
widgetList
..add(SliverAppBar(
title: Text("Title $index"),
pinned: true,
))
..add(SliverFixedExtentList(
itemExtent: 50.0,
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('list item $index'),
);
}, childCount: sliverChildCount),
));
return widgetList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Slivers"),
),
body: CustomScrollView(
slivers: _sliverList(50, 10),
),
);
}
SliverPersistentHeader is the more generic widget behind SliverAppBar that you can use.
SliverPersistentHeader(
delegate: SectionHeaderDelegate("Section B"),
pinned: true,
),
And the SectionHeaderDelegate can be implement with something like:
class SectionHeaderDelegate extends SliverPersistentHeaderDelegate {
final String title;
final double height;
SectionHeaderDelegate(this.title, [this.height = 50]);
#override
Widget build(context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).primaryColor,
alignment: Alignment.center,
child: Text(title),
);
}
#override
double get maxExtent => height;
#override
double get minExtent => height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false;
}