20

Grid of categories with image and category name displayed below the image

 Widget build(BuildContext context) {
return FutureBuilder(
    future: categoriesService.getCategories(1),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.connectionState == ConnectionState.done) {
        if (snapshot.error != null) {
          print('error ${snapshot.error}');
          return Text(snapshot.error.toString());
        }
        // YOUR CUSTOM CODE GOES HERE
        return Container(
          // padding: const EdgeInsets.all(0.0),
          child: GridView.builder(
            physics: NeverScrollableScrollPhysics(),
            shrinkWrap: true,
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              // childAspectRatio: 19 / 12,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
            ),
            itemCount: snapshot.data.length,
            itemBuilder: (context, index) {
              Category category = snapshot.data[index];
              return Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  Container(
                    child: Image.network(
                      category.image,
                      fit: BoxFit.cover,
                    ),
                    decoration: BoxDecoration(
                      border: Border.all(width: 1.0),
                    ),
                  ),
                  Text(category.name)
                ],
              );
            },
          ),
        );
      } else {
        return new CircularProgressIndicator();
      }
    });

}

My child item has an image and category name. as seen in the image, currently child item is overflowing and we cant see the category name below the image, and unable to remove top space between image and border.

enter image description here

Original design is here

enter image description here

maaz
  • 3,534
  • 19
  • 61
  • 100
  • 3
    child aspect ratio is basically width/height of the grid. So let's say you want the width of each grid to be 30 and the height to be 20, you would set the aspect ratio to be 3/2. – ByteMe Jul 05 '20 at 23:43
  • 1
    Not solved my problem yet. I have to show 1:1 image and at the bottom of each image want to add a container of full available width with 100 pixels height. Kindly suggest, how can we do it. Thanks. – Kamlesh May 25 '21 at 17:29

9 Answers9

18

You could use the width and height of the device to calculate the aspect ratio dynamically. I have added a code sample based on yours showing how this could be done. Please note that you might need to adjust the provided aspect ratio slightly in order to fit your particular requirements.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class Category {
  String name;
  String image;

  Category({this.name, this.image,});
}

class CategoriesService {
  Future<List<Category>> getCategories(int value) async {
    return <Category>[
      Category(
        name: 'FRESH CHICKEN',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'FRESH MUTTON',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'HALAL FROZEN FISH',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: '2X STORE',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'FROZEN NONVEG DELIGHTS',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'FROZEN VEG DELIGHTS',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'DAL & PULSES',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'SPICES',
        image: 'https://picsum.photos/400/300',
      ),
      Category(
        name: 'DRY FRUITS & NUTS',
        image: 'https://picsum.photos/400/300',
      ),
    ];
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: FutureBuilder(
          future: CategoriesService().getCategories(1),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.error != null) {
              print('error ${snapshot.error}');
              return Text(snapshot.error.toString());
            }
            // YOUR CUSTOM CODE GOES HERE
            return Container(
              // padding: const EdgeInsets.all(0.0),
              child: GridView.builder(
                physics: NeverScrollableScrollPhysics(),
                shrinkWrap: true,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  childAspectRatio: MediaQuery.of(context).size.width /
                      (MediaQuery.of(context).size.height / 1.4),
                  mainAxisSpacing: 10.0,
                  crossAxisSpacing: 10.0,
                ),
                itemCount: snapshot.data.length,
                itemBuilder: (context, index) {
                  Category category = snapshot.data[index];
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: <Widget>[
                      Container(
                        child: Image.network(
                          category.image,
                          fit: BoxFit.cover,
                        ),
                        decoration: BoxDecoration(
                          border: Border.all(width: 1.0),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.only(
                          top: 8.0,
                        ),
                        child: Text(
                          category.name,
                          textAlign: TextAlign.center,
                        ),
                      )
                    ],
                  );
                },
              ),
            );
          } else {
            return new CircularProgressIndicator();
          }
        }),
      ),
    );
  }
}

iPad Pro iPhone X Galaxy S5

You might find the below answers useful for additional information regarding the aspect ratio of the GridView widget if you have not seen them already.

tnc1997
  • 1,832
  • 19
  • 20
  • I have to show grid in 1:1 (width:height) ratio but height should have additional 100 pixels extra. eg. How can i do? Kindly suggest. Thanks. – Kamlesh May 25 '21 at 15:07
13

I find a way to set aspect ratio of biggest child to our Grid view dynamicly. how it work ? First please Look at this answer for . how we can find size of a widget during the build with overlayEntery

How to get a height of a Widget?

after that we set the right aspect ratio (Measured with overlayEntery ) to our Grid View in childAspectRatio.

I hope this help you .

I Make an Example ...

https://dartpad.dev/4821d71ec618d7d1f1f92f27458fde61



import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}
class GridItemModel {
  String longtext;
  GlobalKey itemKey;
  double width;
  double height;
  GridItemModel(this.longtext) {
    itemKey = GlobalKey();
  }
}
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,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }

}
class MyHomePage extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _StateHomePage();
  }

}

class _StateHomePage extends State<MyHomePage> {
  // this first assign very important don't change it .
  // if you change this part overlayEntry cant find biggest widget correctly .(cant see not scrolled items.)
  // for test change to 1/1 and see what happening.
  var myDynamicAspectRatio = 1000 / 1;
  OverlayEntry sticky;
  List<GridItemModel> myGridList = new List();
  double maxHeight = 0;
  double maxWidth = 0;

  @override
  void initState() {
    if (sticky != null) {
      sticky.remove();
    }
    sticky = OverlayEntry(
      builder: (context) => stickyBuilder(context),
    );

    WidgetsBinding.instance.addPostFrameCallback((_) {
      Overlay.of(context).insert(sticky);
      setState(() {});
    });
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      sticky.remove();
    });
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    final title = 'Grid List';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: GridView.count(
            crossAxisCount: 2,
            childAspectRatio: myDynamicAspectRatio,
            children: List.generate(20, (index) {
              myGridList.add(new GridItemModel(longTextMaker(index)));
              return Column(
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  Container(
                    key: myGridList[index].itemKey,
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.black),
                      color: Colors.teal[index*100]
                    ),
                    child: Text(
                      'Item $index' + myGridList[index].longtext,
                      style: Theme.of(context).textTheme.headline5,
                    ),
                  ),
                ],
              );
            }),
          ),
        ),
    );
  }
  String longTextMaker(int count){
    String  result = "longText";
    for(int i = 0 ; i < count; i++){
      result += "longText" ;
    }
    return result;
  }
  shouldUpdateGridList(){
    bool isChanged =false;
    for(GridItemModel gi in myGridList) {
      if(gi.width != null) {
        if (gi.height > maxHeight) {
          maxHeight = gi.height;
          maxWidth = gi.width;
          isChanged = true;
        }
      }
    }
    if(isChanged) {
        myDynamicAspectRatio = maxWidth / maxHeight;
        print("AspectRatio" + myDynamicAspectRatio.toString());
    }
  }

  Widget stickyBuilder(BuildContext context) {
    for(GridItemModel gi in myGridList) {
      if(gi.width == null) {
        final keyContext = gi.itemKey.currentContext;
        if (keyContext != null) {
          final box = keyContext.findRenderObject() as RenderBox;
          print(box.size.height);
          print(box.size.width);
          gi.width = box.size.width;
          gi.height = box.size.height;
        }
      }
    }
    shouldUpdateGridList();
    return Container();
  }
}

Sajjad
  • 2,593
  • 16
  • 26
  • Not solved my problem yet. I have to show 1:1 image and at the bottom of each image want to add a container of full available width with 100 pixels height. Kindly suggest, how can we do it. Thanks. – Kamlesh May 25 '21 at 17:30
6

There is a flutter package that solves this problem conveniently. Check it out at https://pub.dev/packages/dynamic_height_grid_view. With this you can specify cross-axis count without respecting aspect ratio.

lordvidex
  • 3,581
  • 3
  • 17
  • 25
6

For most use-cases where child height is static or need to be static, please use mainAxisExtent.

Official docs

  /// The extent of each tile in the main axis. If provided it would define the
  /// logical pixels taken by each tile in the main-axis.
  ///
  /// If null, [childAspectRatio] is used instead.
  final double? mainAxisExtent;
Sneh Mehta
  • 121
  • 1
  • 2
5

Child aspect ratio is basically width/height of the grid relative to the device.

So let's say you want the width of each grid to be 30 and the height to be 20, you would set the aspect ratio to be 3/2.

I suggest you watch this video directly from the Flutter team. Although its about the AspectRatio widget, the part about setting the aspectRatio applies to this problem

ByteMe
  • 1,575
  • 1
  • 12
  • 23
  • Not solved my problem yet. I have to show 1:1 image and at the bottom of each image want to add a container of full available width with 100 pixels height. Kindly suggest, how can we do it. Thanks. – Kamlesh May 25 '21 at 17:31
5

You can provide childAspectRatio in GridView.builder like this:

GridView.builder(
  gridDelegate:
     SliverGridDelegateWithFixedCrossAxisCount(
         crossAxisCount:3,childAspectRatio: (150.0 / 220.0)
     )
)
Chandan
  • 11,465
  • 1
  • 6
  • 25
Kaival Patel
  • 461
  • 3
  • 8
2

The childAspectRatio value is a value for you to set the aspect ratio, if you want to make a neat square, you would use 1 and the width will be equal to the height. if you want it to extend to the bottom, decrease the value. sample

  childAspectRatio:0.9,
Hasan koç
  • 51
  • 3
1

(ScreenWidth - (left&right Padding + crossAxisSpace))/ itemHeight

Eg.

  • I have an item with height 241
  • and gridview padding of left and right => 16 + 16
  • and crossAxisSpace 9

I will calculate like this

double extraSpace = (16+16+9); 
aspectRatio : ((MediaQuery.of(context).size.width - extraSpace) / 2) / 241
Balaji
  • 1,773
  • 1
  • 17
  • 30
1

Try this

return LayoutBuilder(builder: (context, constraints) {
    return GridView.count(
      crossAxisCount: crossAxisCount( constraints.maxWidth,100),
    )
  };
);

  int crossAxisCount(double maxWidth, size) {
    int width= maxWidth~/ size;
    return width== 0 ? 1 : width;
  }
Toby Jose
  • 11
  • 1