0

3 items have to be displayed in a row: image and a description below, so it's basically a Row of Columns. Description size varies and can either exceed or be smaller than the image's width. These images has to be laid out evenly with the description multilined if needed.

So the naive code looks roughly like this:

@override
  Widget build(BuildContext context) => Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          for (final item in items)
            Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Image.asset(item.depiction),
                Text(
                  item.name,
                  textAlign: TextAlign.center,
                ),
              ],
            )
        ],
      );

The problem is that the Column is sized by width from the text's size, and if it exceeds the image's size, then the images in a Row aren't spaced evenly. The solution I see is to constrain the Column width to the width of Image, using, for example, some kind of Builder widget, but it does not seem right at all.

What I tried is wrapping all the Row's children with Expanded, but then spaceAround does not have any effect, as all the children get sized to the 1/3 of the Row, and this spacing is vital, where the free space should have the value of row.length - 3 * image.size (pseudocode to give a general idea). Wrapping Column with Flexible and setting stretch cross axis alignment to the same Column gives the same effect - Column sizes to the 1/3 of the Row length.

What's the proper way to constraint the Text width to have maxWidth of image's width?

Image:

image

In the third Row it works properly, as the Text width is less than image's.

In the second Row Text's width is greater than image's width, so the Column is sized by the one-line text width and the image's layout is not equal to the previous one.

The first Row is how I want the second Row to be rendered, in this example it's just the separated (\n) description which results in the visual effect I seek for.

AleksanderGD
  • 414
  • 5
  • 10
nyarian
  • 4,085
  • 1
  • 19
  • 51
  • Can you add an image so that i can understand your problem properly? – littleironical Jul 25 '20 at 09:16
  • @HardikKumar added – nyarian Jul 25 '20 at 09:34
  • Why don’t you use gridview. For the layout shown on the picture gridview is the best approach – delmin Jul 25 '20 at 09:43
  • @delmin there is a logic above that can be explained like `here is a row of items of the same category, please pick one of them`, so `Row` (or horizontal `ListView`) is handy here for such an organization. It can be changed though. But I don't see how it would help with spacing, as adaptive `spaceAround` strategy as desired, and by "adaptive" I mean that it calculates the free space (`layout.width - image.width * 3`) and spaces the children accordingly (so that we get `freeSpace / 4; image1; freeSpace / 4; image2; freeSpace / 4; image3; freeSpace / 4); is it possible with `GridView`? – nyarian Jul 25 '20 at 09:50
  • As far as i understood correctly gridview is what you want.. I do believe all of it is possible with gridview if you want all your columns to have exactly same size. That what gridview does... I might have misunderstood your question but I still believe gridview is the way to go – delmin Jul 25 '20 at 09:58
  • @AndreyIlyunin Ok if you want to have only text width same as image width then you can try to use `IntrinsicWidth` https://api.flutter.dev/flutter/widgets/IntrinsicWidth-class.html – delmin Jul 25 '20 at 10:28
  • @AndreyIlyunin I can not find an example of using it but I found example of using `IntrinsicHeight` here https://stackoverflow.com/questions/62753966/how-to-make-a-widgets-size-same-as-another-widget-in-flutter/62754234#62754234 – delmin Jul 25 '20 at 10:31
  • @delmin yeah I was just trying to wrap my head around it. As far as I understand, `IntrinsicWidth` waits for the child sizing, and then sets the... child's size as the constraint for this child? I'm playing around with `IntrinsicWidth` in the code but can't see any effect of it, could you please provide a more detailed description of it, as I am surely don't understand it correctly – nyarian Jul 25 '20 at 10:38
  • @AndreyIlyunin I'm not in my office currently so I can not answer it at the moment.. You will have to play with it on your own... To be honest I've never used that widget so it would be new for me too but I believe that is the way to go. It should be exactly the same as with height so by following the height example you should be able to modify it to your needs – delmin Jul 25 '20 at 10:43
  • @delmin in that example it's just have to be as the tallest widget, not as the shortest, so it's not as transparent. But thank you anyway, I'll be digging in that direction – nyarian Jul 25 '20 at 10:45
  • https://stackoverflow.com/questions/58468915/flutter-have-child-match-another-childs-width-inside-of-column/70221926#70221926 – Anton Duzenko May 19 '22 at 07:04

4 Answers4

0

You could wrap those Columns in a fixed width SizedBoxs like this..

SizedBox(
        width: 200,
        child: Column(
          children: [
            Image.asset(item.depiction),
            Text(
              'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et',
            ),
          ],
        ),
      )
Jigar Patel
  • 4,953
  • 1
  • 12
  • 20
  • I'm seeking for an adaptive solution without hardcoded values (so it works with image's width), could you provide such one? – nyarian Jul 25 '20 at 09:25
  • If not hardcoded width, we can also use some percentage of the screen's width like `MediaQuery.of(context).size.width * 0.2` – Jigar Patel Jul 25 '20 at 09:28
  • Reuse is complex in this case, as if there is two use cases for such a widget and the first one has more space than the second one, than the hardcoded coefficient value has to be passed from the parent and should be picked manually as well, isn't it? – nyarian Jul 25 '20 at 09:38
  • oh okay you mean adaptive with respect to image's width. I thought adaptive with respective to screen width. The percentage-of-screen-width way is only adaptive to screen's width, yes. Not to image's width. – Jigar Patel Jul 25 '20 at 09:42
  • Yeah, I attached the image link just to clarify this as it is really isn't that clear from the description it seems – nyarian Jul 25 '20 at 09:43
0

I think this is what you want:

Image 1

Image 2

Image 3

Code:

class ClassName extends StatelessWidget {  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[200],

      appBar: AppBar(
        backgroundColor: Colors.white,
        centerTitle: true,
        title: Text('This is the solution', style: TextStyle(color: Colors.black, fontSize: 18.0),),
      ),

      body: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              col(context),
              col(context),
              col(context),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              col(context),
              col(context),
              col(context),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              col(context),
              col(context),
              col(context),
            ],
          ),
        ],
      ),
    );
  }
}

col(context){
  return SizedBox(
    width: MediaQuery.of(context).size.width*0.3,
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        SizedBox(height: 100.0, child: Image.asset('assets/02.jpg')),
        Text('smith smith smith smith smith smith smith', textAlign: TextAlign.center),
      ],
    ),
  );
}
littleironical
  • 1,593
  • 1
  • 11
  • 25
  • That's how it roughly works right now in the implementation that's already in place :) The only difference is the children are just expanded instead of taking a 0.3 of the width to allow better adapting to the parent constraints. But the result is the same nevertheless - the column width isn't equal to the image width, and this is the target for the proper alignment – nyarian Jul 25 '20 at 10:31
  • So you don't want any spaces between those 3 images? – littleironical Jul 25 '20 at 10:48
  • I want the space that depends on the size of the image, not on the size of the screen. For example, if the screen width is 500 px and image width is 100, then I want to have `50px-image-50px-image-50px-image-50px` layout. If image is 150, then I want `12.5px-image-12.5px-image-12.5px-image-12.5px`. If image is 50, then I want `87.5px-image-87.5px-image-87.5px-image-87.5px`. So the free space for spaces is `screen.width - image.width * images.count`. It should be calculated dynamically by the image's width, and it can be arbitrary. – nyarian Jul 25 '20 at 10:51
  • I've added 3 screenshots. In those images, you can see that the spaces depends on the size of the image. 3 screenshots has 3 different images of different dimensions. – littleironical Jul 25 '20 at 11:22
  • They are just scaled down to fit in `SizedBox` which has a fixed width of `size.width * 0.3`, did I understood correctly? – nyarian Jul 25 '20 at 11:24
  • So that the layout does not depend on the image size (and I want it to depend), but rather image size depends on the screen size? – nyarian Jul 25 '20 at 11:27
0

So I ended up with a quite hacky solution for now which I'm not comfortable with, but posting it nevertheless for the people that seek for the same answer:

typedef ImageBasedBuilder = Widget Function(
    BuildContext context, ImageInfo image);

class ImageInfoBasedWidget<T> extends StatefulWidget {
  final ImageProvider<T> provider;
  final ImageBasedBuilder builder;

  const ImageInfoBasedWidget({
    @required this.provider,
    @required this.builder,
    Key key,
  }) : super(key: key);

  @override
  _ImageInfoBasedWidgetState<T> createState() =>
      _ImageInfoBasedWidgetState<T>();
}

class _ImageInfoBasedWidgetState<T>
    extends State<ImageInfoBasedWidget<T>> {
  ImageStream _imageStream;
  ImageStreamListener _streamListener;
  ImageInfo _imageInfo;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _resolveImage();
  }

  @override
  void didUpdateWidget(ImageInfoBasedWidget<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.provider != widget.provider) {
      _resolveImage();
    }
  }

  @override
  void dispose() {
    _imageStream.removeListener(_streamListener);
    super.dispose();
  }

  void _resolveImage() {
    final ImageStream oldImageStream = _imageStream;
    _imageStream =
        widget.provider.resolve(createLocalImageConfiguration(context));
    if (_imageStream.key != oldImageStream?.key) {
      final ImageStreamListener listener = ImageStreamListener(_provideImage);
      oldImageStream?.removeListener(listener);
      _imageStream.addListener(listener);
    }
  }

  void _provideImage(ImageInfo imageInfo, bool syncCall) =>
      setState(() => _imageInfo = imageInfo);

  @override
  Widget build(BuildContext context) {
    if (_imageInfo == null) {
      return Container();
    } else {
      return widget.builder(context, _imageInfo);
    }
  }
}

It's quite straightforward - we are just getting the image configuration that will be used to afterwards by the builder object function. We can place text constraints with ConstrainedBox which will have maxWidth of imageInfo.image.width.

nyarian
  • 4,085
  • 1
  • 19
  • 51
  • Might be hacky, but only something like this looked like a way forward. Because without somehow knowing the image's width, there seemed to be no other way. `IntrinsicWidth` seems to adjust the width according to the most wide child, and not the least wide child. I searched for something that can enforce the width according to the least wide child, but I could not find anything. – Jigar Patel Jul 26 '20 at 05:10
  • Yeah, got the same results when was researching the topic yesterday. Feeling that it can be done simpler and framework supports it out-of-the-shelf does not go away though :\ – nyarian Jul 26 '20 at 08:38
0

This is the third questions I've seen on stack overflow that ask for this.

I just wrote a package for it: https://pub.dev/packages/flex_with_main_child

You can replace your Column with ColumnWithMainChild from flex_with_main_child.

Column(
  mainAxisSize: MainAxisSize.min,
  mainChild: Image.asset(item.depiction),
  childrenBelow: <Widget>[
    Text(
      item.name,
      textAlign: TextAlign.center,
    ),
  ],
)
Shuang Li
  • 75
  • 1
  • 9