Flutter: How to Get the Number of Text Lines
Asked Answered
G

6

34

How to Get the Number of Text Lines In our Flutter project, we need to determine if the number of lines of text exceeds 3, then we will display a prompt message. How can we use the code to implement it?

Grapefruit answered 8/1, 2019 at 11:39 Comment(4)
Should that happen on user input or when showing a Text widget to the user?Automatize
Do you mean Text or TextField input ?Aucoin
you can use maxLines property of texfiled to limit the sameThaxter
i used Text(),and want get the number of Text() LinesGrapefruit
E
65

If you simply want to know how many intrinsic lines the text contains, you can do a simple

final numLines = '\n'.allMatches(yourText).length + 1;

However, I guess you're more interested in the number of lines that are actually being displayed visually. Here, things get a little more complicated, because you need to know the available space (the BoxConstraints) in order to calculate how many lines the text needs. To do that, you can use a LayoutBuilder to delay the widget building and a TextPainter to do the actual calculation:

return LayoutBuilder(builder: (context, constraints) {
  final span = TextSpan(text: yourText, style: yourStyle);
  final tp = TextPainter(text: span, textDirection: TextDirection.ltr);
  tp.layout(maxWidth: constraints.maxWidth);
  final numLines = tp.computeLineMetrics().length;

  if (numLines > 3) {
    // TODO: display the prompt message
    return ColoredBox(color: Colors.red);
  } else {
    return Text(yourText, style: yourStyle);
  }
});

I extracted some of the code from the auto_size_text pub package, which might also be interesting to you: It sizes its text so it fits in the given space.

Anyhow, be careful when displaying the prompt: Your widget's build method may be called several times per second, resulting in multiple prompts being displayed simultaneously.

Note: The computeLineMetrics() function was added after the initial version of the answer. That's why a previous version of this answer used a TextPainter(text: span, maxLines: 3) and then checked if tp.didExceedMaxLines.

Expansible answered 9/1, 2019 at 15:46 Comment(3)
Is there a way to get the actual number of lines if maxLInes is infinity?Overmatter
As far as I know, there's no official API for that, but there are some hacky ways to do that for sure. For example, you could divide tp.height by the fontSize * fontScale, but I'm not sure that works reliably. Or you could increase maxLines until it fits (see github.com/leisim/auto_size_text/issues/14 for a performance discussion about a similar topic).Expansible
The following post discusses the lack of this feature in more detail: medium.com/@studymongolian/…Expansible
O
18

Update: this answer is here for historical purposes. See my updated answer here for a much easier solution.


If you have a TextPainter object and you have already called layout, then you can get the number of lines by selecting everything and calling getBoxesForSelection. Each box is the size of the line.

TextSelection selection = TextSelection(baseOffset: 0, extentOffset: text.length);
List<TextBox> boxes = textPainter.getBoxesForSelection(selection);
int numberOfLines = boxes.length;

Here is an example:

final text = 'My text line.\nThis line wraps to the next.\nAnother line.';

enter image description here

print(numberOfLines); // 4

Unfortunately, this fails if there is a mix of bidirectional text:

final text = 'My text line.\nThis كلمة makes more boxes.\nAnother line.';

enter image description here

print(numberOfLines); // 6

This could be overcome by only counting the boxes that along one edge. If you notice the data from above, there are only four boxes that have an edge at 0.0.

flutter: TextBox.fromLTRBD(0.0, 0.2, 171.8, 36.0, TextDirection.ltr)
flutter: TextBox.fromLTRBD(0.0, 36.5, 68.4, 72.3, TextDirection.ltr)
flutter: TextBox.fromLTRBD(68.4, 38.2, 111.5, 75.0, TextDirection.rtl)
flutter: TextBox.fromLTRBD(111.5, 36.5, 299.9, 72.3, TextDirection.ltr)
flutter: TextBox.fromLTRBD(0.0, 77.2, 92.2, 113.0, TextDirection.ltr)
flutter: TextBox.fromLTRBD(0.0, 113.2, 179.7, 149.0, TextDirection.ltr)

I wish there were a TextPainter.numberOfLines method, but I haven't found it.

Overmatter answered 6/7, 2019 at 1:10 Comment(2)
Suragch, thanks for the article and the code you put together. For my own testing purposes, I pulled out your snippet into a StatelessWidget to make it easier to experiment. gist.github.com/venkatd/86c0185e38d5a482c2f04ecf5aeb057bOrgan
it did not work in flutter web if you text without \n in text.Minnick
O
17

It is now easy to find the number of lines in a paragraph:

import 'dart:ui' as ui;

List<ui.LineMetrics> lines = textPainter.computeLineMetrics();
int numberOfLines = lines.length;

Notes

  • computeLineMetrics must be called after layout.
  • I had another answer which I am keeping for historical purposes because I need to link to it in an explanation of the change.

See also

Overmatter answered 31/8, 2019 at 13:46 Comment(4)
Not sure if it's related but is there any method on Flutter that can get text offset of a line in Flutter or get the String value of text inside a TextBox? Similar to getLinexxx on Android (developer.android.com/reference/android/text/…)Epergne
@LongPhan. I had the same question before, too. It's possible but not super convenient. In the future they may add that info to LineMetrics, but for now see this question and my answer to it.Overmatter
Thank u. I think we can propose it to Flutter team.Epergne
@LongPhan, they know about it.Overmatter
L
9

I just got backed by this code , hence this is how I validate whether it crosses the maxlines or not

var text="Clita takimata dolor diam magna aliquyam et sed dolores sadipscing. Sed ut 
diam rebum labore sadipscing sit amet, diam labore.";
final span=TextSpan(text:text);
final tp =TextPainter(text:span,maxLines: 3,textDirection: TextDirection.ltr);
tp.layout(maxWidth: MediaQuery.of(context).size.width); // equals the parent screen width
print(tp.didExceedMaxLines);
//false for maxlines 3
//true for maxlines 1
Loosestrife answered 29/5, 2021 at 6:2 Comment(1)
it did not work in flutter web remains always false even a single lineMinnick
O
1

in case someone wants to find the number of lines in a TextField

flutter overwrites the textStyle and the textPainter before the rendering in two places so you need to overwrite them too before calling computeLineMetrics().

first, before building the EditableText widget, there are a lot of changes to the textStyle. but only one change affects the layout:

final ThemeData theme = Theme.of(context);
final tp = TextPainter(
  text: TextSpan(
    text: text,
    // merge your style into the default theme
    style: (theme.useMaterial3 ? theme.textTheme.bodyLarge! : theme.textTheme.titleMedium!).merge(style),
  ),
  textDirection: textDirection,
  maxLines: null,
);

second, RenderEditable in editable.dart file changes the layout of the textPainter to make space for the cursor in the end of the line:

// const _kCaretGap can be found in editable.dart file
const caretGap = 1;
tp.layout(maxWidth: constraints.maxWidth - caretGap - cursorWidth);

then you can get the correct num of lines

final numLines = tp.computeLineMetrics().length;

Here is an example of a Widget. You can change it to fit your needs

typedef OnChangeTextFieldWithLinesCallback = void Function(String text, List<LineMetrics> lines);

// force you to use InputDecoration.collapse() and removing all borders and paddings
class InputCollapsedDecoration extends InputDecoration {
  const InputCollapsedDecoration({
    required String hintText,
    floatingLabelBehavior,
    floatingLabelAlignment,
    hintStyle,
    hintTextDirection,
    filled = false,
    fillColor,
    focusColor,
    hoverColor,
    enabled = true,
  }) : super.collapsed(
    hintText: hintText,
    floatingLabelBehavior: floatingLabelBehavior,
    floatingLabelAlignment: floatingLabelAlignment,
    hintStyle: hintStyle,
    hintTextDirection: hintTextDirection,
    filled: filled,
    fillColor: fillColor,
    focusColor: focusColor,
    hoverColor: hoverColor,
    border: InputBorder.none,
    enabled: enabled,
  );
}

class LineListenerTextField extends StatelessWidget {
  const LineListenerTextField({
    super.key,
    required this.onChanged,
    this.controller,
    this.focusNode,
    this.decoration = const InputCollapsedDecoration(hintText: ''),
    this.keyboardType,
    this.style,
    this.textDirection,
    this.readOnly = false,
    this.autofocus = false,
    this.maxLines = 1,
    this.minLines,
    this.inputFormatters,
    this.cursorWidth = 2.0,
    this.cursorRadius,
    this.scrollController,
  });

  final TextEditingController? controller;
  final FocusNode? focusNode;
  final InputCollapsedDecoration decoration;
  final TextInputType? keyboardType;
  final TextStyle? style;
  final TextDirection? textDirection;
  final bool readOnly;
  final bool autofocus;
  final int? maxLines;
  final int? minLines;
  final List<TextInputFormatter>? inputFormatters;
  final double cursorWidth;
  final Radius? cursorRadius;
  final ScrollController? scrollController;
  final OnChangeTextFieldWithLinesCallback onChanged;

  @override
  Widget build(BuildContext context) {

    return TextField(
      focusNode: focusNode,
      textDirection: textDirection,
      controller: controller,
      cursorWidth: cursorWidth,
      cursorRadius: cursorRadius,
      scrollController: scrollController,
      readOnly: readOnly,
      scrollPadding: EdgeInsets.zero,
      decoration: decoration,
      minLines: minLines,
      maxLines: maxLines,
      keyboardType: TextInputType.multiline,
      style: style,
      onChanged: (text) {
        final ThemeData theme = Theme.of(context);
        final tp = TextPainter(
          text: TextSpan(
            text: text,
            // merge your style into the default theme
            style: (theme.useMaterial3 ? theme.textTheme.bodyLarge! : theme.textTheme.titleMedium!).merge(style),
          ),
          textDirection: textDirection,
          maxLines: null,
        );
        // const _kCaretGap can be found in editable.dart file
        const caretGap = 1;
        tp.layout(maxWidth: context.size!.width - caretGap - cursorWidth);
        final lines = tp.computeLineMetrics();
        onChanged(text, lines);
      },
    );
  }
}
Overmuch answered 3/5 at 8:2 Comment(0)
H
-1

just place property maxLines: null, and TextInputAction.done, if you want restrict new lines to a particular limit count "\n" .

Halvah answered 5/4, 2022 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.