Kent Boogaart
Kent Boogaart

Reputation: 178700

How to implement a widget that is adaptive to available space, shrinking part of itself when space is unavailable

I'm trying to accomplish an ostensibly simple layout requirement in Flutter, but am struggling with fulfilling the details of the requirements. I want a widget that displays some text under an image. The image should take whatever available space it has, but shrink if it does not have enough space.

My first attempt was simply:

class VerticalLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final column = Column(
      children: <Widget>[
        Expanded(
          child: LabeledImage(),
        ),
        Expanded(
          child: LabeledImage(),
        ),
        Expanded(
          child: LabeledImage(),
        ),
        Expanded(
          child: LabeledImage(),
        ),
      ],
      mainAxisAlignment: MainAxisAlignment.spaceAround,
    );

    final scaffold = Scaffold(
      body: Center(child: column),
      appBar: AppBar(),
    );

    return scaffold;
  }
}

class LabeledImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final image = FlutterLogo(size: 250);
    final text = Text('foo');
    final column = Column(
      children: <Widget>[
        Expanded(
          child: image,
        ),
        text,
      ],
    );

    return column;
  }
}

This works fine for situations where the image is large enough (as with size: 250 above):

enter image description here

But when the image is smaller, the Expanded causes the image to take up more space than required:

enter image description here

OK, so Expanded doesn't seem like the right choice because I don't really want the image to expand if it cannot fill the space. The next likeliest candidate seemed like FittedBox:

class LabeledImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final image = FlutterLogo(size: 50);
    final text = Text('foo');
    final column = Column(
      children: <Widget>[
        FittedBox(
          child: image,
          fit: BoxFit.scaleDown,
        ),
        text,
      ],
    );

    return column;
  }
}

Now the VerticalLayout works great in situations where the image is small (as above):

enter image description here

But it fails to shrink the image when it's big:

enter image description here

I want my LabeledImage widget to also work when inside a Row, but I can't even find a way to have it "just work" inside a Column.

Can anyone point me in the right direction?

Upvotes: 1

Views: 782

Answers (2)

Kent Boogaart
Kent Boogaart

Reputation: 178700

I arrived at something that seems to work pretty well by combining the answer from @Doc with an answer I received via Slack:

class LabeledImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final image = FlutterLogo(size: 50);
    final text = Text('foo');
    final column = Column(
      children: <Widget>[
        Flexible(
          child: FittedBox(
            child: image,
            fit: BoxFit.scaleDown,
          ),
        ),
        text,
      ],
    );
    return column;
  }
}

I don't fully understand why this works, and it's a bit finicky to use when composing widgets together in different ways. For example, once I had a working VerticalLayout and HorizontalLayout, I then tried stacking multiple HorizontalLayout widgets on top of each other in another Column. I had to wrap each widget in Flexible for it to work, and I don't really understand why.

Upvotes: 1

Doc
Doc

Reputation: 11651

Like this?

working Vertical and Horizontal Layout

class SO extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return VerticalLayout();
//    return HorizontalLayout();
  }
}

class HorizontalLayout extends StatelessWidget {
  final int itemCount = 4;

  @override
  Widget build(BuildContext context) {
    final row = Row(
      children: List.generate(itemCount, (_) => HorizontalLabeledImage()).toList(),
    );

    final scaffold = Scaffold(
      body: row,
      appBar: AppBar(),
    );

    return scaffold;
  }
}
class HorizontalLabeledImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Flexible(//doesn't matter Flexible or Expanded
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          FittedBox(child: FlutterLogo()),
          Text('foobar', textAlign: TextAlign.center),
        ],
      ),
    );
  }
}
class VerticalLayout extends StatelessWidget {
  final int itemCount = 4;

  @override
  Widget build(BuildContext context) {
    final column = Column(
      children: List.generate(itemCount, (_) => VerticalLabeledImage()).toList(),
    );

    final scaffold = Scaffold(
      body: column,
      appBar: AppBar(),
    );

    return scaffold;
  }
}

class VerticalLabeledImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Flexible(//doesn't matter Flexible or Expanded
      child: Column(
        children: <Widget>[
          Expanded(child: FittedBox(child: FlutterLogo())),
          Text('foobar'),
        ],
      ),
    );
  }
}
enter code here

Upvotes: 1

Related Questions