How to make children of a Row have the same height by reading child constraints

Viewed 685

I have a SliverList with a delegate that returns Row for each item. The design requires this result:

Desired layout

The two children are Text and Image. The Image fir is BoxFit.cover and I am using Flexible to give them the desired width ratio. I need the image boundary height to match the text height only if the height is greater than or equal to width boundary (to keep it at least a square).

First solution was to use LayoutBuilder to read the constraints of the image width and put the image in a ConstrainedBox with minHeight of LayoutBuilder->constraints->maxWidth.

LayoutBuilder(
  builder: (context, constraints) => ConstrainedBox(
    constraints: BoxConstraints(
        minHeight: constraints.maxWidth,
        maxHeight: constraints.maxWidth),
    child: CachedNetworkImage(
        imageUrl: "https://via.placeholder.com/400x300",
        fit: BoxFit.cover,
      ),
  ),
)

All is well until the text is longer than image width, at this point the image remains a square and does not stretch to the full row height:

Image height does not fill Row height

Second solution was to wrap the Row in an IntrinsicHeight widget. But this means I can't use the LayoutBuilder inside it. So I removed the Layout builder and now I can't access the constraints to set the Image minHeight. The Image stretches very well to fill the full Row height. Until when the text is too short, which makes the image height go below its width:

enter image description here

How can I build this design to be responsive with variable text height?

3 Answers

Just wrap the Row with a IntrinsicHeight

  
IntrinsicHeight(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [...]
  )
)

This can be done cleverly using Stack instead, because the size of the Stack will match its non-positioned children, so it will automatically match your Text widget. Then use a Positioned widget to display the image - set both its top and bottom to 0 to make it stretch vertically, set right: 0 to make it align, and give it a fixed width.

demo using stack

Sample source code:

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Flutter Demo")),
      body: ListView(
        children: [
          for (int i = 8; i < 40; i += 6)
            Stack(
              children: [
                Padding(
                  padding: EdgeInsets.only(right: 100),
                  child: Text("Lorem ipsum " * i),
                ),
                Positioned(
                  top: 0,
                  bottom: 0,
                  right: 0,
                  width: 100,
                  child: Container(color: Colors.primaries[i % 10]),
                ),
              ],
            ),
        ],
      ),
    );
  }
}

Update:

You mentioned in the comments, you would want to enforce a "minimum height". You can easily do that in the Stack, by putting an invisible SizedBox, for example:

Stack(
  children: [
    Padding(
      padding: EdgeInsets.only(right: 100),
      child: Text("Lorem ipsum " * i),
    ),
    SizedBox(height: 100), // this forces minimum height
    Positioned(
      top: 0,
      bottom: 0,
      right: 0,
      width: 100,
      child: Container(color: Colors.primaries[i % 10]),
    ),
  ],
),

Result:

demo

So I took answer 1 and made it responsive and text direction aware (for RTL langs). Here is the final solution:

Stack(
  children: [
    Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Flexible(flex: 3, child: Text(("Hello There\n" * 10))),
        const Flexible(
          flex: 1,
          child: AspectRatio(
            aspectRatio: 1,
          ),
        )
      ],
    ),
    Positioned.fill(
      child: FractionallySizedBox(
        widthFactor: 0.25,
        alignment: AlignmentDirectional.topEnd,
        child: Image.network(
          'https://via.placeholder.com/600/92c952',
          fit: BoxFit.cover,
          // height: double.infinity,
        ),
      ),
    ),
  ],
)
Related