如何检查Flutter中的文本小部件是否溢出?

34

我有一个文本小部件,如果它超出了一定的大小,则可以被截断:

ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 50.0),
  child: Text(
    widget.review,
    overflow: TextOverflow.ellipsis,
  )
);

或最大行数:

RichText(
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
  text: TextSpan(
    style: TextStyle(color: Colors.black),
    text: widget.review,
  ));

我的目标是让文本只有在溢出时才能展开。是否有一种正确的方法来检查文本是否溢出?

我尝试过的方法

我发现在RichText中,有一个RenderParagraph renderObject,其中有一个私有属性TextPainter _textPainter,它有一个bool didExceedMaxLines

简而言之,我只需要访问richText.renderObject._textPainter.didExceedMaxLines,但正如您所看到的,它被设置为私有。

4个回答

42

我找到了一种方法。下面是完整的代码,简而言之:

  1. 使用LayoutBuilder确定我们有多少空间。
  2. 使用TextPainter模拟文本在该空间内的渲染。

这是完整的演示应用程序:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Overflow Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text("DEMO")),
        body: TextOverflowDemo(),
      ),
    );
  }
}

class TextOverflowDemo extends StatefulWidget {
  @override
  _EditorState createState() => _EditorState();
}

class _EditorState extends State<TextOverflowDemo> {
  var controller = TextEditingController();

  @override
  void initState() {
    controller.addListener(() {
      setState(() {
        mytext = controller.text;
      });
    });
    controller.text = "This is a long overflowing text!!!";
    super.initState();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  String mytext = "";

  @override
  Widget build(BuildContext context) {
    int maxLines = 1;
    double fontSize = 30.0;

    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: Column(
        children: <Widget>[
          LayoutBuilder(builder: (context, size) {
            // Build the textspan
            var span = TextSpan(
              text: mytext,
              style: TextStyle(fontSize: fontSize),
            );

            // Use a textpainter to determine if it will exceed max lines
            var tp = TextPainter(
              maxLines: maxLines,
              textAlign: TextAlign.left,
              textDirection: TextDirection.ltr,
              text: span,
            );

            // trigger it to layout
            tp.layout(maxWidth: size.maxWidth);

            // whether the text overflowed or not
            var exceeded = tp.didExceedMaxLines;

            return Column(children: <Widget>[
              Text.rich(
                span,
                overflow: TextOverflow.ellipsis,
                maxLines: maxLines,
              ),
              Text(exceeded ? "Overflowed!" : "Not overflowed yet.")
            ]);
          }),
          TextField(
            controller: controller,
          ),
        ],
      ),
    );
  }
}

这个功能非常好用。我尝试了另一个解决方案,它只是使用TextPainter,但问题是,当我们构建widget时无法获取renderObjectSize。因此,didExceedMaxLines始终返回true。 - Abhishek Doshi
1
重要提示:当设备缩放字体大小时(向上或向下),didExceedMaxLines 计算将不准确;例如,考虑视力障碍。为了准确支持此功能,请在 TextPainter 初始化中添加:textScaleFactor: MediaQuery.of(context).textScaleFactor - Daan
太棒了,但我如何从本地化中获取文本方向? - hasanm08

27

如果文本溢出或未溢出,有一种更短的方法来获取答案。您只需要定义textStyle并从此方法中获取答案。

bool hasTextOverflow(
  String text, 
  TextStyle style, 
  {double minWidth = 0, 
       double maxWidth = double.infinity, 
       int maxLines = 2
  }) {
  final TextPainter textPainter = TextPainter(
    text: TextSpan(text: text, style: style),
    maxLines: maxLines,
    textDirection: TextDirection.ltr,
  )..layout(minWidth: minWidth, maxWidth: maxWidth);
  return textPainter.didExceedMaxLines;
}

1
非常棒的函数,非常感谢分享!我想补充一下,它(可能)更常用于maxLines = 1,并且我会将其设置为默认值作为示例。 - vladli
好的函数,maxWidth 将是设备宽度。因此,您可能会得到 didExceedMaxLines 的确切布尔值为 true。否则,如果 maxWidth == double.infinity,则始终返回 false。 - Dhiren Basra

15

您可以在pub.dev使用Flutter插件auto_size_text。 当文本溢出时,您可以设置某些小部件以出现。

int maxLines = 3;
String caption = 'Your caption is here';
return AutoSizeText(
    caption,
    maxLines: maxLines,
    style: TextStyle(fontSize: 20),
    minFontSize: 15,
    overflowReplacement: Column( // This widget will be replaced. 
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text(
        caption,
        maxLines: maxLines,
        overflow: TextOverflow.ellipsis,
      ),
      Text(
        "Show more",
        style: TextStyle(color: PrimaryColor.kGrey),
      )
    ],
  ),
);

1
最佳和最有效的解决方案,非常感谢。 - StansAvenger

0
我自己制作了一个小部件,可以在整个项目中使用它,它在构造函数中使用Text小部件并读取其属性。
import 'package:flutter/material.dart';

class OverflowDetectorText extends StatelessWidget {
  final Text child;

  OverflowDetectorText({
    Key? key,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var tp = TextPainter(
      maxLines: child.maxLines,
      textAlign: child.textAlign ?? TextAlign.start,
      textDirection: child.textDirection ?? TextDirection.ltr,
      text: child.textSpan ?? TextSpan(
        text: child.data,
        style: child.style,
      ),
    );

    return LayoutBuilder(
      builder: (context, constrains) {
        tp.layout(maxWidth: constrains.maxWidth);
        final overflowed = tp.didExceedMaxLines;
        if (overflowed) {
          //You can wrap your Text `child` with anything
        }
        return child;
      },
    );
  }
}

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