How to change SliverAppBar height dynamically - flutter

I show image from network, which shrink on scrolling. I want to show whole image without paddings or crops. But if I comment line with expandedHeight - there is no image - only appbar with its height. Is there any widget, which can change its size according to size of uploaded image?
CustomScrollView(
controller: controller,
key: listKey,
slivers: <Widget>[
SliverAppBar(
// expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
background: getHeroWidget(
_conference.dbId,
FadeInImage.assetNetwork(
placeholder: conf_img_placeholder,
image: _conference.info.image,
fit: BoxFit.cover,
)),
title: Text(conference_title),
centerTitle: true,
),
pinned: true,
),

Finally I created custom SliverAppBar. Another issue appears - width of status bar which have to be taken into consideration on calculation.
import 'dart:ui' as ui;
class _HeaderBar extends StatefulWidget {
#override
State<StatefulWidget> createState() => _HeaderState();
}
class _HeaderState extends State<_HeaderBar> {
#override
Widget build(BuildContext context) {
Image image = Image.network(...);
Completer<ui.Image> completer = new Completer<ui.Image>();
image.image.resolve(ImageConfiguration()).addListener((ImageInfo info, bool _) {
completer.complete(info.image);
});
final double statusBarHeight = MediaQuery.of(context).padding.top;
return FutureBuilder(
future: completer.future,
builder: (context, AsyncSnapshot<ui.Image> snapshot) {
return SliverAppBar(
expandedHeight: snapshot.hasData
? MediaQuery.of(context).size.width / snapshot.data.width.toDouble() * snapshot.data.height.toDouble() -
statusBarHeight
: 0.0,
...

I was looking for this problem, I find a solution with NestedScrollView and a plugin SliverStickyHeader
Here is how I did this,
Scaffold(
body: SafeArea(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverStickyHeader(
sticky: false,
header: Container(
color: Theme.of(context).primaryColor,
child: Column(
children: [
Text(
'Header',
style: Theme.of(context).textTheme.headline6,
),
Text(
'Header',
style: Theme.of(context).textTheme.headline5,
),
Text(
'Header',
style: Theme.of(context).textTheme.headline4,
),
Text(
'Header',
style: Theme.of(context).textTheme.headline3,
),
],
),
),
)
];
},
body: Column(
children: [
AppBar(
title: Text('My List items'),
),
Expanded(
child: ListView.builder(itemBuilder: (context, i) {
return ListTile(
leading: CircleAvatar(child: Text('$i')),
title: Text('Appbar with dynamic height'),
);
}),
),
],
),
),
),
);
and it is fulfilling my requirement, Plugin also support fully Sliver functionality, but I use it with as header only. I hope it will be helpful.

Related

Flutter List View Builder Using API

I am able to fetch data from API and I can show it in the List View, but i want to show the first card as the latest updated data.
I tried to use the Reverse property of List View Builder, it some how solve my problem but gave rise to another problem. Now i need to scroll upward in order to see latest updated data.
Help me through it
I just want to show the latest updated data at the top and then scroll down to see another data.
Please Help Me.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class PromotersDetails extends StatefulWidget {
final String url,title;
PromotersDetails({Key key, #required this.url, #required this.title}) : super(key: key);
#override
_PromotersDetailsState createState() => _PromotersDetailsState(url,title);
}
class _PromotersDetailsState extends State<PromotersDetails> {
fetchSelfies() async {
var url1;
url1 = await http.get(Uri.parse(
url));
var res = json.decode(url1.body);
print(res);
return json.decode(url1.body)['selfies'];
}
String url,title;
_PromotersDetailsState(this.url, this.title);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width * 0.6;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: false,
title: Text(
title,
style: TextStyle(fontSize: 25.0, color: Colors.white),
),
elevation: 0.0,
backgroundColor: Colors.green,
),
body: FutureBuilder(
future: fetchSelfies(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
if (snapshot.hasData) {
return ListView.builder( **// List View Builder**
reverse: false,
cacheExtent: 10000.00,
itemCount: snapshot.data.length,
padding: EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
return Row(
children: [
Container(
height: 120,
alignment: Alignment.center,
child: Container(
height: 120,
width: 120,
child: Card(
child: Image.network(snapshot.data[index]['image']),
),
),
),
Expanded(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
Row(
children: [
Text(
"Time: ",
style: TextStyle(color: Colors.black),
),
Text(
snapshot.data[index]['time'],
style: TextStyle(color: Color(0xff868597)),
),
],
),
Container(
height: 50,
child: Text(
snapshot.data[index]['location'],
style: TextStyle(color: Color(0xff868597)),
),
),
],
),
),
),
],
);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
})
);
}
}
If you just need to sort the API response backwards, you can use the reverse property of ListView.
ListView.builder(
reverse: true,
...
),
If the way you're populating the ListView is by appending new data, then it's better to store the data on a List then do sort/reversed. Though the sorting should be best done at the backend instead of passing the burden to the client app.

Show content under a SliverChildBuilderDelegate

Is there a way to show widgets (for example a SliverToBoxAdapter or a normal Container) underneath a SliverChildBuilderDelegate? I have a condition that turns on the SliverList and there can only be one parent widget for a condition, what would I need to wrap it in?
Code currently looks like:
lass Testaaa extends StatelessWidget {
final bool hasData;
const Testaaa({Key key, this.hasData}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerSettings(),
body: Container(
child: CustomScrollView(
slivers: <Widget>[
AppBarSliver(),
hasData
? SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
return ListTile(
title: Text('cool'),
);
},
),
) // I would like to put another Container here that
// scrolls with the bottom, underneath the SliverList
: Container(
child: Text('no data'),
),
],
),
),
);
}
seems like a work for the spread operator
CustomScrollView(
slivers: <Widget>[
AppBarSliver(),
...hasData
? <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
return ListTile(
title: Text('cool'),
);
},
),
),
const SliverToBoxAdapter(
child: SizedBox(
child: Text('End of the list'),
),
),
]
: <Widget>[
const SliverToBoxAdapter(
child: SizedBox(
child: Text('no data'),
),
)
]
],
)
This way you're telling if hasData is true add all this widgets in the brackets (SliverList and SliverToBoxAdapter with Text 'End of the list') if false add all this other widgets (which is only a SliverToBoxAdapter)

Create customized SliverPersistentHeader like Widget with Flutter

I have a Header that consists of:
1 - appBar (title + return button)
2 - row with a data picker)
3 - a row serving as a customized TabBar (with two Tabs)
4 - a row serving as a FilterBar (with a Button and a search field)
Then I have the "body" with a list of Cards that is scrollable.
I needed to retract/hide numbers 1-3 and keep only number 4 visible when I scroll down the list of cards. How could I build a customized Widget to help me with that?
Here is part of the code (it's too long to bring everything):
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
backgroundColor: Colors.blue,
title: 'List of Tasks',
elevation: 0.0,
pinned: true,
),
SliverPadding(
padding: EdgeInsets.all(15),
sliver: SliverToBoxAdapter(
child: DatePicker(
date: date,
onChange: (newDate) {
setState(() {});
},
),
),
),
SliverToBoxAdapter(
child: ActivitiesTabController(
pageController: _pageController,
currentTab: currentTab,
onChangedTab: (currentTab) {},
),
),
SliverToBoxAdapter(
child: Row(
children: [
SearchFilterBar(
taskId: taskId,
onChange: (floors, serviceName) {},
),
],
),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return new Container(color: Colors.amberAccent, height: 150.0);
}),
),
],
));
}
I think you're referring to two app bars, where the first one can be scrolled away while the second one is persistent.
I think it would be something like this:
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: [
SliverAppBar(
title: Text('This hides'),
),
SliverAppBar(
title: Text('This is always shown'),
pinned: true,
primary: false,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, i) {
return ListTile(title: Text('Line $i'));
},
childCount: 150,
),
),
]),
);
}

Add Flutter AlertDialog over slivers?

My simple app is mostly a CustomScrollView of slivers. Works great, but now that I need to add AlertDialogs, I'm not sure where they'd fit among the slivers.
Below is my screen widget that creates SliverAppBar and SliverList. I inserted a test AlertDialog but am getting errors in my simulator
class QuestionsScreen extends StatelessWidget {
#override
Widget build(context) {
final List<Question> questions = Provider.of<Questions>(context).items;
return CustomScrollView(
slivers: <Widget>[
AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
SliverAppBar(
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: ChangeNotifierProvider.value(
value: Score(),
child: ScoreText(),
),
),
floating: true,
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
"Fixed Wing Frat\n\n\n\n\n",
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
background: Image.asset(
"assets/images/PalFM_blue.jpg",
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ChangeNotifierProvider.value(
value: questions[index],
child: QuestionCard(),
);
},
childCount: questions.length,
),
),
],
);
}
}
My app widget that builds the screen is here
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
Questions questions = Questions();
questions.loadFromLocal(context).then((_) {
questions.loadFromOnline(context);
});
return ChangeNotifierProvider.value(
value: questions,
child: MaterialApp(
title: 'FRATpal',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.lightBlue,
fontFamily: 'Lato',
),
home: QuestionsScreen(),
routes: {
UnusedDetailScreen.routeName: (_) => UnusedDetailScreen(),
},
),
);
}
Where would be a good place to insert my AlertDialog? Below are the errors.
The AlertDialog is not meant to be visible inside another component, but is designed to stay above everyone for a short period (for example error messages)
The AlertDialog should be pushed as a modal page. A good way to do this is to use the showDialog method.
The Flutter documentation on the AlertDialog explains how to do this.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
}

Flutter How to check if Sliver AppBar is expanded or collapsed?

I am using a SliverAppBar in Flutter, with a background widget.
The thing is When it's expanded, the title and icons (leading and actions) should be white in order to be seen correctly, and when it's collapsed, they should be changed to black.
Any ideas on how I can get a bool out of it? Or other ways of resolving this problem.
Thank you.
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.body,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
// I need something like this
// To determine if SliverAppBar is expanded or not.
bool isAppBarExpanded = false;
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: itemColor, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: itemColor, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: itemColor, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.body),
],
),
);
}
}
You can use LayoutBuilder to get sliver AppBar biggest height. When biggest.height = MediaQuery.of(context).padding.top + kToolbarHeight, it actually means sliver appbar is collapsed.
Here is a full example, in this example MediaQuery.of(context).padding.top + kToolbarHeight = 80.0:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var top = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// print('constraints=' + constraints.toString());
top = constraints.biggest.height;
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedOpacity(
duration: Duration(milliseconds: 300),
//opacity: top == MediaQuery.of(context).padding.top + kToolbarHeight ? 1.0 : 0.0,
opacity: 1.0,
child: Text(
top.toString(),
style: TextStyle(fontSize: 12.0),
)),
background: Image.network(
"https://images.unsplash.com/photo-1542601098-3adb3baeb1ec?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=5bb9a9747954cdd6eabe54e3688a407e&auto=format&fit=crop&w=500&q=60",
fit: BoxFit.cover,
));
})),
];
},body: ListView.builder(
itemCount: 100,
itemBuilder: (context,index){
return Text("List Item: " + index.toString());
},
),
));
}
}
Final result:
You need to use ScrollController to achieve the desired effect
try this code
import 'package:flutter/material.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SliverExample(
bodyWidgets: Text(
'Hello Body gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg'),
backgroundWidget: Text('Hello Background'),
),
);
}
}
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.bodyWidgets,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
ScrollController _scrollController;
Color _theme ;
#override
void initState() {
super.initState();
_theme = Colors.black;
_scrollController = ScrollController()
..addListener(
() => _isAppBarExpanded ?
_theme != Colors.white ?
setState(
() {
_theme = Colors.white;
print(
'setState is called');
},
):{}
: _theme != Colors.black ?
setState((){
print(
'setState is called');
_theme = Colors.black;
}):{},
);
}
bool get _isAppBarExpanded {
return _scrollController.hasClients &&
_scrollController.offset > (200 - kToolbarHeight);
}
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
//Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: _theme, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: _theme, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: _theme, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.bodyWidgets),
],
),
);
}
}
if you are not familiar with ? : notation you can use the following
_scrollController = ScrollController()
..addListener(
(){
if(_isAppBarExpanded){
if(_theme != Colors.white){
setState(() {
_theme = Colors.white;
print('setState is called with white');
});
}
}else{
if(_theme != Colors.black){
setState(() {
_theme = Colors.black;
print('setState is called with black');
});
}
}
}
Use NestedScrollView that have innerBoxIsScrolled boolean flag that will be a decent solution for your problem something like this below
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
print("INNEER SCROLLED VI=======>$innerBoxIsScrolled");
return <Widget>[
SliverAppBar(),
]},
body:Center(child:Text("Test IT")),
);
You can use SliverLayoutBuilder to get the current SliverConstraints and read its value, to easily detect how much scrolling has occurred. This is very similar to LayoutBuilder except it's operating in the sliver-world.
If constraints.scrollOffset > 0, that means the user has scrolled at least a little bit. Using this method, if you want to do some animation/transition when scrolling, it's easy too - just get the current scrollOffset and generate your animation frame based on that.
For example, this SliverAppBar changes color when user scrolls:
SliverLayoutBuilder(
builder: (BuildContext context, constraints) {
final scrolled = constraints.scrollOffset > 0;
return SliverAppBar(
title: Text('Sliver App Bar'),
backgroundColor: scrolled ? Colors.blue : Colors.red,
pinned: true,
);
},
)
This works for me
check this line
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black:Colors.white),),
change your title to
title: innerBoxIsScrolled? Text("hello world") : Text("Good Morning",)
class _ProductsState extends State<Products> {
String image;
String title;
_ProductsState(this.image,this.title);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
leading: InkWell(
onTap: (){
},
child: Icon(
Icons.arrow_back_ios,
color: Colors.black,
),
),
backgroundColor: Colors.white,
pinned: true,
//floating: true,
stretch: true,
expandedHeight: 300.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black: Colors.white),),
background: CachedNetworkImage(imageUrl:image,fit: BoxFit.fitWidth,alignment: Alignment.topCenter,
placeholder: (context, url) => const CircularProgressIndicator(color: Colors.black,),
errorWidget: (context, url, error) => const Icon(Icons.error),),
),
),
),
];
},
body: Builder(
builder:(BuildContext context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverToBoxAdapter(
child: Container(
height: 90,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
color: Colors.green,
),
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
],
);
}
),
),
);
}
}