How to implement this complex view in the flutter?
I am trying to implement a GridView
with n columns and the child should be of a certain aspect ratio(say 1.3) but the height of the child should be (wrap content in Android terminology).
I am stuck because as fas I understand GridView's childAspectRatio:1.3
(default:1) always lays out the child in same aspect ratio but not dynamic content.
Note: Child should expand its height according to the image's height
Use case: I am trying to implement a view like below, in which image is wrapped height = wrap content
so that in case an image with stretched height can look good and form a StaggeredGridView
like structure.
Adjust a Height of Item The Child Aspect Ratio property of the GridView allows you to do that. By default, the value for the Child Aspect Ratio is set to 1. That means the height of the item will be of the same size as of width of the item on the main axis.
Edit: I added the constructor StaggeredTile.fit
in the 0.2.0. With that you should be able to build you app ;-).
First comment: For now with StaggeredGridView, the layout and the children rendering are completely independant. So as @rmtmckenzie said, you will have to get the image size to create your tiles. Then you can use StaggeredTile.count
constructor with a double value for the mainAxisCellCount
parameter: new StaggeredTile.count(x, x*h/w)
(where h
is the height of your image and w
its width. So that the tile with have the same aspect ratio as your image.
What you want to accomplish will need more work because you want to have an area below the image with some information. For that I think you will have to compute the real width of your tile before creating it and use the StaggeredTile.extent
constructor.
I understand this is not ideal and I'm currently working on a new way to create the layout. I hope it will help to build scenarios like yours.
First let me tell you about how I ended up here:
In my application I wanted a grid view to display my ad cards and all the data coming from the server database and images are coming from the server and images are in different sizes. I used FutureBuilder
to map those data to GridView
. First I tried to use:
double cardWidth = MediaQuery.of(context).size.width / 3.3; double cardHeight = MediaQuery.of(context).size.height / 3.6; //.... GridView.count( childAspectRatio: cardWidth / cardHeight, //..
As you can see it will not dynamic to all cards. I came here like you and tried to use all answers those are great and you have to tackle a bit to understand how, but any of those answer completely solved my issue.
Using @RomainRastel answer and thanks to his StaggeredGridView
package. I had to use StaggeredGridView.count
as my constructor to map all cards and for the staggeredTiles
property I had to again map all cards and add for each StaggeredTile.fit(2)
.
I'm sure you didn't get it still, so let's try a simple example so that you do not need to go somewhere else to find an answer:
First add dependency to pubspec.yaml
, now version is 0.2.5
. You can checkout the latest one here.
dependencies: flutter_staggered_grid_view: ^0.2.5
If you are fetching data from internet or if you are going to copy paste this example, you have to also ad this dependency: http: ^0.12.0
.
import 'package:flutter/material.dart'; //this is what you need to have for flexible grid import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; //below two imports for fetching data from somewhere on the internet import 'dart:convert'; import 'package:http/http.dart' as http; //boilerplate that you use everywhere class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "Flexible GridView", home: HomePage(), ); } } //here is the flexible grid in FutureBuilder that map each and every item and add to a gridview with ad card class HomePage extends StatelessWidget { //this is should be somewhere else but to keep things simple for you, Future<List> fetchAds() async { //the link you want to data from, goes inside get final response = await http .get('https://blasanka.github.io/watch-ads/lib/data/ads.json'); if (response.statusCode == 200) return json.decode(response.body); return []; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Dynamic height GridView Demo"), ), body: FutureBuilder<List>( future: fetchAds(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return new Padding( padding: const EdgeInsets.all(4.0), //this is what you actually need child: new StaggeredGridView.count( crossAxisCount: 4, // I only need two card horizontally padding: const EdgeInsets.all(2.0), children: snapshot.data.map<Widget>((item) { //Do you need to go somewhere when you tap on this card, wrap using InkWell and add your route return new AdCard(item); }).toList(), //Here is the place that we are getting flexible/ dynamic card for various images staggeredTiles: snapshot.data .map<StaggeredTile>((_) => StaggeredTile.fit(2)) .toList(), mainAxisSpacing: 3.0, crossAxisSpacing: 4.0, // add some space ), ); } else { return Center( child: new CircularProgressIndicator()); // If there are no data show this } }), ); } } //This is actually not need to be a StatefulWidget but in case, I have it class AdCard extends StatefulWidget { AdCard(this.ad); final ad; _AdCardState createState() => _AdCardState(); } class _AdCardState extends State<AdCard> { //to keep things readable var _ad; String _imageUrl; String _title; String _price; String _location; void initState() { setState(() { _ad = widget.ad; //if values are not null only we need to show them _imageUrl = (_ad['imageUrl'] != '') ? _ad['imageUrl'] : 'https://uae.microless.com/cdn/no_image.jpg'; _title = (_ad['title'] != '') ? _ad['title'] : ''; _price = (_ad['price'] != '') ? _ad['price'] : ''; _location = (_ad['location'] != '') ? _ad['location'] : ''; }); super.initState(); } @override Widget build(BuildContext context) { return Card( semanticContainer: false, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Image.network(_imageUrl), Text(_title), Text('\$ $_price'), Text(_location), ], ), ); } }
If you have any issue, here is complete example in a git repository.
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With