Flutter - restrict widget to match others widget height - dart

I would like to create list item widget with photo, and some information on the right side of this photo. Since web Image may be way to large, I also want my photo widget to match the height (while maintaining aspect ration) with "content" widgets height, possibly without giving an explicit height.
For now my widget looks like this:
In Android I would restrict image size with:
constraintTop_toTopOf(contentId)
constraintBottom_toBottomOf(contentId)
Is it possible to do similar thing in Flutter ?
For now my code looks like this (I would like to avoid giving explicit height in 'maxHeight' parameter of ConstrainedBox):
class SpeakerItemWidget extends StatelessWidget{
final Speaker _speaker;
SpeakerItemWidget(this._speaker);
Widget _speakerItemContent() =>
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'${_speaker.name} ${_speaker.surname}',
style: TitleTextStyle(),
),
SizedBox(height: Margin.small),
Text(
_speaker.title,
style: DescriptionTextStyle(),
),
Text(
_speaker.company,
style: DescriptionTextStyle(),
)
],
);
#override
Widget build(BuildContext context) =>
InkWell(
splashColor: Theme.of(context).accentColor,
onTap: () => debugPrint('open event details'),
child: Padding(
padding: EdgeInsets.all(Margin.small),
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 60,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FittedBox(
fit: BoxFit.contain,
child: SpeakerPhotoWidget(_speaker),
),
_speakerItemContent(),
],
),
),
),
);
}
class SpeakerPhotoWidget extends StatelessWidget{
final Speaker _speaker;
SpeakerPhotoWidget(this._speaker);
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
//initial size of widget is 0,0 - causes crash when inside FittedBox
minHeight: 1,
minWidth: 1,
),
child: Padding(
padding: EdgeInsets.only(right: Margin.small),
child: FadeInImage.assetNetwork(
placeholder: 'assets/images/speaker_placeholder.png',
image: 'some uri',
fit: BoxFit.contain,
),
)
);
}
}

Related

I want to make a listview within a column take up all available space

I am making a settings page and the layout is a container with a set height and then under it a listview however this listview needs a set height; so it works when I wrap it in a container and give it a height however it doesn't work if I wrap it in a expanded.
I have tried many things like putting it in a layout builder and giving the height as BoxConstraints.maxHeight and a lot of other tricks which should work but don't.
class Settings extends StatelessWidget {
Settings({#required this.userInfo, #required this.licenseInfo});
final userInfo;
final licenseInfo;
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
child: Scaffold(
resizeToAvoidBottomPadding: false,
backgroundColor: Colors.transparent,
appBar: AppBar(
elevation: 0.0,
backgroundColor: Color(0xFF77FDA7),
title: Text('Settings',
style: TextStyle(color: darkGrey, fontWeight: FontWeight.w600)),
),
body: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
height: 91,
width: width,
decoration: BoxDecoration(gradient: greenGradient),
),
Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SettingsProfile(
userInfo: this.userInfo,
licenseInfo: this.licenseInfo),
SizedBox(
height: 300,
child: SettingsList(),
),
],
),
],
),
],
)),
);
}
}
As you see there is a stack and another column however these are just parts of the UI and I need them. They do not have anything to do with me trying to fix this issue. PLEASE HELP as I need this listview to take up all available space in the column without overflowing.
I had a similar use-case and what I did on my app was wrap LayoutBuilder with Expanded. The LayoutBuilder fetches the dimensions of available screen space for the widget and it can be used to set the Widget's height.
Expanded(
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints constraints) {
return Container(
height: constraints.maxHeight, // fetch height with Constraints
width: constraints.maxWidth,
child: ListView.builder(...),
);
}
),
)

Expanded widget inside Column not expanded, instead it's got invisible

I am trying to expand widget inside of Column widget but not able to make it expended.
When giving constant height to parent widget, the layout will be rendered as expected. But as I remove the constant height layout is not as expected as I want to make Listview with it and I should not give a constant height to the widget which will be used as listview item.
Below is my layout code.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
// height: 200, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child:Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: Center(
child: ListView(
children: <Widget>[
cell(),
],
)
),
);
}
}
Below is my expected output screenshot.
Try to wrap your Container with IntrinsicHeight
return IntrinsicHeight(
Container(
color: Colors.yellow
child: ...
)
)
Your ListView needs to be inside Flexible. Flexible inside Column will set maximum height available to ListView. ListView needs a finite height from parent, Flexible will provide that based on max. space available.
Column(
children: <Widget>[
Flexible(
child: ListView.builder(...)
),
Container(
color: Colors.red,
child:Text('apple 2'),
),
],
)
A nice way of doing this, it's to play with the MediaQuery, heigth and width.
Let me explain, if you want the widget to have the maximum heigth of a decide screen, you can set it like this:
Container(
height: MediaQuery.of(context).size.height // Full screen size
)
You can manipulate it by dividing by 2, 3, 400, the value you want.
The same things works for the width
Container(
width: MediaQuery.of(context).size.width // Can divide by any value you want here
)
Actually quite the opposite, if you're planning to use this as an item in a listViewyou can't let infinite size on the same axis your listView is scrolling.
Let me explain:
Currently you're not defining any height on your cell() widget, which is fine if you're using it alone. like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
//height: 250, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: cell(),
);
}
}
But using it with a listView you have to define a height. A listView scrolls as long as it have some content to scroll. Right now it just like you're giving it infinite content so it would scroll indefinitely. Instead Flutter is not constructing it.
It's actually quite ok to define a global size for your container (as an item). You can even define a specific size for each using a parameter like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell(double height) {
return Container(
color: Colors.yellow,
height: height, //after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: ListView(
children: <Widget>[
cell(250.0),
cell(230.0),
cell(300.0),
],
)
);
}
}

Center Expanded ListView inside Column Flutter

I'm trying to build a layout where there are two Text objects at the top and bottom which stays stationery and a ListView at their center.
Here's the code for the Screen
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
DateFormat("hh:mm 'PM ,' MMMM d").format(DateTime.now()),
style: Theme.of(context).textTheme.title,
),
),
Expanded(
child: ListView.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int index) =>
CustomAppText(
text: 'Custom App',
),
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
"Settings",
style: Theme.of(context).textTheme.title,
),
),
],
),
),
),
);
}
}
The output of the given code
The Design I'm looking for
I have tried using the Center widget but it does not center the ListView
The ListView fills the entire Expanded Widget, that's why using the Center widget didn't work, so shrinkWrap: true should be added so the ListView takes only the height of it's children.
After skimming through the documentation I found about Flexible Widget
Flexible, which does not force the child to fill the available space.
Made the change and works like a charm
Flexible(
child: ListView.builder(
shrinkWrap: true,
itemCount: 4,
itemBuilder: (BuildContext context, int index) =>
CustomAppText(
text: 'Custom App',
),
),
),
For those still looking for an answer, this is what worked for me:
Column(
children: [
Container(), // some top content
Expanded(
child: Center(
child: ListView(
shrinkWrap: true,
children: [] //your list view content here
)
)
),
Container(), // some bottom content
]
)
The Expanded widget makes the content take up all available space.
The Center widget centers the content you want to display.
The ListView holds your list content and the "shrinkWrap: true" property makes your list view shrink according to content size(allowing it to centralized by the Center widget when it's not taking a lot of space).
Hope it helps. Give the top and bottom widgets the 25% of the screen size. Give the listview the 50% of the screen size.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
#override
Widget build(BuildContext context) {
final _size = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(28.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Top Widgets
Container(
width: double.infinity,
// color: Colors.green,
height: _size.height * 0.25, // Take 25% width of the screen height
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('11: 25 AM', style: TextStyle(fontSize: 23.0),),
Text('Set As Launcher', style: TextStyle(fontSize: 23.0),)
],
),
),
Expanded(
child: Container(
// color: Colors.yellow,
child: ListView(
children: List.generate(25, (index){
return Text('Custom App $index', style: TextStyle(fontSize: 45.0),);
}),
),
),
),
// Bottom Widgets
Container(
width: double.infinity,
// color: Colors.blue,
height: _size.height * 0.25, // Take 25% width of the screen height
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Settings', style: TextStyle(fontSize: 23.0),),
],
),
)
],
),
),
),
);
}
}
Just add an Expanded as a wrapper for your first Container inside the Column
Expanded(
child: Container(
margin: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
DateFormat("hh:mm 'PM ,' MMMM d").format(DateTime.now()),
style: Theme.of(context).textTheme.title,
),
),
),
Just wrap your ListView.builder inside a container class and give it a height, either explicitly in double or as a percentage of screen height.
class Trial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
"Some Text",
style: Theme.of(context).textTheme.title,
),
),
Container(
// height: 40,
height: MediaQuery.of(context).size.height * 0.4,
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return Text("Hello");
}
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
"Settings",
style: Theme.of(context).textTheme.title,
),
),
],
),
),
),
);
}
}
Add two spacer it will give you 100% same result as you expected
Spacer(),
Expanded(
child: ListView.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int index) =>
CustomAppText(
text: 'Custom App',
),
),
),
Spacer(),
It have to use shrinkWrap: true and Flexible together to show
all items of ListView.
Flexible(
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
itemCount: models.length,
If you replace your Expanded widget with a Container and give that a fixed height, I think you get what you are looking for.

CircularProgressIndicator widget doesn't disappear when image loads

I'm using this dart package here
There's an example in the flutterio cookbook. My problem is, when the image loads the CircularProgressIndicator doesn't disappear. Here is the item for a ListView.
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
import 'models/post.dart';
class PostItem extends StatelessWidget {
const PostItem(this.post);
final Post post;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Stack(
children: <Widget>[
const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
)),
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Center(
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: post.imageUrl,
),
),
),
],
),
const SizedBox(height: 8.0),
Text(
post.username,
style: Theme.of(context).textTheme.headline,
),
const SizedBox(height: 8.0),
Text(
post.text,
style: Theme.of(context).textTheme.body1,
),
]);
}
}
A gif of the problem:
My understanding is, that once the image loads the CircularProgressIndicator should disappear?
You may try with cached_network_image plugin
new CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
),
That happens because you're stacking a CircularProgressIndicator widget with a ClipRect.
When its first build, you don't have an url and that's the reason why the image isn't showing up. However, as soon as it has content and you rebuild your parent tree (where your PostItem widget is inserted) it will refresh with the image but the progress bar won't be gone because doesn't have any condition to prevent its render.
To solve it, the easy way, just add a condition, if you have an url, then you show a CircularProgressBar otherwise, a ClipRect with your content.
(...)
Stack(
children: <Widget>[
post?.url != null
? const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
))
: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Center(
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: post.imageUrl,
),
),
),
],
),
(...)
If you don't plan to stack items, you can actually remove the Stack widget and place it directly within your column. Below is the complete example (without the Stack).
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
import 'models/post.dart';
class PostItem extends StatelessWidget {
const PostItem(this.post);
final Post post;
#override
Widget build(BuildContext context) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
post?.url != null
? const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
))
: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Center(
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: post.imageUrl,
),
),
),
const SizedBox(height: 8.0),
Text(
post.username,
style: Theme.of(context).textTheme.headline,
),
const SizedBox(height: 8.0),
Text(
post.text,
style: Theme.of(context).textTheme.body1,
),
]);
}
}

Flutter Overlapped InkWells Gesture Detection

While working on an application, we have an instance where we want a card to have an inkwell as well as a button on the card (also with an inkwell). However, I have been unable to determine a way to separate the gestures such that only the inkwell directly under the user's tap is invoked. As it is today, it appears that the tap 'bleeds through' to the next inkwell such that both splash effects are invoked. This is undesirable behavior, the application appears to be selecting the card not the invokable item on the card (note: actual application is much different but the same issue is present). I have reproduced this in a simple application to demonstrate the bleed through when the user pressed the button in the bottom right of the card. Is there something I am missing which can prevent this behavior? Thanks
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Card(
color: Colors.blue,
child: InkWell(
onTap: () { },
child: Container(
height: 150.0,
child: Align(
alignment: Alignment.bottomRight,
child: RaisedButton(
color: Colors.red,
onPressed: () { },
),
),
),
),
),
);
}
}
This is the normal expected InkWell behavior as most of the time you want to use it's tap feature for every widget in it's tree. So what you can do is to define a Stack and set the button in the z-axis absolute over the InkWell:
Widget build(BuildContext context) {
return new Scaffold(
body: Center(
child: Card(
color: Colors.blue,
child: Stack(
children: <Widget>[
InkWell(
onTap: () {
print("inkwell");
},
child: Container(
height: 150.0,
),
),
RaisedButton(
color: Colors.red,
onPressed: () {
print("button");
},
),
],
),
),
),
);
}
If you would want to set the button in the bottom right corner again you can set a Row and Colum around it and assign it's alignment:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Center(
child: Container(
height: 150.0,
child: Card(
color: Colors.blue,
child: Stack(
children: <Widget>[
InkWell(
onTap: () {
print("inkwell");
},
child: Container(
height: 150.0,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
RaisedButton(
color: Colors.red,
onPressed: () {
print("button");
},
),
],
),
],
),
],
),
),
),
),
);
}
Upper code would result in seperated widgets:

Resources