如何检测小部件中的溢出问题

14

我想知道是否有办法检测部件中发生的溢出并返回一个布尔值,以便根据truefalse值进行一些更改。

例如,我有一个包含文本的Container。我想要检测当文本溢出容器时,我将使用一些条件使容器变大。我知道有一些方法,如auto_size_text,但重点在于检测溢出


这个回答解决了你的问题吗?如何检查Flutter Text小部件是否溢出 - Kirill Matrosov
@KirillMatrosov。实际上不是这样的。我想知道什么时候会发生。那只是一个例子来说明我的观点。无论是文本溢出还是任何其他小部件。 - Taba
不行,这也不起作用。 - Taba
我认为你无法检测到它,但你可以遵循一些提示来避免这种情况。 - ahmad bajwa
展示一些代码片段,指出错误所在。 - Janvi Patel
4个回答

14

试图检测溢出发生的时候,对我来说看起来像是一个XY问题。让我们停下来思考一下:为什么我们想要检测溢出?当它溢出时我们能做些什么呢?

在Flutter中,子小部件的大小受其父级限制,很少出现溢出的情况。例如,考虑下面的代码:

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

对于一个经验不足的Flutter开发者来说,内部的Container(200x800)可能会溢出其父容器(100x100),但实际上,内部的大小将自动调整以匹配其父容器,因此您不会看到黑色/黄色的溢出条纹,您只会看到屏幕上的一个100x100的绿色容器。
那么,什么情况下可以预期在Flutter中溢出子节点?通常有4种情况会导致溢出:
1. Flex:非常频繁使用的Column和Row widgets都继承自Flex widget,您也可以直接使用它。在这些widget中,当执行布局时,它们将在主轴上传递“未限定”的约束。例如,在Column的情况下,它的主轴是垂直轴,因此它将允许其子元素具有任意高度(但不是任意宽度)。如果其子元素的组合高度超过了Column本身的布局约束(由Column的父代传递),则会发生溢出。基本上,如果将Column放在100x100的容器中,则Column的高度不能超过100,如果其子元素超过了这个高度,就会发生溢出。
对于这些情况,检测是可行的,但通常是不必要的。例如,为了检测,您可以首先使用LayoutBuilder获取Column本身的父级约束条件,以便知道可能具有的最大尺寸(例如100x100,因为Column放置在这样的Container中),然后您需要获得每个子元素的大小,对于Column来说不太容易,因为在完成布局过程之前,您无法知道它们的大小。(如果必须在布局完成之前知道它们的大小,则可以考虑查看CustomMultiChildLayout或编写自定义RenderBox)。您可以使用GlobalKey获取任何widget的大小,但这必须在下一帧中完成,一旦子元素第一次排列好。
也许在这里提出一个更好的问题是:为什么要知道它是否会溢出?如果它溢出了,您能做些什么?如果您可以为Column分配更多的空间,那么为什么不在第一时间这样做呢?您可以在Column上设置MainAxisSize.min,告诉它缩小,并且不浪费所有这些额外的空间,除非它确实需要。或者,也许您想允许滚动?如果是这样,请将整个Column包装在SingleChildScrollView widget中,当Column变得太高时,它将自动开始滚动。(顺便说一下,这与使用ListView不同,如果您有很多项目要滚动,请使用ListView)。
  1. Stack:堆栈中的小部件,特别是那些定位小部件,如果您故意这样做,可以溢出堆栈。例如,通过设置 left: -10, top: -20,您知ingly地呈现小部件在边界之外(略微超过左上角)。但是堆栈会自动为您剪辑它,所以您甚至看不到那些溢出的内容。要查看它们,实际上需要手动更改堆栈的 clipBehavior。如果您正在进行所有这些工作,我认为“检测算法”并不是必需的。

  2. Text:文本很棘手,具体取决于字体大小(甚至可能被用户在系统级别修改),字重,字体系列和许多其他因素,很难计算出一个段落将占用多少空间。这就是 TextPainter 可以用来帮助您进行所有计算的地方,layout 首先对文本进行布局,然后您可以读取其宽度和高度。

  3. OverflowBox:Flutter 中其他可能的溢出方式是有意使用诸如 OverflowBoxSizedOverflowBox 等小部件。这些小部件有意打破了父约束,但是如果您使用这些小部件,我相信您知道自己在做什么。根据用例,您可以考虑使用 CustomMultiChildLayout 在决定放置它们之前获取每个子级的大小。

最后的备注:由于您可能想要一个“通用解决方案”,因此这里是:在 Flutter 中,大多数事情都不能溢出。如果需要,可以使用 LayoutBuilder 小部件来确定小部件的当前布局约束,以便您知道父项希望您的最大和最小大小。要获取子小部件的大小,最好的方法是在布局过程中使用 CustomSingleChildLayoutCustomMultiChildLayout。要获取屏幕上已经存在的任何小部件的大小和位置,可以使用 GlobalKey。但是,更重要的是考虑为什么要知道这些大小,而不是如何获得它们。


在我的情况下,我正在处理大小不一的图像。这些图像中的每一个都将被包含在一个具有固定高度和宽度的容器中。想要检测溢出的原因是为了在图像顶部引入一个小部件,告诉用户当图像超出给定高度时点击图像,从而导航到另一页,在该页面上可以完整地查看图像。 - raaaay
@ray 我明白了,然而图像分辨率以“像素”为单位,例如200px * 200px,但是容器(当设置为200x200时)实际上并没有使用像素,而是使用设备相关的逻辑像素...因此一些缩放(或适配,即Imagefit属性)已经发生了,对吗?此外,由于您将容器设置为200x200,而不是检测图像是否会溢出,那么识别图像的分辨率、宽高比等是否更容易呢?换句话说,如果您让它正确地fit,即使是一个巨大的2000x2000图像也不会在50x50的容器中溢出。 - WSBT
我同意,但我仍然相信Flutter核心中有某种实现,团队使用它来显示黄色条纹。我认为,如果一个人深入了解这一点,就可以检测到溢出...但前提是我们首先能够访问这样的实现。 - raaaay
@ray 当然,就像答案中提到的那样,使用CustomMultiChildLayout等类似方法,您可以在决定如何定位它们之前访问每个子项的大小。如果您编写自己的渲染对象,则还可以访问黄色条纹。该条纹通常是通过DebugOverflowIndicatorMixin完成的,您可以阅读相关文档,或者如果您提出一个新问题(并将链接发送给我),我可以回答并提供一些代码示例。 - WSBT

6

使用TextPainter可以测量文本的宽度,而使用LayoutBuilder则可以确定其边界框:

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

代码笔上的演示


1
这是目前为止最好的答案,谢谢。是否可以将其扩展到其他小部件?正如我在问题中提到的那样,“Text”只是一个例子。我想知道我们是否也可以将“painter.size.width > constraints.maxWidth”用于其他小部件? - Taba
看看这个答案,但要小心使用 - 这种方法更加复杂。 - Spatz

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

通过这段代码,您可以获取当前容器的高度和宽度,然后将其与设备的高度和宽度进行比较,并检查溢出情况。
height = MediaQuery.of(context).size.height;
weight = MediaQuery.of(context).size.width;

-2

我认为改变容器的大小不是一个好主意,如果容器再次溢出其父级怎么办?正确的方法是使用auto_size_text来调整容器内部的文本大小:

AutoSizeText(
  'The text to display',
  style: TextStyle(fontSize: 20),
  maxLines: 2,
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接