【Flutter基础组件】Text详细讲解

本文详细介绍了Flutter中Text组件的内部实现,特别是TextSpan树的形成过程。Text组件的构建有两种方式,分别基于"data"和"textSpan"。在构建过程中,TextSpan树被用来表示文本内容,最终通过TextPainter布局到屏幕上。在engine层,TextSpan数据按照树形结构添加,遵循从上到下、从左到右的顺序。文章还探讨了TextSpan与engine层数据的交互,涉及ParagraphBuilder及其在engine层的实现。
摘要由CSDN通过智能技术生成

前言

这一篇文章我来带大家一起来阅读一下flutter框架中的文本组件Text相关源码,对于文本组件Text的使用大家应该是十分的熟悉,但其内部实现机制应该是很少有人去了解。当你了解完text的内部实现后,你会发现你完全可以通过自定义自己的Text来实现自己的文本内容和布局方式。话不多说,让我们一起进入flutter世界,一睹Text组件的真正容貌吧。


第一节:Text之TextSpan树的形成

阅读过Text源码的人都应该知道Text最终是由TextSpan组成的树形结构,以下为Text类中的两个数据参数:

final String data;
final InlineSpan textSpan;

以下为构建“data”数据参数的构造函数:

const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  }) : 
       textSpan = null,
       super(key: key);

上图代码中可以看到该构造函数只能传入“data”作为必要的参数,并且“textSpan = null”。

const Text.rich(
    this.textSpan, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  }) : 
       data = null,
       super(key: key);

而上图代码中只能传入“textSpan”作为必要参数,并且“data = null”。

 

这里可以大概明了类Text只有两种构造方式:

  1. 将参数“data”作为构造源,此时忽略了"textSpan"参数。
  2. 将参数“textSpan"作为构造源,此时忽略了“data”参数。

 

我们再来看看Text类的“build“函数的主要逻辑:

@override
Widget build(BuildContext context) {
  ...
  Widget result = RichText(
    textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
    textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
    locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
    softWrap: softWrap ?? defaultTextStyle.softWrap,
    overflow: overflow ?? defaultTextStyle.overflow,
    textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
    maxLines: maxLines ?? defaultTextStyle.maxLines,
    strutStyle: strutStyle,
    textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
    textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
    text: TextSpan(
	  style: effectiveTextStyle,
	  text: data,
	  children: textSpan != null ? <InlineSpan>[textSpan] : null,
    ),
  );
  ...
  return result;
}

上图中我们主要观察"RichText"构造时传入的参数“text”,该text是一个"TextSpan"对象,该对象有两个主要的数据源:

  • text - String类型,当类Text采用第一种传入“data”的构造方式时,该text才有值。
  • children - List<InlineSpan>?类型,当类Text采用第二种传入"textSpan"的构造方式时,该children才不会为null。

从以上分析中得到这样的论述:

  1. 当以“data”构造Text时,树形中只会存在一个根TextSpan。
  2. 当以"textSpan"构造Text时,树形中会存在多个TextSpan子树结构。

我们具体看图:

1. 当以“data”构造Text时的树结构:

 

2. 当以"textSpan"构造Text时的树结构:

 

 所以总结下来,不管Text是如何构建,内部总是会形成一棵TextSpan树。我们来看下面的例子:

Text.rich(
	TextSpan(
	  text: "ABC\n",
	  children: [
		TextSpan(
			text: "DE\n",
			children: [
			  TextSpan(
				text: "F\n",
				style: TextStyle(
				  fontSize: 12
				)
			  )
			]
		),
		TextSpan(
			text: "HIJ\n",
			style: TextStyle(
			  color: Colors.green
			),
			children: [
			  TextSpan(
				text: "K\n",
				style: TextStyle(
				  color: Colors.black
				)
			  ),
			  TextSpan(
				text: "LMN\n",
			  )
			]
		)
	  ]
	),
	style: TextStyle(
	  fontSize: 24,
	  color: Colors.red
	)
  )

显示效果如下:

代码结合上图图片显示可知,代码中的顺序为从上往下来显示每个TextSpan数据,这是符合用户角度的。

我们再来看该段代码演示的树形图:

 

 

从图形中的树状结构可知,每个TextSpan中都有“data”和"style"两个字段描述文本,其中“data”是文本字符内容,而“style”是用来修饰文本的显示风格的,我们称为文本样式。可以看到,如果子节点的文本样式为空或者样式中的某个属性没有,它就会继承父样式中的属性,这里采用的是就近原则,直到找到根Root的样式。当然如果根样式中依然未找到,就会使用默认值,像字体fontSize的默认值为14,颜色color的默认值为黑色等等。

 

那么,对于这种树形特性,其内部是如何工作的呢?我们知道Text类的构建函数“build“最终返回的是RichText类对象,我们再深入RichText类代码中一探究竟:

class RichText extends MultiChildRenderObjectWidget {
  
  ......

  @override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      textWidthBasis: textWidthBasis,
      textHeightBehavior: textHeightBehavior,
      locale: locale ?? Localizations.localeOf(context, nullOk: true),
    );
  }

  ......
}

可以看到RichText类创建的渲染对象为RenderParagraph,并且将TextSpan的根节点“text”传入,其余参数我们暂且不讨论。继续看RenderParagraph类的关键代码:

@override
void performLayout() {
  final Bo
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值