使用 LineMetrics 获取文本高度的 Flutter 问题

Flutter problem getting text-height with LineMetrics

我有一个应用程序显示内容卡片,背景图像前面有文本。为了更好的可读性,在文本和背景图像之间添加了一个模糊层。由于 flutter 在构建之前获取小部件(或文本)的高度并不容易,因此我能找到解决此问题(不使用回调或重绘)的唯一方法是 LineMetrics。使用 LineMetrics,我可以计算 space 文本将以正确的大小绘制模糊层。

现在问题来了:LineMetrics 的计算 width 属性有时不适合呈现的文本宽度。这在导致错过换行符的情况下是有问题的,因为模糊的背景不再覆盖整个文本区域。

我在做什么:按照这个 medium article 我首先创建一个带有文本和样式的 TextSpan,然后将它添加到 TextPainter,然后使用 minWidth: 0 调用 layout() 函数,和 maxWidth: maxTextWidth。最后我用 textPainter.computeLineMetrics():

创建了 LineMetrics
  // Widget
  @override
  Widget build(BuildContext context) {

    // Get text styles
    final TextStyle titleStyle = TextStyle(
      fontFamily: 'SF Pro Display',
      fontStyle: FontStyle.normal,
      fontSize: 14,
      height: (17 / 14),
      fontWeight: FontWeight.bold,
      color: Colors.white);
    final TextStyle textStyle = TextStyle(
      fontFamily: 'SF Pro Display',
      fontStyle: FontStyle.normal,
      fontSize: 14,
      height: (17 / 14),
      fontWeight: FontWeight.normal,
      color: Colors.white);

    // Build text spans
    final titleSpan = TextSpan(text: title, style: titleStyle);
    final textSpan = TextSpan(text: text, style: textStyle);

    // Calculate text metrics
    final double _maxWidth = style.width - style.margin.horizontal;
    List<LineMetrics> _titleLines = _getLineMetrics(_maxWidth, titleSpan);
    List<LineMetrics> _textLines = _getLineMetrics(_maxWidth, textSpan);

    // Calculate text heights
    final double _titleHeight = _getLinesHeight(_titleLines) + style.margin.top;
    final double _textHeight = _getLinesHeight(_textLines) + style.margin.bottom;

    // Generate coloured debug container
    Column _titleContainer = _getTextSpanContainer(_titleLines);
    Column _textContainer = _getTextSpanContainer(_textLines);

    // Widget content
    List<Widget> _textOverlay = [
      Expanded(child: Text('')),
      Stack(children: <Widget>[
        Align(alignment: Alignment.topLeft, child: _titleContainer),
        Align(alignment: Alignment.topLeft, child: RichText(text: titleSpan))
      ]),
      Stack(children: <Widget>[
        Align(alignment: Alignment.topLeft, child: _textContainer),
        Align(alignment: Alignment.topLeft, child: RichText(text: textSpan))
      ])
    ];


  List<LineMetrics> _getLineMetrics(double width, TextSpan textSpan) {
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr
    );

    textPainter.layout(
      minWidth: 0,
      maxWidth: width,
    );

    // TODO: width of lineMetrics sometimes not matching real text width
    return textPainter.computeLineMetrics();
  }

  double _getLinesHeight(List<LineMetrics> lines) {
    double _textHeight = 0;

    for (LineMetrics line in lines) {
      _textHeight += line.height;
    }

    return _textHeight;
  }

  Column _getTextSpanContainer(List<LineMetrics> lines) {
    List<Widget> container = [];

    for (LineMetrics line in lines) {
      container.add(Container(
        width: line.width,
        height: line.height,
        color: Colors.red,
      ));
    }

    return Column(
      children: container,
      crossAxisAlignment: CrossAxisAlignment.start,
    );
  }

我用 LineMetrics 的计算宽度在每个文本行后面添加了红色轮廓以可视化问题。大多数情况下它都有效,但有时计算出的宽度不匹配:

我尝试了 TextStyles 中几乎所有可能的属性(WordSpacing、LetterSpacing、textWidthBasis...),使用和不使用自定义字体构建它,使用 RichText 和普通文本元素绘制,但所描述的问题没有任何改变。

任何人都可以帮助解决这个奇怪的行为,或者提供一种替代方法来在构建小部件之前获取文本高度吗?

一些相关问题:

编辑:事实证明,在某些情况下,此解决方案也仅适用于静态修复 (maxWidth - 10)

虽然我找不到reason/solution flutter line-metrics width-calculations 错误的问题,但我确实找到了解决之前获取flutter text-widget 高度问题的方法它的构建:

extension StringExtension on String {
  double getHeight(BuildContext context, RichText richText, double maxWidth) {
    double maxWidthFix = maxWidth - 10; //NOTE: Fix!!!
    BoxConstraints constraints = BoxConstraints(
      maxWidth: maxWidthFix, // maxwidth calculated
    );

    RenderParagraph renderObject = richText.createRenderObject(context);
    renderObject.layout(constraints);
    return renderObject.getMaxIntrinsicHeight(maxWidthFix);
  }
}

使用此字符串扩展,可以计算出正确的文本高度,从而以相应的大小绘制背景模糊:

// Get text heights
final double _titleHeight = title.getHeight(context, title, maxTextWidth);
final double _textHeight = text.getHeight(context, text, maxTextWidth);
final double _blurHeight = _titleHeight + _textHeight + margin;

这些屏幕截图证明使用字符串扩展时最初的问题已解决:

我根据相关主题的两个答案创建了这个解决方案:
How can I get the size of the Text Widget in flutter

对于 flutter line-metrics 我只找到了解决问题 99% 的解决方法?案例数:

// NOTE: width of lineMetrics sometimes not matching real text width
final double lineFix = 10;

textPainter.layout(
  minWidth: 0,
  maxWidth: width - lineFix,
);

textPainter.computeLineMetrics();

我无法解释您观察到的 textPainter.computeLineMetrics 的奇怪行为,但我认为您的问题有不同的解决方案,不涉及测量文本小部件的高度。

您的 content-cards 可以使用 Stack with a Positioned and a BackdropFilter,您可以对其进行剪裁以适合覆盖的文本。 这样做时,您无需费心测量文本小部件。

示例:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: <Widget>[
        FittedBox(
          fit: BoxFit.fill,
          child: Image.network('https://picsum.photos/300'),
        ),
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: ClipRect(
            child: BackdropFilter(
              filter: ImageFilter.blur(
                sigmaX: 3.0,
                sigmaY: 3.0,
              ),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: const Text(
                    'Aut velit illum eos aut aut eaque totam. Autem aut quis omnis et minus. Itaque at molestias enim sunt autem voluptas voluptatem delectus. Minima deleniti fugiat sit sunt fugiat. Cumque iusto quo eum. Ipsa laborum est qui.'),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

NOTE: This example renders an expanded Stack. For your content-cards, you would probably use e.g. a SizedBox to constrain its dimensions.

此致