4

I am working on an app in Flutter that presents stories and articles to users.

Currently, I am building the stories in a RichText widget to add special stylings and interactivity to various substrings of the text, and the data that is being input to building the list of TextSpan or WidgetSpan that I am filling the RichText with comes from a List of a custom class which holds the actual substring along with information for styling it.

For simplicity let's say each child of this List is:

CustomSpanClass(string: "substring that may include ,!'\" punctuation", weight: FontWeight.bold, ...etc)

Currently the entire RichText is being held in a container that is scrollable so the user can scroll through the story on the same page. I want to give users the option to have a swiping reading experience though, and here lies my biggest obstacle: the data being fed in is just one large list of substrings, so I have no idea at which point the text would overflow the screen.

Does anyone know how to capture which TextSpan/WidgetSpan would first overflow the constraints of its container which is bound by the screen's width and height? I have done a lot of reading and think maybe I need to utilize/access the RenderObject but I'm really at a loss here.

This is going to be a bit of an over-simplification but, in other words, if I have a RichText:

Text.rich(
  children: [
    CustomSpan(string: "Flutter is pretty awesome,"),
    CustomSpan(string: "and I love that it uses"),
    CustomSpan(string: "widgets", weight: FontWeight.bold, color: Colors.blue),
    CustomSpan(string: "and has hot reload capability."),
    CustomSpan(string: "This span won't fit on the same screen for an iPhone 8 plus",),
    CustomSpan(string: "And this span won't fully fit on the same screen for an iPhone 11 Pro."),
  ],
)

Does anyone have any idea how I could get the index of the first child of the Text.rich() spans that overflows a page, and proceed to build a second screen from there?

Tristan Bennett
  • 746
  • 1
  • 8
  • 18

1 Answers1

1

You can use TextPainter to reimplement the text paging algorithm base on this answer (Android native).

To get the line startOffset and endOffset you can use textPainter.getPositionForOffset https://stackoverflow.com/a/56943995/2474384

My try to implement:

  void _paginate() {
    final pageSize =
        (_pageKey.currentContext.findRenderObject() as RenderBox).size;

    _pageTexts.clear();

    final textSpan = TextSpan(
      text: widget.text,
      style: widget.style,
    );
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout(
      minWidth: 0,
      maxWidth: pageSize.width,
    );

    // https://medium.com/swlh/flutter-line-metrics-fd98ab180a64
    List<LineMetrics> lines = textPainter.computeLineMetrics();
    double currentPageBottom = pageSize.height;
    int currentPageStartIndex = 0;
    int currentPageEndIndex = 0;

    for (int i = 0; i < lines.length; i++) {
      final line = lines[i];

      final left = line.left;
      final top = line.baseline - line.ascent;
      final bottom = line.baseline + line.descent;

      // Current line overflow page
      if (currentPageBottom < bottom) {
        // https://stackoverflow.com/questions/56943994/how-to-get-the-raw-text-from-a-flutter-textbox/56943995#56943995
        currentPageEndIndex =
            textPainter.getPositionForOffset(Offset(left, top)).offset;
        final pageText =
            widget.text.substring(currentPageStartIndex, currentPageEndIndex);
        _pageTexts.add(pageText);

        currentPageStartIndex = currentPageEndIndex;
        currentPageBottom = top + pageSize.height;
      }
    }

    final lastPageText = widget.text.substring(currentPageStartIndex);
    _pageTexts.add(lastPageText);
  }

Demo.

Full code.

ltvu93
  • 91
  • 1
  • 12
  • this is really nice, I wonder if this can be applied to an HTML rendering element? – mohamnag Aug 17 '21 at 06:35
  • @mohamnag IIRC, the Flutter team ported textPainter functions to flutter web in the previous flutter update, so I think i will work with flutter web HTML rendering element as well. You can test it on dartpad. https://dartpad.dev/?id=36b249d1b5b5861a5ef58d958a50ad98 – ltvu93 Sep 09 '21 at 02:38