14

I'm wondering if there are ways to detect the overflow happening in widgets and return a boolean to make some changes Depending on the true or false value.

For example, I have a Container with text inside. I want to detect when the text overflows the container, after which I will make the container bigger with some conditions. I know there are some ways like auto_size_text but the point here is to detect the overflow.

raaaay
  • 496
  • 7
  • 14
Taba
  • 3,850
  • 4
  • 36
  • 51

4 Answers4

14

Trying to detect when an overflow happens, looks like an XY Problem to me. Let's stop and think: why do we want to detect an overflow? What can we do when it overflows?

In Flutter, child widgets' sizes are constrained by their parents, there are very few cases where an overflow can happen. For example, consider this code below:

Container(
  height: 100,
  width: 100,
  color: Colors.red,
  child: Container(
    width: 200,
    height: 800,
    color: Colors.green,
  ),
)

To an inexperienced Flutter developer, it may look like the inner Container (200x800) will overflow its parent (100x100), but in fact, the inner's sizes will be auto adjusted to match its parent, so you won't see the black/yellow overflow stripes, you will just see a 100x100 container on your screen, in green.

So, when can we expect children to overflow in Flutter? Usually there are 4 types of cases where things can overflow:

  1. Flex: the very frequently used Column and Row widgets are inherited from Flex widget, which you can also use directly. In these widgets, when performing layout, they will pass an "unbounded" constraint in their main axis. For example, in the case of a Column, its main axis is the vertical axis, so it would allow its children to be however tall they want (but not however wide). If its children's combined height exceeds the layout constraint for Column itself (passed down by Column's parent), it will overflow. Basically, if you put a Column inside a 100x100 Container, then the Column cannot exceed 100 in height, and if its children do, it overflows.

For these cases, detection is doable but often unnecessary. To detect, for example, you can first use LayoutBuilder to get the parental constraint for the Column itself, so you know what's the maximum size you can possibly have (say 100x100, because the Column is placed in such Container), then you need to obtain the size of each children, which is not too easy for a Column because you won't know their sizes before the layout process is completed. (If you must know their size before layout is completed, you can consider looking into CustomMultiChildLayout or write a custom RenderBox). You can maybe use GlobalKey to get the size of any widget, but that would have to be done in the next frame, once the children are first laid out.

Perhaps a better question to ask here is: why do you want to know whether it will overflow? And if it overflows, what can you do? If you can allocate more space to the Column, then why not do so in the first place? You can set MainAxisSize.min on Column to tell it to shrink, and not waste all those extra space unless it really needs to. Or, perhaps you want to allow scrolling? If so, simply wrap the whole Column with a SingleChildScrollView widget, and it will start scrolling automatically whenever the Column grows too tall. (BTW, this is NOT the same as using ListView, you should use ListView if you have lots of items to scroll.)

  1. Stack: widgets in a stack, especially those Positioned ones, can overflow the stack if you purposely do so. For example, by setting left: -10, top: -20 you are knowingly rendering a widget outside of the bound (a little bit over the top left corner). But stack will clip it for you automatically, so you won't even see those overflown content. To see them, you actually need to manually change the clipBehavior of the stack. If you are doing all that work, I don't think a "detection algorithm" is exactly necessary.

  2. Text: text is tricky, depending on the font size (might even be modified by the user at system level), font weight, font family, and many other factors, it's very difficult to calculate how much room a paragraph will take. This is where TextPainter can be used to help you do all the calculations, layout the text first, and then you can read its width and height.

  3. OverflowBox: other possible ways to overflow in Flutter are to intentionally use widgets such as OverflowBox or SizedOverflowBox and etc. These widgets purposely break the parent constraints, but if you are using these, I'm sure you know what you are doing. Depending on the use case, you can consider using CustomMultiChildLayout to get the size of each child, before deciding where to place them.

Final remarks: since you probably want a "general solution", so here it is: in Flutter, most things cannot overflow. If needed, LayoutBuilder widget can be used to determine the current layout constraints of a widget, so you know the max and min size your parent wants you to be. To get the size of your child widget(s), the best way is to use either CustomSingleChildLayout or CustomMultiChildLayout during the layout process. To get the size and position of any widget that's already on the screen, you can use GlobalKey. However, it's often more important to consider WHY you want to know these sizes, rather than HOW to get them.

WSBT
  • 33,033
  • 18
  • 128
  • 133
  • In my case, I am dealing with images of varying sizes. Each of these images is to be contained in a container that has a fixed height and width. The reason for wanting to detect the overflow was to introduce a widget on top of the image that tells the user to click on the image when the image overflows beyond the given height...an action that would navigate them to a different page in which the image can viewed in full. – raaaay Feb 25 '21 at 11:40
  • @ray I see, however, image resolutions are in units of "pixels", such as 200px*200px, but Containers (when set to 200x200) is not really using pixels, rather, it's using logical pixels that are device-depended... so some scaling (or fitting, the `fit` property of `Image`) already happened, right? Further, since you set the container to say 200x200, rather than detecting if an image would overflow, wouldn't it be easier to identify the resolution of the image, its aspect ratio and etc? In other words, a huge 2000x2000 image would not overflow in a 50x50 container if you let it `fit` properly. – WSBT Feb 26 '21 at 23:42
  • I agree but I still believe that there is some kind of implementation in flutter core that the team uses to show the yellow stripes. I think if one taps into that, one could detect an overflow... but only if such an implementation is accessible to us in the first place. – raaaay Mar 01 '21 at 15:48
  • @ray Of course, like mentioned in the answer, using `CustomMultiChildLayout` and alike, you can access each child's size before deciding where to position them. The yellow stripe is also accessible if you write your own render object. The stripe is usually done via `DebugOverflowIndicatorMixin`, you can read the docs on that, or if you ask a new question (and send me the link) I can answer that and provide you with some code example. – WSBT Mar 02 '21 at 13:18
6

Using TextPainter we can measure width of text, and with LayoutBuilder we can determine the bounding box:

import 'package:flutter/material.dart';

class Demo extends StatefulWidget {
  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  String text = "text";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            text += " text";
          });
        },
      ),
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: LayoutBuilder(
          builder: (ctx, constraints) {
            final style = Theme.of(context).textTheme.headline1;
            final span = TextSpan(
              text: text,
              style: style,
            );
            final painter = TextPainter(
              text: span,
              maxLines: 1,
              textScaleFactor: MediaQuery.of(context).textScaleFactor,
              textDirection: TextDirection.ltr,
            );
            painter.layout();
            final overflow = painter.size.width > constraints.maxWidth;
            return Container(
              color: overflow ? Colors.red : Colors.green,
              child: Text.rich(span, style: style),
            );
          },
        ),
      ),
    );
  }
}

Demo on CodePen

Spatz
  • 18,640
  • 7
  • 62
  • 66
  • 1
    This is the best answer so far, thanks. Is it possible to expand it for any other widgets? As I mentioned in the question the `Text` was just an example. I was wondering if we can use the `painter.size.width > constraints.maxWidth` for other widgets too? – Taba Feb 23 '21 at 10:41
  • Look at [this answer](https://stackoverflow.com/a/50115927/1891712), but use it with caution - this method is more sophisticated. – Spatz Feb 23 '21 at 13:24
0
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var dimension = 40.0;

  increaseWidgetSize() {
    setState(() {
      dimension += 20;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(children: <Widget>[
          Text('Dimension: $dimension'),
          Container(
            color: Colors.teal,
            alignment: Alignment.center,
            height: dimension,
            width: dimension,
            // LayoutBuilder inherits its parent widget's dimension. In this case, the Container in teal
            child: LayoutBuilder(builder: (context, constraints) {
              debugPrint('Max height: ${constraints.maxHeight}, max width: ${constraints.maxWidth}');
              return Container(); // create function here to adapt to the parent widget's constraints
            }),
          ),
        ]),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: increaseWidgetSize,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Via this Code you will get the current container height and width now you can compare this to the device height and width and check overflow .

height = MediaQuery.of(context).size.height;
weight = MediaQuery.of(context).size.width;
-2

I don't think altering size of container is a good idea, what if the container overflow again it's parent? the proper way is to resize the text inside the container by using this, auto_size_text:

AutoSizeText(
  'The text to display',
  style: TextStyle(fontSize: 20),
  maxLines: 2,
)
Jim
  • 6,928
  • 1
  • 7
  • 18