梳理Flutter项目的过程中发现还有一些遗留的TODO
没处理,其中有一个和Text
组件相关。
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,
)
设置无效,只显示一行:
当时赶项目进度,没时间细究,暂时通过设置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
方法,确实是这样的。
Text
的build
方法(省略部分):
build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
...
Widget result = RichText(
...,
maxLines: maxLines ?? defaultTextStyle.maxLines,
...,
);
...
return result;
}
Widget
问题的原因难道就是这个?断点调试看看:
DefaultTextStyle
并没有指定maxLines
的值,也是null
。通过不断断点调试,可以很确定maxLines
的值一直都是null
。这难道是Flutter的bug?想了想应该不可能,如果是Flutter的bug,这么明显的问题应该早就被修复了。
假定Flutter没问题,那问题只能出在Text
组件的使用上。除了maxLines
,还设置了softWrap
和overflow
属性。咦?️不限制行数的情况下,文本不存在溢出情况,那设置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
有问题,其他文本溢出处理方式没问题。
通过追踪overflow
属性的传递,来到RenderParagraph
类的构造方法:
初始化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;
关键在于第二段文档注释,当maxLines
为null
且文本不止一行(文本宽度超出一行的宽度)时,ellipsis
会用于第一行,也就是截断为一行。文档已经说的很清楚了,我也不知道当时为啥还要标个TODO
。不过有句话说得好,来都来了,怎么也得继续再往下看看具体是在哪做文本截断处理的。
找了一番,发现难以继续分析下去,文本的绘制应该涉及到了Flutter引擎部分,光靠Flutter框架代码想要分析下去有点难。不过,也不是没有收获,在RenderParagraph
类的performLayout
方法中发现了一些关键调用:
执行布局时,调用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
方法的第三段文档注释也明确说了,如果maxLines
为null
且ellipsis
不为空,会返回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)。
从调试结果看,如果有ellipsis
且不限制行数时,会提前终止循环,也就是第一行就被截断。分析到这,基本可以确定文本最终就是在这被分行截断。
Text组件设置不限行数时,请勿将overflow
属性设置为TextOverflow.ellipsis
。
如果这篇文章对你有所帮助,请不要吝啬你的点赞加星,谢谢~