Taba
Taba

Reputation: 4316

How to detect overflow in widgets

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.

Upvotes: 17

Views: 11901

Answers (4)

WSBT
WSBT

Reputation: 36333

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.

Upvotes: 15

Tasnuva Tavasum oshin
Tasnuva Tavasum oshin

Reputation: 4750

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;

Upvotes: 0

Jim
Jim

Reputation: 7601

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,
)

Upvotes: -2

Spatz
Spatz

Reputation: 20118

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

Upvotes: 6

Related Questions