Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter problem getting text-height with LineMetrics

I have an app displaying content-cards with text in front of a background-image. For better readability a blur-layer is added between text and background-image. As it is not easy with flutter to get the height of a widget (or text) before it is build, the only way I could find to solve this problem (without using callbacks or redraws) are LineMetrics. With LineMetrics I can calculate the space the text will take to draw the blur-layer in the correct size.

Now comes the problem: the calculated width attribute of LineMetrics sometimes doesn't fit the rendered text width. This is problematic in cases where it causes the miss of a line break, as the blurred background then doesn't cover the whole text-area anymore.

Screenshot Text Fill

What am I doing: Following this medium article I first create a TextSpan with text and style, then add it to a TextPainter and later call the layout() function with minWidth: 0, and maxWidth: maxTextWidth. Finally I create the LineMetrics with textPainter.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,
    );
  }

I added red outlines behind each text-line with the calculated width of LineMetrics to visualise the problem. Most of the time it works but sometimes the calculated width's doesn't match:

Screenshot Text Outline

I tried out almost every possible attribute in TextStyles (WordSpacing, LetterSpacing, textWidthBasis...), build it with and without custom font, drawing with RichText and normal text Elements, but nothing changes on the described problem.

Can anyone help to fix this strange behaviour, or provide an alternative method to get the text-height before a widget is build?

Some related issues:

  • How to get the raw text from a Flutter TextBox
  • What is the meaning Flutter's width metrics for the Paragraph class?
  • How can I get the size of the Text Widget in flutter
like image 216
Rafa2602 Avatar asked Mar 24 '26 14:03

Rafa2602


1 Answers

EDIT: It turns out in some cases this solution works only with a static fix (maxWidth - 10) too 😕

Although I couldn't find the reason/solution for the problem of wrong width-calculations of flutter line-metrics, I did find a solution for the problem of getting the height of a flutter text-widget before it's build:

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

With this String extension it's possible to calculate the correct height of texts to draw the background blur in a corresponding size:

// 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;

These Screenshots proof that the initial problem is solved when using the string extension:

enter image description here

I created this solution based on two answers of the related topic:
How can I get the size of the Text Widget in flutter

  • https://stackoverflow.com/a/56028745/9915210
  • https://stackoverflow.com/a/67482567/9915210

For flutter line-metrics I only found a workaround that solves the problem in 99%? of cases:

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

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

textPainter.computeLineMetrics();
like image 110
Rafa2602 Avatar answered Mar 27 '26 09:03

Rafa2602



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!