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

Posted

技术标签:

【中文标题】如何在 Flutter 中根据屏幕大小对长文本进行分页【英文标题】:How to paginate long text based on screen size in Flutter 【发布时间】:2020-09-03 18:38:58 【问题描述】:

我正在开发一款 Flutter 应用,用于向用户展示故事和文章。

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

为简单起见,假设此 List 的每个子项是:

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

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

要获取行 startOffset 和 endOffset 你可以使用 textPainter.getPositionForOffset https://***.com/a/56943995/2474384

我的尝试:

  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://***.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.

【讨论】:

这真是太好了,不知道这是否可以应用于html渲染元素? @mohamnag IIRC,Flutter 团队在之前的 Flutter 更新中将 textPainter 函数移植到了 Flutter Web,所以我想我也会使用 Flutter Web HTML 渲染元素。您可以在 dartpad 上进行测试。 dartpad.dev/?id=36b249d1b5b5861a5ef58d958a50ad98

以上是关于如何在 Flutter 中根据屏幕大小对长文本进行分页的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中根据屏幕大小重新排列 UI 元素

如何根据屏幕大小调整按钮的图像和文本大小(自动布局)

Flutter:如何将文本小部件中心放在屏幕上

如何让这个flutter屏幕根据不同的屏幕尺寸做出响应?

如何根据屏幕尺寸调整颤振小部件的大小?

flutter屏幕适配