Flutter问题记录 - Text组件设置不限行数无效

文章目录

  • 前言
  • 开发环境
  • 问题描述
  • 问题分析
  • 解决方案
  • 最后


前言

梳理Flutter项目的过程中发现还有一些遗留的TODO没处理,其中有一个和Text组件相关。

开发环境

  • Flutter: 3.7.12
  • Dart: 2.19.6

问题描述

Text组件设置maxLines: null不限制行数:

Text(
  'The [Text] widget displays a string of text with single style. The string might break across multiple lines or might all be displayed on the same line depending on the layout constraints.',
  softWrap: true,
  overflow: TextOverflow.ellipsis,
  maxLines: null,
)

设置无效,只显示一行:

Flutter问题记录 - Text组件设置不限行数无效_第1张图片

当时赶项目进度,没时间细究,暂时通过设置maxLines: 9999实现虚假的不限制行数。

问题分析

先看看maxLines的文档注释:

/// An optional maximum number of lines for the text to span, wrapping if necessary.
/// If the text exceeds the given number of lines, it will be truncated according
/// to [overflow].
///
/// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
/// edge of the box.
///
/// If this is null, but there is an ambient [DefaultTextStyle] that specifies
/// an explicit number for its [DefaultTextStyle.maxLines], then the
/// [DefaultTextStyle] value will take precedence. You can use a [RichText]
/// widget directly to entirely override the [DefaultTextStyle].

从文档中可知,当maxLines设为null时,如果DefaultTextStyle中有指定maxLines的值,那么将以这个值为准。查看Text组件的build方法,确实是这样的。

Textbuild方法(省略部分):


Widget build(BuildContext context) {
  final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
  ...
  Widget result = RichText(
    ...,
    maxLines: maxLines ?? defaultTextStyle.maxLines,
    ...,
  );
  ...
  return result;
}

问题的原因难道就是这个?断点调试看看:

Flutter问题记录 - Text组件设置不限行数无效_第2张图片

DefaultTextStyle并没有指定maxLines的值,也是null。通过不断断点调试,可以很确定maxLines的值一直都是null。这难道是Flutter的bug?想了想应该不可能,如果是Flutter的bug,这么明显的问题应该早就被修复了。

假定Flutter没问题,那问题只能出在Text组件的使用上。除了maxLines,还设置了softWrapoverflow属性。咦?️不限制行数的情况下,文本不存在溢出情况,那设置overflow属性完全是多此一举。移除overflow: TextOverflow.ellipsis,重新运行一切正常!

TextOverflow定义:

enum TextOverflow {
  /// Clip the overflowing text to fix its container.
  clip,

  /// Fade the overflowing text to transparent.
  fade,

  /// Use an ellipsis to indicate that the text has overflowed.
  ellipsis,

  /// Render overflowing text outside of its container.
  visible,
}

奇怪的是只有TextOverflow.ellipsis有问题,其他文本溢出处理方式没问题。

Flutter问题记录 - Text组件设置不限行数无效_第3张图片

通过追踪overflow属性的传递,来到RenderParagraph类的构造方法:

Flutter问题记录 - Text组件设置不限行数无效_第4张图片

初始化TextPainter时,如果overflow属性值等于TextOverflow.ellipsis,会用_kEllipsis常量初始化ellipsis_kEllipsis常量定义:

// '\u2026'即省略号'…'
const String _kEllipsis = '\u2026';

继续往下走,在TextPainter类中找到关于ellipsis的定义:

/// The string used to ellipsize overflowing text. Setting this to a non-empty
/// string will cause this string to be substituted for the remaining text
/// if the text can not fit within the specified maximum width.
///
/// Specifically, the ellipsis is applied to the last line before the line
/// truncated by [maxLines], if [maxLines] is non-null and that line overflows
/// the width constraint, or to the first line that is wider than the width
/// constraint, if [maxLines] is null. The width constraint is the `maxWidth`
/// passed to [layout].
///
/// After this is set, you must call [layout] before the next call to [paint].
///
/// The higher layers of the system, such as the [Text] widget, represent
/// overflow effects using the [TextOverflow] enum. The
/// [TextOverflow.ellipsis] value corresponds to setting this property to
/// U+2026 HORIZONTAL ELLIPSIS (…).
String? get ellipsis => _ellipsis;
String? _ellipsis;

关键在于第二段文档注释,当maxLinesnull且文本不止一行(文本宽度超出一行的宽度)时,ellipsis会用于第一行,也就是截断为一行。文档已经说的很清楚了,我也不知道当时为啥还要标个TODO。不过有句话说得好,来都来了,怎么也得继续再往下看看具体是在哪做文本截断处理的。

找了一番,发现难以继续分析下去,文本的绘制应该涉及到了Flutter引擎部分,光靠Flutter框架代码想要分析下去有点难。不过,也不是没有收获,在RenderParagraph类的performLayout方法中发现了一些关键调用:

screenshot5

执行布局时,调用textPainter.size获取的高度只有一行的高度,最关键的是调用_textPainter.didExceedMaxLines返回了true,这表示文本被截断或被省略。 didExceedMaxLines方法的定义:

/// Whether any text was truncated or ellipsized.
///
/// If [maxLines] is not null, this is true if there were more lines to be
/// drawn than the given [maxLines], and thus at least one line was omitted in
/// the output; otherwise it is false.
///
/// If [maxLines] is null, this is true if [ellipsis] is not the empty string
/// and there was a line that overflowed the `maxWidth` argument passed to
/// [layout]; otherwise it is false.
///
/// Valid only after [layout] has been called.
bool get didExceedMaxLines {
  assert(_debugAssertTextLayoutIsValid);
  return _paragraph!.didExceedMaxLines;
}

didExceedMaxLines方法的第三段文档注释也明确说了,如果maxLinesnullellipsis不为空,会返回true。沿着_paragraph!.didExceedMaxLines来到了Flutter SDK目录/bin/cache/pkg/sky_engine/lib/ui/text.dart文件:

/// True if there is more vertical content, but the text was truncated, either
/// because we reached `maxLines` lines of text or because the `maxLines` was
/// null, `ellipsis` was not null, and one of the lines exceeded the width
/// constraint.
///
/// See the discussion of the `maxLines` and `ellipsis` arguments at
/// [ParagraphStyle.new].
<Bool Function(Pointer<Void>)>(symbol: 'Paragraph::didExceedMaxLines', isLeaf: true)
external bool get didExceedMaxLines;

从这开始,已经脱离Flutter框架,来到Flutter引擎。经过一番查找,最终锁定在了skia项目中的TextWrapper.cpp文件,里面有个breakTextIntoLines函数,顾名思义,这个函数的作用就是将文本分成几行。这个函数有点长,又是C++写的,直接看有点累,如果能调试会轻松很多,调试环境的搭建可以参考这篇文章Flutter - 搭建引擎调试环境(iOS)。

Flutter问题记录 - Text组件设置不限行数无效_第5张图片

从调试结果看,如果有ellipsis且不限制行数时,会提前终止循环,也就是第一行就被截断。分析到这,基本可以确定文本最终就是在这被分行截断。

解决方案

Text组件设置不限行数时,请勿将overflow属性设置为TextOverflow.ellipsis

最后

如果这篇文章对你有所帮助,请不要吝啬你的点赞加星,谢谢~

你可能感兴趣的:(问题记录,flutter)