2

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

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

屏幕截图文本填充

我在做什么:在这篇中篇文章之后,我首先创建了一个带有文本和样式的 TextSpan,然后将其添加到 TextPainter 中,然后使用, 和调用该layout()函数。最后,我使用以下命令创建 LineMetrics :minWidth: 0maxWidth: maxTextWidthtextPainter.computeLineMetrics()

  // 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 和普通文本元素进行绘制,但所描述的问题没有任何变化。

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

一些相关问题:

4

2 回答 2

1

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

虽然我找不到颤动线度量的错误宽度计算问题的原因/解决方案,但我确实找到了在构建之前获取颤动文本小部件高度问题的解决方案:

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

对于颤动线度量,我只找到了一种解决方法,可以解决 99% 的问题?案例:

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

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

textPainter.computeLineMetrics();
于 2021-06-06T17:58:58.903 回答
1

我不能说textPainter.computeLineMetrics你观察到的奇怪行为,但我认为你的问题有一个不同的解决方案,它不涉及测量文本小部件的高度。

content-cards可以使用带有PositionedBackdropFilter的Stack,您可以对其进行剪辑以适合覆盖的文本。这样做时,您无需费心测量 Text 小部件。

例子:

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

注意:此示例呈现展开的堆栈。对于您的内容卡,您可能会使用例如SizedBox来限制其尺寸。

此致

于 2021-06-07T10:03:49.103 回答