如何检测小部件中的溢出

时间:2020-04-15 06:23:47

标签: flutter flutter-layout flutter-widget

我想知道是否有任何方法可以检测窗口小部件中发生的溢出并返回布尔值以进行是否有溢出的更改。

例如,我有一个包含文本的容器。我想通知文本何时容器溢出,然后在某些情况下尝试使容器变大。我知道有一些类似auto_size_text的方法,但是这里的重点是检测溢出

4 个答案:

答案 0 :(得分:3)

尝试检测何时发生溢出,对我来说看起来像是 XY Problem

在 Flutter 中,子小部件的大小受其父小部件的限制,很少有可能发生溢出的情况。例如,请考虑以下代码:

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

对于一个没有经验的 Flutter 开发者来说,看起来内部 Container (200x800) 会溢出其父容器 (100x100),但实际上,内部容器的大小会自动调整以匹配其父容器,因此您不会看到黑色/黄色溢出条纹,您只会在屏幕上看到一个绿色的 100x100 容器。

那么,我们什么时候可以期待孩子们在 Flutter 中溢出呢?通常有四种情况会导致溢出:

  1. Flex:非常常用的 ColumnRow 小部件继承自 Flex 小部件,您也可以直接使用它们。在这些小部件中,当执行布局时,它们将在其主轴上传递一个“无界”约束。例如,在 Column 的情况下,它的主轴是垂直轴,所以它允许它的子节点有他们想要的任何高度(但不是多宽)。如果它的孩子的组合高度超过了 Column 本身的布局约束(由 Column 的父级传递),它将溢出。基本上,如果您将 Column 放入 100x100 Container 中,则 Column 的高度不能超过 100,如果其子项超过,则它会溢出。

对于这些情况,检测是可行的,但通常是不必要的。例如,要检测,您可以首先使用 LayoutBuilder 获取 Column 本身的父约束,这样您就知道可能拥有的最大尺寸是多少(比如 100x100,因为 Column 被放置在这样的Container),那么你需要获取每个子节点的大小,这对于一个Column来说不太容易,因为在布局过程完成之前你不会知道它们的大小。 (如果您必须在布局完成之前知道它们的大小,您可以考虑查看 CustomMultiChildLayout 或编写自定义 RenderBox)。您也许可以使用 GlobalKey 来获取任何小部件的大小,但必须在下一帧中完成,一旦子项首次布局。

也许在这里问一个更好的问题是:你为什么想知道它是否会溢出?如果它溢出,你能做什么?如果可以为 Column 分配更多空间,那为什么不首先这样做呢?您可以在 Column 上设置 MainAxisSize.min 以告诉它缩小,除非确实需要,否则不要浪费所有这些额外空间。或者,也许您想允许滚​​动?如果是这样,只需用一个 Column 小部件包裹整个 SingleChildScrollView,它就会在 Column 长得太高时自动开始滚动。 (顺便说一句,这与使用 ListView 不同,如果要滚动的项目很多,则应使用 ListView。)

  1. 堆栈:堆栈中的小部件,尤其是那些 Positioned 小部件,如果您故意这样做,可能会溢出堆栈。例如,通过设置 left: -10, top: -20,您有意在边界外渲染小部件(左上角一点)。但是堆栈会自动为您剪辑,因此您甚至看不到那些溢出的内容。要查看它们,您实际上需要手动更改堆栈的 clipBehavior。如果您正在做所有这些工作,我认为“检测算法”并不完全必要。

  2. 文本:文本很棘手,取决于字体大小(甚至可能由用户在系统级别修改)、字体粗细、字体系列和许多其他因素,很难计算出一个空间有多少段将采取。在这里可以使用 TextPainter 来帮助您完成所有计算,首先 layout 文本,然后您可以读取其宽度和高度。

  3. OverflowBox:Flutter 中其他可能溢出的方法是有意使用小部件,例如 OverflowBoxSizedOverflowBox 等。这些小部件故意打破父约束,但如果您使用这些,我相信你知道你在做什么。根据用例,您可以考虑使用 CustomMultiChildLayout 来获取每个孩子的大小,然后再决定放置它们的位置。

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

答案 1 :(得分:1)

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

Demo 在 CodePen 上

答案 2 :(得分: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;

答案 3 :(得分:-1)

我认为改变容器的大小不是一个好主意,如果容器再次溢出它是父容器怎么办?正确的方法是使用此auto_size_text

来调整容器内文本的大小
AutoSizeText(
  'The text to display',
  style: TextStyle(fontSize: 20),
  maxLines: 2,
)