I want to make sliverAppBar resized while scrolling list below it.
Now there are two issues:
1) sliverAppBar doesn't resize while I am scrolling list (screenshot)
2) I can't find example/solution how to resize child content of sliverAppBar when it change height (screenshot)
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar( // <-- how to resize on scrolling ListView?
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: SafeArea(
child: Column(
children: [
Row( // <-- how to make it flexible/resizable?
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/user.png',
fit: BoxFit.cover,
width: 120,
)
],
),
],
),
)
),
SliverFillRemaining(
child: ListView.builder(
shrinkWrap: false,
itemCount: widget.europeanCountries.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.europeanCountries[index]),
);
},
),
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Use [FlexibleSpaceBar] and [SliverList]
[SliverList] can change the size of [sliverAppBar] when scrolling the list
[FlexibleSpaceBar] prevents border overflow
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: true,
pinned: true,
flexibleSpace: LayoutBuilder(
builder: (context, bc) {
double size = min(
// bc.constrainHeight() - MediaQuery.of(context).padding.top,
bc.constrainHeight(),
120);
return FlexibleSpaceBar(
centerTitle: true,
// title: Container(
// width: size,
// height: size,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// image: DecorationImage(
// image: NetworkImage(
// 'https://i.loli.net/2019/08/09/OvVzMqpF3jmI8lE.jpg'),
// fit: BoxFit.cover,
// ),
// ),
// ),
background: Center(
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
'https://i.loli.net/2019/08/09/OvVzMqpF3jmI8lE.jpg'),
fit: BoxFit.cover,
),
),
),
),
);
},
),
),
SliverList(
//
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text("Text"),
);
},
childCount: 100,
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Related
I just want to make UI like content over the appbar, but I couldn't. Here is my code that I got from web. how to make the listview top of appbar and remove the card.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Sample2(),
),
);
}
class Sample2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
child: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverAppBar(expandedHeight: 200),
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) => ListTile(
title: Text("Index: $index"),
),
),
)
],
),
),
);
}
}
class MySliverAppBar extends SliverPersistentHeaderDelegate {
final double expandedHeight;
MySliverAppBar({#required this.expandedHeight});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
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,
child: Text(
"MySliverAppBar",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 23,
),
),
),
),
Positioned(
top: expandedHeight / 2 - shrinkOffset,
left: MediaQuery.of(context).size.width / 4,
child: Opacity(
opacity: (1 - shrinkOffset / expandedHeight),
child: Card(
elevation: 10,
child: SizedBox(
height: expandedHeight,
width: MediaQuery.of(context).size.width / 2,
child: FlutterLogo(),
),
),
),
),
],
);
}
#override
double get maxExtent => expandedHeight;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
Using stack is not the best way I think. May be I am wrong. but I think customScrollView and sliverappbar can do this. but I didn't get any tutorial or youtube videos. Most of the examples are listview below the appbar. I just want content over the appbar at first time, when it scrolls it should go below the appbar.
This will help you to hide appbar on scroll
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text('Sameple text'),
pinned: true,
floating: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: <Tab>[
Tab(text: 'ONE'),
Tab(text: 'TWO'),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Text(
"xyz",
style: TextStyle(fontSize: 60),
),
),
Text("xyz"),
],
),
),
);
}
Here is the scenario -
Need TabBarView as user can swipe to change the screen.
Want to load more items when user scrolls to the bottom of the screen.
The first code is the NestedScrollView with TabBarView which has two tabs containing listview with 4 items. Even though the body height is less than screen height the body scrolls. I understand the default height is set to view port height but if I want achieve point number 2, I cant since the scroll is way too much. Is there a way to wrap the body to the height of the content?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Scroll Demo with TabBarView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NestedScrollViewTest(),
);
}
}
class NestedScrollViewTest extends StatelessWidget {
const NestedScrollViewTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
forceElevated: true,
elevation: 2.0,
primary: true,
pinned: true,
stretch: true,
backgroundColor: Colors.white,
expandedHeight: 500,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
background: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 4,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.red,
),
),
),
SizedBox(
height: 5,
),
Expanded(
flex: 5,
child: Container(
color: Colors.amber,
),
),
SizedBox(
height: 5,
)
],
),
),
),
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
// shrinkWrap: true, // even with this it is not working.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 4,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
In the second code, I am using a CustomScrollView instead. Here since there is no SliverTabBarView, I am using a SliverFillRemaining widget to wrap the TabBarView and place it in the CustomScrollView. Even here the body scrolls way too much since SliverFillRemaining default height is view port height. Without using the TabBarView the CustomScrollView wraps the body based on the height of the content but I need TabBarView.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Scroll Demo with TabBarView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CustomScrollViewTest(),
);
}
}
class CustomScrollViewTest extends StatelessWidget {
const CustomScrollViewTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: CustomScrollView(
slivers: [
SliverAppBar(
forceElevated: true,
elevation: 2.0,
primary: true,
pinned: true,
stretch: true,
backgroundColor: Colors.white,
expandedHeight: 500,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
background: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 4,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.red,
),
),
),
SizedBox(
height: 5,
),
Expanded(
flex: 5,
child: Container(
color: Colors.amber,
),
),
SizedBox(
height: 5,
)
],
),
),
),
SliverFillRemaining(
// hasScrollBody: false,
child: TabBarView(
children: _tabs.map((String name) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
},
itemCount: 4,
);
}).toList(),
),
)
],
),
),
);
}
}
Steps I have tried,
If I change the property hasScrollBody: false in SliverFillRemaining, I get the error -
RenderViewport does not support returning intrinsic dimensions.
If I use SliverToBoxAdapter instead of SliverFillRemaining then I get this error since TabBarView height is dependent on the parent.
Horizontal viewport was given unbounded height.
Is there a way to wrap the content based on the body height keeping TabBarView in mind.
Edit: adding images -
Initial
start scroll
end scroll
I need to add an image at the top of the app bar in Flutter. Below the image will be the title and action icons. How do I achieve this?
You can use CustomScrollView then you can find SliverAppBar>bottom
Updated Result
Updated Widget
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 300,
floating: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
color: Colors.cyanAccent,
child: Image.asset(
"assets/ocean.jpg",
fit: BoxFit.cover,
)),
),
),
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(),
Text("Title"),
Row(
children: [
IconButton(
onPressed: () {
print("tapped");
},
icon: Icon(Icons.ac_unit),
)
],
)
],
),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(
height: 722,
color: Colors.pinkAccent,
),
],
),
),
],
),
),
);
}
}
I want to achieve the above design
I did somewhat like this using slivers
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: DefaultTabController(
length: 4,
child: NestedScrollView(
body: TabBarView(children: [
//pages of tabBar
]),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
//for the pinned appBar
elevation: 0.0,
backgroundColor: Colors.red,
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(backgroundColor: Colors.red.withOpacity(0.4),
child: Icon(Icons.arrow_back,color: Colors.white,),
),
),
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
"https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&h=350",
fit: BoxFit.cover,
),
),
expandedHeight: 150,
),
SliverList(delegate: SliverChildListDelegate([
// for bio and other stuff
])),
SliverAppBar(
toolbarHeight: 0.0,
primary: false,
pinned: true,
bottom: TabBar(tabs: [
// tab bars
],),
)
];
},
),
),
),
);
}
}
I tried adding stack inside my sliver list with positioned but Circle Avatar gets display behind the app bar.
How can we achieve such a design or what could be an alternative way to achieve it.
I am trying to apply image in the background of the status bar in an flutter project. So which widget can help me to apply the following expected result.
detailpage.dart
import 'package:flutter/material.dart';
class MyDetailPage extends StatefulWidget {
#override
_MyDetailPageState createState() => _MyDetailPageState();
}
class _MyDetailPageState extends State<MyDetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
height: 300,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/image2.png"),
fit: BoxFit.fitHeight,
),
),
)
],
),
);
}
}
Use Scaffold property
extendBodyBehindAppBar: true, and
set statusBarColor: Colors.transparent
This code is for status bar colour.
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent
));
Here is an example of code in my current project.
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent
));
return Scaffold(
extendBodyBehindAppBar: true,
body: Column(
children: [
Container(
height: 250,
child: Container(
child: Stack(
alignment: Alignment.topLeft,
children: [
Container(
width: screenWidthPercentage(context,percentage: 1),
child: Image.network(
contentImage,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.only(top: 48.0,left: 12),
child: Icon(Icons.arrow_back,color: Colors.white,),
),
],
),
),
),
],
),
);
}
}
you can do it by not keeping SafeArea widget and set statusbar color as transparent.
I have created an example as below.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FullScreen extends StatefulWidget {
const FullScreen({Key? key}) : super(key: key);
#override
_FullScreenState createState() => _FullScreenState();
}
class _FullScreenState extends State<FullScreen> {
#override
void initState() {
super.initState();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light
//color set to transperent or set your own color
)
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: buildBody(),
);
}
Widget buildBody() {
return SizedBox(
height: 400,
width: double.infinity,
child: Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(40),
bottomRight: Radius.circular(40)),
),
child: Image.network(
'https://source.unsplash.com/random',
fit: BoxFit.cover,
),
),
);
}
}
I have added card as it gives nice elevation effect to the image 😎 .
FYI keep margin as 0 in card if you are using card.
You can achieve it using FlexibleSpaceBar, here is the example code.
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 300,
flexibleSpace: FlexibleSpaceBar(
background: Image.asset("assets/images/chocolate.jpg", fit: BoxFit.cover),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text("Item ${index}")),
childCount: 100,
),
),
],
),
);
}
When we using a ListView on a top tree,
The ListView makes a default padding.top even without a SafeArea.
So we need a
padding: EdgeInsets.zero.