How can I make a Card height extend based on content? - flutter

I am creating a Card which has a header and a grid view of photos. Below is the code:
_getImageWidget(Post post, AppConfig config) {
if (post.photoPaths != null && post.photoPaths.length > 0) {
var url = config.imagePathDomain + post.photoPaths[0];
try {
return Expanded(
child: GridView.count(
crossAxisCount: 3,
shrinkWrap: false,
children: post.photoPaths.map<Widget>((String path) {
return CachedNetworkImage(
imageUrl: url,
);
}).toList()));
} catch (err) {
return Container();
}
}
return Container();
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
return BlocBuilder<UserInfoBloc, UserInfo>(builder: (_, userInfo) {
return Container(
width: MediaQuery.of(context).size.width,
child: Card(
child: Column(children: <Widget>[
Row(
children: <Widget>[
IconButton(
iconSize: 30,
icon: roundImage(post.userPicture, Icon(Icons.person)),
onPressed: () {},
),
Text('#${userInfo.username}')
],
),
this._getImageWidget(post, config),
])));
});
}
The header in the Card is a Row includes a IconButton and Text.
The body of the Card is a gridview which includes a few photo.
Below is the screenshot when I run the code. You can see that the photo is shown only a half. And I can scroll vertically on the grid view. The number of photos is dynamic which means there could be many rows of photos in the GridView. How can I make the Card extend based on its children?

By simply setting your CachedNetwork image to use a fit: BoxFit.cover that will resize to fill the content while preserving ratio (this means you may lose some of its details) or fit: BoxFit.contain that will try to be as big as possible while containing the entire source (image) within the bounds.
If that doesn't help as well (as I'm not seeing the entire tree so I'm not sure about the ancestors of your Card) you can also replace the return of your BlocBuilder's child to be a FittedBox instead of a Container and apply the same logic for the fit property, but for the whole card instead.

Try using a fixed size container and using the BoxFit property on the container.
Something like this :
new Container(
width: 80.0,
height: 80.0,
decoration: new BoxDecoration(
shape: BoxShape.rectangle,
image: new DecorationImage(
fit: BoxFit.fill,
image: CachedNetworkImageProvider(url),
),
),
),
Edit : Try to remove itemExtent from ListView.builder

Related

Flutter Card child content height is larger than its parent

I'm trying to use a GridView to handle displays for multiple Card, each Card contains of an Image. Unfortunately it turns out that the Image is taking a larger height than its parent (see attached picture for the details).
I'm pretty new to Flutter layout so any ideas why this is happening and how I can resolve this? I want the layout to be something like this:
Display 2 cards on each line.
The Card width or height should not be fixed.
The Image height should be scaled according to its width.
class SquadSelectionScreen extends StatelessWidget {
final List<Team> teams;
const SquadSelectionScreen({super.key, required this.teams});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Squads'),
),
body: GridView.count(
crossAxisSpacing: 10,
crossAxisCount: 2,
padding: const EdgeInsets.all(16),
children: teams
.map(
(team) => SquadView(team: team),
)
.toList(),
),
);
}
}
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.push('/squads/${team.code}');
},
child: Card(
elevation: 1,
child: Column(
children: [
Image(
image: NetworkImage(team.imageUrl),
),
const SizedBox(
height: 8,
),
Center(
child: Text(team.name),
),
],
),
),
);
}
}
Using GridView.count has a very visible drawback, namely the size of the aspect ratio of the grid will always be one (1:1 or Square) and can't be changed.
So if you look at the code above, you can't set an image with the same aspect ratio because the text will sink.
The first suggestion for me if you still want to use GridView.count is
Wrapping your Image with AspectRatio that has value higher than one (example set Ratio to 4/3, 5/3, 16/9, or landscape looks). Note: 4/3 = is higher than 1, 16/9 = is higher than 1, etc..
Then wrap the Text Widget with Expanded()
Example code:
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
AspectRatio(
aspectRatio: 4/3, // you can set the value to 16/9 or anything that result is higher than one
child: Image(
image: NetworkImage(team.imageUrl),
fit: BoxFit.cover, // set How the image looks to Fit
),
),
const SizedBox(
height: 8,
),
Expanded(
child: Center(
child: Text(team.name, overflow: TextOverflow.ellipsis),
),
),
],
),
),
),
);
}
}
I suggest you try GridView.builder or another GridView. You can look at the documentation here
or this third package this will be good for to try flutter_staggered_grid_view. The flutter_staggered_grid_view is more flexible to create GridView with various size.

Is there any way to List the list views size in flutter?

I am new to flutter and trying to develop a version updation software. I need to show the version in a radio list. Is there any way to contain the list view in a container? Currently the list view is overflowing through the other widgets. I need to contain the list view between the text and the button. Is there any way to do it.The code the screenshot is given below
[![screenshot][1]][1]
Widget _createVersionRadioTiles(List<String> versions) {
var radioTiles = <Widget>[];
for (var version in versionList) {
var tile = Padding(
padding: const EdgeInsets.all(8.0),
child: RadioListTile<String>(
selected: selectedVersion == version,
tileColor: colorDarkGray,
// selectedTileColor: Colors.white,
value: version,
groupValue: selectedVersion,
onChanged: (String? value) {
setState(() {
selectedVersion = value!.toString();
});
},
title: Text(version),
),
);
radioTiles.add(tile);
}
return Column(
children: [
SizedBox(
height: 500,
width: 500,
child: ListView(children: radioTiles),
)
],
);
} ```
[1]: https://i.stack.imgur.com/qQxns.jpg
Wrap the RadioListTile with a Card widget. It will contain the ListTile in the container.
You can replace SizedBox with Container, and use clipBehavior: Clip.hardEdge to remove the overflows. see code below:
return Column(
children: [
Container(
height: 500,
width: 500,
clipBehavior: Clip.hardEdge,
child: ListView(children: radioTiles),
)
],
);

Putting a CircularProgressIndicator below each image

There are a lot of such stack elements on the page:
Stack(
children: <Widget>[
SizedBox(
height: 150,
width: 150,
child: Center(child: CircularProgressIndicator()),
),
Center(child: _getImage(imagePath)),
],
),
So there will be a CircularProgressIndicator below each image. Does it reduce the ui speed? When the Progress Indicator widget is ovelapped by the other widget will be disabled then?
First off you may use const on the constructor for better performance. Secondly, if you want to show a progress indicator when the image is loading maybe try using a FutureBuilder to check if the image is loading, if so return a CircularProgressIndicator else return the image.
But generally speaking flutter should be able to render tons of CircularProgressIndicator and yes it will still render the CircularProgressIndicator even if your image is shown and will therefore take up resources. If you dont want it to show and take resources you could warap it in a Visablity widget but I highly recommend using a futurebuilder so you can maintain the state of the image loading.
I think you are using progress loaders for image loading state, but I'd go with a package.
extended_image: ^4.1.0 seems like a good one and check the code below. It allows you to interfere in Loading State of the image, and you can put whatever widget in place of that state. This package does more than your intended use as well.
ExtendedImage.network(
url,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
fit: BoxFit.fill,
cache: true,
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
_controller.reset();
return Image.asset(
"assets/loading.gif",
fit: BoxFit.fill,
);
break;
///if you don't want override completed widget
///please return null or state.completedWidget
//return null;
//return state.completedWidget;
case LoadState.completed:
_controller.forward();
return FadeTransition(
opacity: _controller,
child: ExtendedRawImage(
image: state.extendedImageInfo?.image,
width: ScreenUtil.instance.setWidth(600),
height: ScreenUtil.instance.setWidth(400),
),
);
break;
case LoadState.failed:
_controller.reset();
return GestureDetector(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(
"assets/failed.jpg",
fit: BoxFit.fill,
),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Text(
"load image failed, click to reload",
textAlign: TextAlign.center,
),
)
],
),
onTap: () {
state.reLoadImage();
},
);
break;
}
},
)

Flutter async method keeps running even after the corresponding widget is removed

I have a list of image paths and I am using the List.generate method to display images and a cross icon to remove image from list. Upload method is called on each image and when I remove the image from the list the method still keeps running until the image is uploaded. The behavior I am expecting is when I remove the image from the list the method should also stop running. I am using a future builder to display the circular progress bar and error icons while uploading an image.
What should I be doing to make sure the future method associated to the current widget also stops when I remove the widget from the list?
This is the code where I am creating a list
List.generate(
files.length,
(index) {
var image = files[index];
return Container(
height: itemSize,
width: itemSize,
child: Stack(
children: <Widget>[
Container(
getImagePreview(image, itemSize)
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
uploadHandler(image, field),
InkWell(
onTap: () => removeFileAtIndex(index, field),
child: Container(
margin: EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.7),
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: 22,
width: 22,
child: Icon(Icons.close, size: 18, color: Colors.white),
),
),
],
),
],
),
);
},
)
This is Upload Handler method.
Widget uploadHandler(file, field) {
return FutureBuilder(
future: upload(file),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.statusCode == 201) {
return doneUpload();
} else {
logger.d(snapshot.error);
return error();
}
} else {
return uploading();
}
},
);
}
The lifecycle of the widget isn't attached to the async functions invoked by the widget.
You can check the mounted variable to check if the widget still mounted from your async function.

Assign an image dynamically in flutter

How can I assign an image dynamically in flutter ? For example:
final topContent = Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage(lesson.imagePath),
fit: BoxFit.cover,
),
)),
Container(
height: MediaQuery.of(context).size.height * 0.5,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
child: SingleChildScrollView(
controller: _scrollController,
child: Center(
child: topContentText,
),
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
Now the image at the start lesson.imagePath is what I want to change dynamically. I tried to use setState() but it gives me an error:
The expression here is a type of void and cannot be used
image: setState ((){
if (someCondition) {
return new AssetImage(lesson.imagePath);
}
}),
Your setState call is wrong! The most simple way is make your image as state of your widget and update this image inside a setState call. setState method does not returns nothing it just rebuilds your widget.
In your _WidgetState class you declare as member:
AssetImage _imageToShow;
You can provider a initial image inside initState method.
#override
initState(){
_imageToShow = AssetImage('youAssetImage');
}
Your Container widget should be declared as:
Container(
padding: EdgeInsets.only(left: 10.0),
height: MediaQuery.of(context).size.height * 0.5,
decoration: new BoxDecoration(
image: new DecorationImage(
image: _imageToShow,
fit: BoxFit.cover,
),
)),
),
And to update your image with setState call you just need:
void updateImage() {
setState ((){
if (someCondition) {
_imageToShow = new AssetImage(lesson.imagePath);
}
});
}
But remember that something has to call updateImage method.
The above solution can work also you can set an array of names and you can set the same image name in the assets folder and you can dynamically select the image where you want to use.
suppose in your case you have a list of lessons.
var lesson = ['a','b','c'];
In assets, folder give the same name to the images. (Don't forget to update the pubspec.yaml file)
Then in the AssetImage you can give the path which can be chosen dynamically.
image:AssetImage('assets/${lesson[index]}.jpg')
Remember to give the same name to the image like here a,b and c to the image.
Also the same extension to be given like here .jpg
image:AssetImage('assets/${lesson[index]}.jpg')