Best way to define a bespoke Card in Flutter

157 views Asked by At

enter image description here

I've been attempting to define a bespoke Card in Flutter using row and column and cannot seem to get a fixed format layout similar to the image above (the red lines denote the areas of the card and are just there to show the areas).

e.g.

return Card(child: Column(
  children: [
    Row(
      children: [
        Column( children: [
          Text('Riverside cafe...'),
          Ratingbar(),
        ],),
        ImageWidget(),
      ],
    ),
    Container(child: Text('Pubs & restaurants'), color : Colors.purple)
  ],

The resulting cards are to be displayed in a listview and using rows and columns above results in the areas being different sized depending on the data.

It seems to me that using row and column may not be the best way to achieve this. Is there a better way?

1

There are 1 answers

0
Benjamin Lee On

As for the best, I suppose that's for you and your client to decide.

For as long as I've been working with Flutter, I haven't come across anything like CSS grid which is great for situations like this. The closest comparison is StaggeredGrid (https://pub.dev/packages/flutter_staggered_grid_view) but that doesn't offer as much control as CSS grid and doesn't seem to quite fit your use case.

Rows, Columns (and other layout widgets) can get the job done: enter image description here

Here's the main.dart that produced the above example. Code quality isn't perfect, but hopefully you can follow it well enough and it helps you get done what you need to get done.

import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  static const String _title = 'Bespoke card example';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Bespoke card example')),
      body: Center(
        child: Wrap(runSpacing: 10.0, children: [
          BespokeCard(title: 'Short name', width: 350),
          BespokeCard(
              title: 'Riverside Cafe with a really long name', width: 350)
        ]),
      ),
    );
  }
}

class BespokeCard extends StatelessWidget {
  final String title;
  final double width;
  BespokeCard({this.title, this.width});
  @override
  Widget build(BuildContext context) {
    Widget _restaurantNameContainer = Container(
      constraints: BoxConstraints(
        minHeight: 0,
        maxHeight: 120,
        maxWidth: (500.0 - 40 - 175 + 1),
        minWidth: (500.0 - 40 - 175 + 1),
      ),
      child: AutoSizeText(
        title,
        style: TextStyle(fontSize: 60),
        maxLines: 2,
        minFontSize: 10,
        stepGranularity: 0.1,
        overflow: TextOverflow.ellipsis,
      ),
    );
    Widget _rightSideSection = Container(
      width: 175,
      height: Size.infinite.height,
      child: Center(
        child: Icon(
          Icons.umbrella,
          size: 70,
        ),
      ),
    );
    Widget _topSection = Flexible(
      flex: 1,
      child: Row(
        children: [
          Flexible(
            fit: FlexFit.tight,
            flex: 3,
            child: Padding(
              padding: EdgeInsets.only(left: 40.0, top: 25.0),
              child: Column(
                children: [
                  Flexible(child: Container(), flex: 1),
                  _restaurantNameContainer,
                  Text('* * * * *', style: TextStyle(fontSize: 70)),
                ],
              ),
            ),
          ),
          _rightSideSection
        ],
      ),
    );

    Widget _bottomSection = Container(
        height: 70,
        width: Size.infinite.width,
        child: Center(
          child: Text('Pubs & Restaurants',
              style: TextStyle(color: Colors.white, fontSize: 40)),
        ),
        color: Colors.purple);

    Widget unfittedCard = Card(
        child: SizedBox(
      width: 500,
      height: 300,
      child: Column(
        mainAxisSize: MainAxisSize.max,
        children: [_topSection, _bottomSection],
      ),
    ));
    return Container(
        width: this.width,
        child: FittedBox(fit: BoxFit.fitWidth, child: unfittedCard));
  }
}


NOTES:

  • Be aware of flexFit (tight or loose) property: https://api.flutter.dev/flutter/widgets/Flexible/fit.html
  • You can either define fixed ratios with all flexibles, or you can mix Flexibles with Containers / SizedBoxes what have you
  • The package auto_size_text is great for situations like this. (Add auto_size_text: ^2.1.0 to your dependencies)
  • Be aware of box constraints. I needed them to make the title autosizing text be able to grow tall without also sitting in a large container.
  • Fitted box is really handy and makes scaling very easy in flutter.