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")),
);
}
}
Related
The following code yields a scrollable list together with a "translucent pinned sliver header".
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverPersistentHeader(
delegate: _SliverPersistentHeaderDelegate(),
pinned: true,
),
];
},
body: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Container(
color: Colors.amber.withOpacity(0.3),
child: Text('Item $index'),
),
);
},
),
),
),
);
}
}
class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.blue.withOpacity(0.75),
child: Placeholder(),
);
}
#override double get maxExtent => 300;
#override double get minExtent => 200;
#override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
It's all good; except, I need the "header" to be transparent, but having it translucent, causes the underneathed list-items to get revealed (as per the screenshot below).
So, how to "mask-out" the "list items" that are visible through the "translucent header"?
How about using CustomClipper for List itself? Because the list height is dynamic during scrolling, the clip height must be calculated dynamically. So I pass the clipHeight into the custom clipper.
To get the clipHeight, I use MediaQuery.of(context).size.height - header height. So I create another class to get this value.
...
body: CustomWidget (
child: ListView.builder(
...
class CustomWidget extends StatelessWidget {
final Widget child;
CustomWidget({this.child,Key key}):super(key:key);
#override
Widget build(BuildContext context) {
return ClipRect(
clipper: MyCustomClipper(clipHeight: MediaQuery.of(context).size.height-200),
child: child,
);
}
}
class MyCustomClipper extends CustomClipper<Rect>{
final double clipHeight;
MyCustomClipper({this.clipHeight});
#override
getClip(Size size) {
double top = math.max(size.height - clipHeight,0) ;
Rect rect = Rect.fromLTRB(0.0, top, size.width, size.height);
return rect;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
Pinned SliverPersistentHeader works like "CSS position: absolute".
So your body widget doesn't know that something is upon it.
One of the option is to not to use the SliverPersistentHeader.
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ScrollController controller;
#override
void initState() {
currentHeight = _maxExtent;
controller = ScrollController();
controller.addListener(() {
_updateHeaderHeight();
});
super.initState();
}
_updateHeaderHeight() {
double offset = controller.offset;
if (offset <= _maxExtent - _minExtent) {
setState(() {
currentHeight = math.max(_maxExtent - offset, _minExtent);
});
}
}
double currentHeight;
final double _maxExtent = 300;
final double _minExtent = 200;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DecoratedBox(
// only to prove transparency
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),
image: NetworkImage(
'https://picsum.photos/720/1280',
),
fit: BoxFit.cover,
),
),
child: Stack(
children: [
Header(currentHeight: currentHeight),
Padding(
padding: EdgeInsets.only(top: currentHeight),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent),
),
child: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return ListTile(
title: Container(
color: Colors.amber.withOpacity(0.3),
child: Text('Item $index'),
),
);
},
),
),
),
],
),
),
),
);
}
}
class Header extends StatelessWidget {
const Header({Key key, this.currentHeight}) : super(key: key);
final double currentHeight;
#override
Widget build(BuildContext context) {
return Container(
height: currentHeight,
color: Colors.blue.withOpacity(0.75),
child: Placeholder(),
);
}
}
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(),
]),
)
],
),
);
}
}
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
How I can get such effect of AppBar title, sample:
The big title "Coffee shop" disappears when page scrolls and appears in appBar. Thanks.
probably you need SliverAppBar
You can also check this tutorial
An example based on the article:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MainCollapsingToolbar());
}
}
class MainCollapsingToolbar extends StatefulWidget {
#override
_MainCollapsingToolbarState createState() => _MainCollapsingToolbarState();
}
class _MainCollapsingToolbarState extends State<MainCollapsingToolbar> {
ScrollController _scrollController = ScrollController();
double offset = 0.0;
#override
void initState() {
super.initState();
_scrollController.addListener(() {
setState(() {
offset = _scrollController.offset;
});
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
centerTitle: (offset == 0) ? false : true,
title: Text("Collapsing Toolbar",
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
)),
),
),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
TabBar(
labelColor: Colors.black87,
unselectedLabelColor: Colors.grey,
tabs: [
Tab(icon: Icon(Icons.info), text: "Tab 1"),
Tab(icon: Icon(Icons.lightbulb_outline), text: "Tab 2"),
],
),
),
pinned: true,
),
];
},
body: Center(
child: Text("Sample text"),
),
),
),
);
}
}
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;
}
}
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;
}