I am trying to set up a SliverAppBar in a CustomScrollView using Flutter, and can't get to vertically center the title.
I already tried this solution (and this SO question is exactly what I want to do) but fortunately, it didn't work for me.
Here is my build method:
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: 200,
//backgroundColor: Colors.transparent,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
centerTitle: true,
title: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Should be centered", textAlign: TextAlign.center),
],
),
background: Image.asset("assets/earth.jpg", fit: BoxFit.cover),
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
tooltip: "Menu",
onPressed: () {
// onPressed handler
},
),
],
),
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.green,
child: Text("Index n°$index"),
);
},
),
)
],
),
);
}
I really don't understand what is wrong and why it isn't centered. I observed that the column is way too big when setting mainAxisSize to mainAxisSize.max.
Any idea?
Thanks in advance!
I tinkered around a bit in your code and was able to center it. So the main problem here was the expandedHeight. This height expands the SliverAppBar both upwards and downwards meaning that half of that 200 was always above the screen. Taking that into consideration, you would be trying to center the text in only the bottom half of the app bar. The simplest way was to just use Flexible to size the items relative to their container. Here's the working code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: 200,
//backgroundColor: Colors.transparent,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
centerTitle: true,
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
flex: 3,
child: Container(),
),
Flexible(
flex: 1,
child:
Text("Should be centered", textAlign: TextAlign.center),
),
Flexible(
flex: 1,
child: Container(),
),
],
),
background: Image.asset("assets/earth.png", fit: BoxFit.cover),
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
tooltip: "Menu",
onPressed: () {
// onPressed handler
},
),
],
),
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.green,
child: Text("Index n°$index"),
);
},
),
)
],
),
);
}
A way without empty containers
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
centerTitle: true,
title: SizedBox(
height: 130,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Should be centered", textAlign: TextAlign.center),
],
),
),
background: Image.asset("assets/earth.png", fit: BoxFit.cover),
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.menu),
tooltip: "Menu",
onPressed: () {
// onPressed handler
},
),
],
),
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.green,
child: Text("Index n°$index"),
);
},
),
)
],
),
);
}
Instead of using a FlexibleSpaceBar use a different Widget.
flexibleSpace: Padding(
padding: EdgeInsets.all(4),
child: Container(),
}),
),
Kinda like what Pedro said earlier, you can instead use the flexibleSpace parameter in SliverAppBar, and then center things from there. This is what I did.
SliverAppBar(
flexibleSpace: Center(
child: Text("27 is my favorite number")
)
)
Related
Is it possible for a SliverToBoxAdapter to not be overlaped with a transparentSliverAppBar when scrolled upon?
Or give up on using SliverAppBar and just use AppBar instead?
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: [
Image(
image: bkgImage,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
),
Scaffold(
backgroundColor: Colors.transparent,
body: CustomScrollView(
slivers: [
SliverAppBar(
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
],
pinned: _pinned,
snap: _snap,
floating: _floating,
expandedHeight: 160,
backgroundColor: Colors.transparent,
elevation: 0.0,
flexibleSpace: FlexibleSpaceBar(
expandedTitleScale: 1.6,
titlePadding: EdgeInsets.all(18),
title: Text('Panahon'),
),
),
SliverList(
delegate: SliverChildListDelegate([
SizedBox(
height: 200,
child: Card(),
),
SizedBox(
height: 200,
child: Card(),
),
SizedBox(
height: 200,
child: Card(),
),
SizedBox(
height: 200,
child: Card(),
),
SizedBox(
height: 200,
child: Card(),
),
]),
)
],
),
),
],
),
);
}
The goal is this:
Precious view:
To get actual effect, we can use SliverPersistentHeaderDelegate. Concept is we need to pass image itself [both image must be screen size].
class MySliverHeaderDelegate extends SliverPersistentHeaderDelegate {
final double _maxExtent = 160;
final VoidCallback onActionTap;
final Image imageWidget;
MySliverHeaderDelegate({
required this.onActionTap,
required this.imageWidget,
});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
debugPrint(shrinkOffset.toString());
return Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: imageWidget,
),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Panahon',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.white,
),
),
),
),
// here provide actions
Positioned(
top: 0,
right: 0,
child: IconButton(
icon: const Icon(
Icons.search,
color: Colors.white,
),
onPressed: onActionTap,
),
),
],
);
}
#override
double get maxExtent => _maxExtent;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant MySliverHeaderDelegate oldDelegate) {
return oldDelegate != this;
}
}
And use
Widget build(BuildContext context) {
final imageWidget = Image.asset(
"assets/images/image01.png",
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
);
return SafeArea(
child: Stack(
children: [
imageWidget,
Scaffold(
backgroundColor: Colors.transparent,
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: MySliverHeaderDelegate(
imageWidget: imageWidget,
onActionTap: () {
debugPrint("on Tap");
}),
),
//.....
],
),
),
],
),
);
}
Others way to handle this situation
Being SliverAppBar's backgroundColor:Colors.transparent you are able to see though appbar.
If you provide your preferable color, that will fix the issue in this case, but you will not get background effect on appBar.
SliverAppBar(
backgroundColor: Colors.green, // the one you like
For your case, you can use CupertinoSliverNavigationBar instead of using SliverAppBar. It will still block the background on scroll and it won't get that much height.
CupertinoSliverNavigationBar(
backgroundColor: Colors.transparent,
largeTitle: const Align(
alignment: Alignment.bottomLeft,
child: SizedBox(
child: Text('Panahon'),
),
),
trailing: IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
),
I'm trying to improve my app and want to wrap an existing FutureBuilder with a SliverList. Unfortunately I'm having a strange and incomprehensible scrolling behaviour.
Here's my code so far:
#override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 300,
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: Padding(
padding: EdgeInsets.fromLTRB(30, 10, 30, 10),
child: Text(
'some subtext here...',
style: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.white,
fontSize: 12.0)),
),
background: Stack(
fit: StackFit.expand,
children: [
Container(
child: Image.network(
'https://dummyimage.com/600x400/000/fff',
fit: BoxFit.cover,
),
),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(0.0, 0.5),
end: Alignment(0.0, 0.0),
colors: <Color>[
Color(0xcc000000),
Color(0x55000000),
],
),
),
),
],
),
),
),
SliverPadding(
padding: EdgeInsets.only(top: 10),
sliver: SliverList(
delegate: SliverChildListDelegate([
Container(
child: FutureBuilder(
future: fetchData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Text('loading...');
} else {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext content, int index) {
// return Text('Text ' + snapshot.data[index].name);
return snapshot.hasData ? Card(
elevation: 4,
child: ListTile(
leading: CircleAvatar(
backgroundImage:
NetworkImage(snapshot.data[index].image_url),
),
trailing: Text('Test'),
title: Text(snapshot.data[index].name),
subtitle: Text(snapshot.data[index].latin_name, style: TextStyle(
fontStyle: FontStyle.italic,
)),
onTap: () {
/*Navigator.push(context,
new MaterialPageRoute(builder: (context)=> DetailPage(snapshot.data[index]))
);*/
print('tapped');
},
),
) : Text('no Data');
},
);
//return Text('loading complete');
}
}
),
),
],),
),
)
],
)),
],
));
}
The SliverAppBar gets displayed correctly, but unfortunately since adding the Slivers my FutureBuilder doesn't display anything at all.
After adding scrollDirection and shrinkWrap to the ListView the data get's displayed but I'm unable to scroll correctly.
When dragging it at the red area the List snaps back up right after releasing The SliverAppBar doesn't shrink either. When scrolling in the green area the Sliver Header works correctly and the Scrolling doesn't snap right at the top when releasing.
Has anyone had this issue before? Or could someone explain on why this could happen?
I would like to use a Row widget instead of a Text widget as the title for the FlexibleSpaceBar.
Unfortunately Flutter returns an error.
Here is my code:
CustomScrollView(
slivers: [
const SliverAppBar(
pinned: true,
floating: false,
backgroundColor: Color(0xFF172A3A),
snap: false,
expandedHeight: 200,
centerTitle: true,
flexibleSpace: FlexibleSpaceBar(
background: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Demo'),
]
),
centerTitle: true,
),
),
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'),
);
},
),
),
],
),
I know that the title should be a Text widget but I actually need a Row.
You can copy paste run full code below
Step 1 : Remove const keyword in front of SliverAppBar
Step 2 : Use Row like this
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('title'),
Text('test'),
]),
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
floating: false,
backgroundColor: Color(0xFF172A3A),
snap: false,
expandedHeight: 200,
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('title'),
Text('test'),
]),
flexibleSpace: FlexibleSpaceBar(
background: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Demo'),
]),
centerTitle: true,
),
),
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'),
);
},
),
),
],
),
);
}
}
I'm trying to implement a custom tab bar widget inside a SliverAppBar. So far I've tried wrapping my CustomTabBar within a PreferredSize widget.
Here's my code:
Widget _buildBody(){
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
leading: Container(),
backgroundColor: Colors.transparent,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
"Item 1"
),
Text(
"Item 2"
),
Text(
"Item 3"
)
],
)
]),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: CustomTabWidget(
items: ['Challenge', 'My friends'],
activeColor: secondaryColor,
currentIndex: currentIndex,
backgroundColor: tabColor,
activeTextColor: Colors.white,
backgroundTextColor: Colors.white,
onTabSelect: (int index) {
onLeaderboardTabSelect(index);
},
),
),];
},
body: ListView.separated(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('row $index'),
);
},
separatorBuilder: (context, index) {
return Divider();
},
) // should return listview depending on the tab
);
}
CustomTabWidget
Widget build(BuildContext context) {
return Container(
height: height,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(
30.0,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildItems(context),
),
);
}
The code successfully shows the my custom tab bar widget but whenever I scroll down or tap another tab, it disappears.
I might have overlooked something within the code.
Can anyone help me?
this work on my project
class TabBarInSliverAppbar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text("Tabbar in SliverAppbar Example"),
pinned: true,
floating: true,
bottom: TabBar(
tabs: <Widget>[
Tab(
text: "First Tab",
),
Tab(
text: "Second Tab",
),
],
),
),
SliverToBoxAdapter(
child: TabBarView(
children: <Widget>[
Center(
child: Text("First Tab"),
),
Center(
child: Text("Second Tab"),
),
],
),
),
],
),
),
);
}
}
you can also checki this link :Flutter TabBar and SliverAppBar that hides when you scroll down
I have a page with a DefaultTabController that is:
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
resizeToAvoidBottomPadding: true,
appBar: AppBar(
bottom: TabBar(
indicatorColor: Colors.red,
indicator: BoxDecoration(
color: buttonColor,
),
tabs: [Tab(text: "Login"), Tab(text: "Register", key: Key("tabRegistro"))],
),
centerTitle: true,
title: Text(appBarTitle)),
body: TabBarView(
children: [
new LoginPage(),
new RegisterPage(),
],
),
),
);
}
And for example, the LoginPage build is:
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: true,
key: _scaffoldKey,
body: SingleChildScrollView(
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Center(
child: Container(
color: backgroundColor,
child: Padding(
padding: const EdgeInsets.all(36.0),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 155.0,
child: Image.asset(
"assets/images/pet.PNG",
fit: BoxFit.contain,
),
),
SizedBox(height: 20.0),
emailField,
SizedBox(height: 10.0),
passwordField,
SizedBox(height: 20.0),
loginButon,
SizedBox(height: 20.0),
GoogleSignInButton(
darkMode: true,
text: "Login with Google",
onPressed: () {
_sigIn();
}),
],
),
)),
),
),
)));
}
But when I'm writing, the keyboard cover the Textfields and they aren't visible.
With SingleChildScrollView before it works, but now it doesn't work propertly. I have tried to put resizeToAvoidBottomPadding: true but it doesn't work. What could I do to fix this problem?
The code is Ok, the problem is at Android.manifest.xml. I'm using Android Simulator and to hide the status bar I put these line:
android:theme="#android:style/Theme.Holo.NoActionBar.Fullscreen"
If i put the original line it works. The original line is:
android:theme="#style/LaunchTheme"