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.

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:

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:
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:

I created this solution based on two answers of the related topic:
How can I get the size of the Text Widget in flutter
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With