Using the height of a row's tallest child as the height of the whole row in a grid

533 views Asked by At

I have a bunch of cards with varying heights, and I'd like to display them in a grid, where the row height is the height of the row's tallest widget, like so:

╔════╗╔════╗╔════╗╔════╗
║    ║║    ║║    ║║    ║
║    ║╚════╝║    ║║    ║
╚════╝      ║    ║╚════╝
            ╚════╝
╔════╗╔════╗╔════╗╔════╗
║    ║║    ║║    ║║    ║
║    ║║    ║╚════╝║    ║
╚════╝║    ║      ║    ║
      ║    ║      ╚════╝
      ╚════╝
╔════╗╔════╗╔════╗╔════╗
║    ║║    ║║    ║║    ║
║    ║╚════╝║    ║║    ║
╚════╝      ╚════╝║    ║
                  ╚════╝

How can I do this? I'd prefer not to use third-party packages; flutter_staggered_grid_view, for example, has a critical bug related to window resizing making it unusable for me.

2

There are 2 answers

0
yellowgray On

you can try to use ListView + Row. Remember to set crossAxisAlignment to start and mainAxisSize to min

enter image description here

import 'dart:math' as math;

class CustomGrid extends StatelessWidget {
  final r = math.Random();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Text('123'),
          _aRow(4),
          _aRow(4),
          _aRow(4),
          _aRow(4),
        ],
      ),
    );
  }

  Widget _aRow(int count){
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: List.generate(count, (index) => Expanded(child: _child())),
    );
  }

  Widget _child(){
    final h = r.nextDouble()*150;
    return Container(
      margin: const EdgeInsets.all(10),
      width: double.infinity,
      height: h,
      color: Colors.grey,
    );
  }
}
0
hacker1024 On

I've implemented this using a ListView with Rows, utilising ListView.builder for decent performance.

LayoutBuilder, along with package::quiver's partition function, is used to calculate width to achieve behaviour similar to a GridView with a SliverGridDelegateWithMaxCrossAxisExtent.

LayoutBuilder(
  builder: (context, constraints) {
    // Set minimum and maximum item width bounds.
    const minimumItemWidth = 200.0;
    const maximumItemWidth = 270.0;

    // Divide the maximum available width by the minimum item width to determine
    // the maximum amount of items that can fit in one row.
    //
    // If there's less than the minimum item width available, just use 1 item per
    // row.
    final itemsPerRow = constraints.maxWidth <= minimumItemWidth
        ? 1
        : constraints.maxWidth ~/ minimumItemWidth;

    // Partition the data into rows.
    final rows = quiver
        .partition(data, itemsPerRow)
        .toList(growable: false);

    return ListView.builder(
      itemCount: rows.length,
      itemBuilder: (context, index) {
        return Row(
          children: rows[index]
              .map((data) => SizedBox(
                  // Size row items to their maximum available width, constrained
                  // by the maximum width constant.
                  width: min(constraints.maxWidth / itemsPerRow, maximumItemWidth),
                  child: Tile(data: data),
              )).toList(growable: false),
        );
      },
    );
  },
);