0

So I have a Row inside a fixed size Container, the Row contains 2 Text widgets, both of them wrapped in FittedBox so the text wouldn't overflow.

[Edit] The Container's fixed size is just a placeholder for the minimal example. In reality the Container is in many parents and his size is determined by the size screen. So I can't really access the Containers width and height properties easily.

Container(
  height: 100,
  width: 240,
  color: Colors.blue,
  child: Center(
    child: Container(
      color: Colors.green,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Expanded(
            child: Container(
              color: Colors.red,
              child: const FractionallySizedBox(
                heightFactor: 0.5,
                child: FittedBox(
                  fit: BoxFit.contain,
                  child: Text("10"),
                )
              )
            ),
          ),
          Expanded(
            child: Container(
              color: Colors.yellow,
              child: const FittedBox(
                fit: BoxFit.contain,
                child: Text("10"),
              )
            )
          ),
        ],
      )
    )
  )
)

I want the red widget to be half (or any other fraction) the height of the yellow widget, so I wrapped the red widget in the FractionallySizedBox, which works if the yellow widget has same height as the blue Container.

This is fine

But if the text in the yellow widget is longer, its height gets smaller and the Row's height gets smaller, but the red widget is still taking the fraction from the blue Container not the smaller green Row.

Red widget is taking the fraction of the blue Container, not the green Row

I want the red widget to be at most half the height of the yellow widget even if it gets smaller because of the text length. If the red widget has to be smaller because of the length of its text, then it should be smaller, but it should never be taller than half of the yellow box.

How to do it?

MrKew
  • 333
  • 1
  • 14
  • You might just be able to wrap the Text of the yellow side in a SizedBox.expand – Gregory Conrad Oct 15 '22 at 22:03
  • No, that just makes the yellow side to fill the height of the container. I want the red side to adjust according the yellow side, even if the yellow side is smaller because of the text. – MrKew Oct 15 '22 at 22:38

2 Answers2

2

I am using this class MeasureSize to detect the size of Widget after rendering.

typedef OnWidgetSizeChange = void Function(Size size);

class MeasureSizeRenderObject extends RenderProxyBox {
  Size? oldSize;
  final OnWidgetSizeChange onChange;

  MeasureSizeRenderObject(this.onChange);

  @override
  void performLayout() {
    super.performLayout();

    Size newSize = child!.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      onChange(newSize);
    });
  }
}

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key? key,
    required this.onChange,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }
}

So this is exemple with your code may help you:

class TestWidget extends StatefulWidget {
  const TestWidget({Key? key}) : super(key: key);

  @override
  State<TestWidget> createState() => _TestWidgetState();
}

class _TestWidgetState extends State<TestWidget> {

  final ValueNotifier<Size?> _size = ValueNotifier<Size?>(null);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
          height: 100,
          width: 240,
          color: Colors.blue,
          child: Center(
              child: Container(
                  color: Colors.green,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Expanded(
                        child: ValueListenableBuilder<Size?>(
                          valueListenable: _size,
                          builder: (context, size, child) {
                            if (size != null) {
                              return Container(
                                  color: Colors.red,
                                  height: size.height / 2,
                                  child: const FittedBox(
                                    fit: BoxFit.contain,
                                    child: Text("10"),
                                  )
                              );
                            }
                            return const SizedBox.shrink();
                          }
                        ),
                      ),
                      Expanded(
                          child: MeasureSize(
                            onChange: (Size size) {
                              _size.value = size;
                            },
                            child: Container(
                                color: Colors.yellow,
                                child: const FittedBox(
                                  fit: BoxFit.contain,
                                  child: Text("100"),
                                )
                            ),
                          )
                      ),
                    ],
                  )
              )
          )
      ),
    );
  }
}

this is what i got:

enter image description here

GNassro
  • 891
  • 1
  • 10
  • 23
  • 1
    Note to the OP: measured size is also available as a package on pub. Also, it’s worth noting that this approach is often considered a hack and should be avoided when possible because of the extra layout step needed (except it might be needed here). – Gregory Conrad Oct 15 '22 at 22:50
  • Oh ok, but there has to be some "legal" way to do this, right? I mean it seems to be pretty basic stuff, in the constraint based ui it would take 1 constraint, possibly a some sort of content hugging priority. So it seems odd to me that it would be basically impossible in the declarative ui. – MrKew Oct 16 '22 at 00:51
1

The below code recursively calculates the font size that will be used by the yellow text. Then once the yellow text font size is determined the height of the yellow text can be determined. Then set the height of the red text to be 1/2 the height of the yellow text.

ref the below link for how text size is determined How can I get the size of the Text widget in Flutter

import 'package:flutter/material.dart';

/// return the text size
/// modified version of code from ref
/// ref: https://stackoverflow.com/questions/52659759/how-can-i-get-the-size-of-the-text-widget-in-flutter
Size _textSize({
  required String text,
  required double maxWidth,
  required double maxHeight,
  double? fontSize,
}) {
  // for initial run, match the max height
  fontSize ??= maxHeight;

  // calculate size of text based on font size
  final TextPainter textPainter = TextPainter(
    // add params to text style if you use them on yellow text
    text: TextSpan(
      text: text,
      style: TextStyle(fontSize: fontSize),
    ),
    maxLines: 1,
    textDirection: TextDirection.ltr,
  )..layout(
      minWidth: 0,
      maxWidth: double.infinity,
    );

  // if text would exceed the width reduce font size by 1 and re-run
  if (textPainter.size.width > maxWidth) {
    return _textSize(
      text: text,
      maxWidth: maxWidth,
      maxHeight: maxHeight,
      fontSize: fontSize - 1,
    );
  }

  // once text doesn't exceed the width return
  return textPainter.size;
}

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: Builder(
            builder: (context) {
              // set text for yellow, and calculate the size it will be
              String yellowText = "1000";
              Size yellowSize = _textSize(
                text: yellowText,
                // 1/2 of width, because that is the max with of the yellow text
                maxWidth: 240 / 2,
                maxHeight: 100,
              );

              // create specified container
              return Container(
                height: 100,
                width: 240,
                color: Colors.blue,
                child: Center(
                  child: Container(
                    color: Colors.green,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Expanded(
                          child: Container(
                            color: Colors.red,

                            // make text size 1/2 yellow text size
                            height: yellowSize.height / 2,
                            child: const FittedBox(
                              fit: BoxFit.contain,
                              child: Text("10"),
                            ),
                          ),
                        ),
                        Expanded(
                          child: Container(
                            color: Colors.yellow,
                            // set yellow text height to match above calculation
                            height: yellowSize.height,
                            child: FittedBox(
                              fit: BoxFit.contain,
                              // any styles added to the text need to be added in the _textSize function
                              child: Text(
                                yellowText,
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    ),
  );
}
CatTrain
  • 214
  • 2
  • 5
  • Oh I am sorry, I tried to make the minimal example, but I failed miserably. The blue container hasn't actually got the fixed size, but its size is calculated from its parent and eventually from the screen size. So I cant really calculate the text size like that, because I don't have the exact size of the container. I just know that it can't expand so the text has to fit in. I'll update the question. – MrKew Oct 16 '22 at 07:56
  • 1
    If that is the case, use a [LayoutBuilder](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html), or [MediaQuery](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html) to set the static values used in the example. @MrKew – CatTrain Oct 16 '22 at 15:24