如何在Flutter中根据屏幕大小对长文本进行分页

4

我正在使用Flutter开发一款应用程序,为用户呈现故事和文章。

目前,我正在使用RichText小部件构建故事以添加特殊样式和交互性到文本的各个子串,并且输入构建TextSpan列表或WidgetSpan列表的数据来填充RichText的是一个自定义类的列表,该自定义类包含实际子字符串以及其样式化信息。

为了简单起见,我们假设此列表的每个子项是:

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

目前整个RichText被容器包含,该容器可滚动,以便用户可以在同一页上滚动故事。我想为用户提供可滑动的阅读体验,但是最大的障碍在于:输入的数据只是一个由多个子字符串组成的长列表,因此我不知道文本何时会超出屏幕。

有人知道如何捕获TextSpan/WidgetSpan将首先溢出其容器(该容器受屏幕宽度和高度的限制)的位置吗?我已经阅读了很多资料,认为可能需要利用/访问RenderObject,但我真的很茫然。

这可能有点过于简化,换句话说,如果我有一个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."),
  ],
)

有没有人知道如何获得Text.rich()中超出页面的第一个子文本范围的索引,并继续从那里构建第二个屏幕?
1个回答

1

您可以使用TextPainter基于this answer (Android native)重新实现文本分页算法。

要获取行的startOffset和endOffset,您可以使用textPainter.getPositionForOffset https://dev59.com/DLTna4cB1Zd3GeqPC_rb#56943995

我的尝试实现:

  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://dev59.com/DLTna4cB1Zd3GeqPC_rb#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);
  }

演示

完整代码


这真的很不错,我想知道这是否可以应用于HTML渲染元素? - mohamnag
如果我没记错的话,Flutter团队在之前的Flutter更新中将textPainter函数移植到了Flutter Web上,所以我认为它也可以与Flutter Web HTML渲染元素一起使用。你可以在Dartpad上测试一下。 https://dartpad.dev/?id=36b249d1b5b5861a5ef58d958a50ad98 - ltvu93

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接