2.1.1 Text
Text组件,顾名思义,是一个最基础的显示文本的组件。一般来讲,开发者只需把想要渲染的字符串传给Text组件就可以完成“从字符串到组件”的转换。例如可在屏幕上渲染"Hello
Flutter!"字样,代码如下:
Text("Hello Flutter!")
运行效果如图2-1所示。
图2-1 Text组件默认的文本显示风格
这里可以观察到,即使在调用Text组件时没有传入任何字体样式信息,它仍然“较为正确”地使用了系统默认的字体大小和颜色。这其实是因为Flutter新建项目的时候,默认自动生成的演示小程序已经包含了相对成熟的界面框架,其中自动生成的Scaffold组件就含有向下级组件提供默认字体样式的功能,包括字体大小和颜色等。
在MaterialApp中,如果Text组件的上级组件中不存在任何默认的样式,则Text组件会渲染出醒目的红色大号字,并加上耀眼的黄色双下画线,用来提醒开发人员注意设置文本样式。
例如,在新建一个Flutter应用程序后把里面自动生成的代码全部删除,替换成以下代码:
在这段代码中,Text组件的父级是Center组件(用于居中,具体用法可以参考第1章的相关内容),而Center组件的父级是MaterialApp(提供最基本的App常用功能,具体可以参考第10章的相关介绍)。相对于Flutter新建项目时生成的计数器演示程序,这里缺少了Scaffold组件,因此Text组件如图2-2所示,渲染出黑底红字及醒目的黄色双下画线,以引起开发者的注意。
实战中若发现Text组件渲染出图2-2的效果,则应检查它是否能从上级继承文本样式,或使用style属性为其设置文本样式。
图2-2 Text组件缺少样式参数时的渲染风格
Flutter框架小知识
Flutter程序的入口在哪里?
通过上面这个非常短小的程序实例可以观察到,Dart程序的入口是main函数。这一点与很多其他编程语言相似。
Flutter应用程序启动的核心代码就在于main函数里面的这句runApp。它负责初始化WidgetsFlutterBinding等核心服务,并与Flutter引擎配合,最终将组件实体化后渲染到屏幕上。这里可以看到,调用runApp函数时只需传入一个参数,就是一个组件。Flutter会将这个组件当作组件树(Widget Tree)的根,再继续一层层嵌套其他组件,最终满足各式复杂的布局需求和程序效果。
1.必传的位置参数
在Text组件的默认构造函数中有一个必须传的位置参数(也称作非命名参数),且必须作为第1个参数传入。其内容就是需要显示的文本,类型是String。例如,上例中,Text("Hello Flutter!")就传入了"Hello Flutter!"这个字符串作为其位置参数的值。
必须注意的是,这里需要显示的文本字符串可以是空字符(""),但不可以为空值(null)。在Flutter 2.0之前,不慎传入null会导致运行时错误。在Flutter 2.0启用空安全后,传入null会直接导致代码无法编译,从而杜绝了可能会发生的运行时错误。
Dart Tips语法小贴士
Flutter 2.0中健全的空安全
Dart语言自2020年11月引入了健全的空安全(Sound Null Safety)概念,并于2021年3月随着Flutter 2.0的发布逐渐普及。在启用空安全的项目中,变量类型默认为非空。这样原本运行时的错误,就可以在编写代码时被发现。
若开发者确实需要允许某个变量可空,则在声明时需要在类型后加上问号,如String表示“非空字符串”,而“String?”则表示“可空字符串”。
除了默认的构造函数外,Text组件还有一个非常好用的构造函数,Text.rich()可以用于在一段文字之间切换样式。使用这个构造函数时,必传的位置参数则不再是需要显示的字符串,而是一个TextSpan类。与简单的字符串不同的是,TextSpan类除了可以通过text属性传入字符串外,还可以通过style属性自定义文字样式,甚至可以再通过children属性嵌套另一个TextSpan列表,以达成叠加文字样式的目的。如果在一段文字中需要用到多个不同的样式,则可以考虑使用这个构造函数。Text.rich()本质与RichText组件十分相似,其中TextSpan类的用法也是一致的,具体用法读者可参考本章。
2. style属性
Text组件的默认样式是由上级DefaultTextStyle(默认文本样式组件,详见2.1.2节)继承而来。如果需要修改样式,则可以通过style参数传入一个TextStyle类型的值。Text组件在渲染时,会自动将style参数中的样式与上级提供的样式合并,这种合并的设计在实战中非常有用。例如,当需要将文字的某一段加粗时,就可以单独传入字体粗细信息,而不需要传入其他信息,这样这段加粗的文字就会沿用周围文字的字体、字号、颜色等。TextStyle类有以下属性可供设置。
1)fontWeight
字体粗细信息,包括w100、w200、w300、w400、w500、w600、w700、w800、w900这一系列值,分别对应最细到最粗的字体样式。需要最细的字可以用w100表示,最粗的字可以用w900表示。通常实战中并不需要使用这么多不同粗细的字,因此也可以直接通过bold(粗体)和normal(正常)两个可读性更高的值设置是否要加粗。其中bold实际对应w700,而normal则对应w400的粗细样式。
图2-3从上到下依次展示了w100(最细)、w400(正常)、w700(粗体)和w900(最粗)的运行效果。
图2-3 设置不同字体粗细的效果
用于实现图2-3所示效果的代码如下:
2)fontStyle
字体样式信息,只有2个可选值:正常和斜体,分别为FontStyle.normal和FontStyle. italic。默认为正常显示,如需显示斜体,则可参考以下代码:
Text( "这段文字为斜体", style: TextStyle(fontStyle: FontStyle.italic), )
运行效果略。
3)自定义字体
若想使用非系统自带的特殊字体,则需先将字体文件(如ttf文件)放在项目的目录内,并在pubspec.yaml里设置目录路径。如需使用自定义字体的不同粗细或斜体,则需要传入相应的字体文件。例如,以下配置文件定义了2种字体,分别为Raleway和Schyler,代码如下:
这里配置文件的family标签决定了字体的名称,即在编写代码时需要通过fontFamily参数传入的值。定义名称后即可用fonts标签定义一个或多个字体文件,每个文件都用asset标签定义路径(这里的路径是相对于项目的pubspec.yaml文件位置的相对路径),以及用weight和style标签说明该字体文件是否为粗体或斜体版本。
由此可见,上例的配置文件共指定了5个字体文件的路径,其意义如下:
·Raleway字体,正常,文件路径:fonts/Raleway-Regular.ttf。
·Raleway字体,w500粗细,文件路径:fonts/Raleway-Medium.ttf。
·Raleway字体,w600粗细,文件路径:fonts/Raleway-SemiBold.ttf。
·Schyler字体,正常,文件路径:fonts/Schyler-Regular.ttf。
·Schyler字体,斜体,文件路径:fonts/Schyler-Italic.ttf。
定义好字体名称和文件路径后,在开发调用时,可以通过style属性的fontFamily参数传给Text组件。例如,以下代码就可以使用斜体的Schyler字体渲染,即fonts/Schyler-Italic.ttf这个字体文件:
运行效果略。
4)size
字号,可以传入一个小数类型,默认值为14.0。例如可以传入数值28以达到平时字号的两倍大小。相比于单独设置每个Text组件的字号信息,这里更推荐使用Theme或者DefaultTextStyle等继承式组件(InheritedWidget),统一设置整个App的字体风格。
5)color和backgroundColor
这2个属性负责调节颜色,其中color是指文本颜色,而backgroundColor是指背景颜色,传入类型均为Color类。这些TextStyle的参数都可以叠加使用,例如配合上文提到的字体粗细、样式和字号这3个参数,可以设置出一个黑底、白字、加大、加粗、加斜的文字,代码如下:
运行效果及与默认风格的对比如图2-4所示。
图2-4 同时设置背景颜色和字号等多种样式风格
6)decoration
借助文本修饰属性decoration,以及配套的decorationStyle、decorationColor和decorationThickness 共4个属性,可为文本添加修饰线段。例如,某些文字编辑软件中常见的一种修饰是针对拼写错误的单词标注红色波浪线,这个功能就可以借助这些参数实现。
其中,decoration参数可以传入3种TextDecoration,分别是overline(上画线)、underline(下画线)及lineThrough(划掉)。如果对同一个文字需要同时渲染多种修饰,则可以使用Combine()构造函数,传入一个TextDecoration列表。例如同时添加上画线和下画线的代码如下:
decoration: TextDecoration.combine([ TextDecoration.overline, TextDecoration.underline, ])
在剩余3个配套的属性中,decorationStyle参数可以传入5种TextDecorationStyle,分别是solid(实线)、dashed(虚线)、dotted(点线)、wavy(波浪线)及double(两条实线),而decorationColor和decorationThickness则分别对应修饰线的颜色和粗细。
例如,利用这些属性可制作一行被划掉的文字和一行有下画波浪线的文字,代码如下:
运行效果如图2-5所示。
图2-5 文本修饰线条的渲染效果
7)letterSpacing和wordSpacing
这2个属性用来调节文本间距,其中letterSpacing是字母与字母之间的留白,而wordSpacing是单词与单词之间的留白。这里的“单词”是以空格(或其他空白字符)为界线识别的,而一般汉语中不会出现空格,因此汉语中每个汉字之间的留白都可以直接使用字母letterSpacing属性设置。
例如,可用2个Text组件显示一些中英文混搭的句子,并将第1个的字母留白设置为4倍,将第2个的单词留白设为10倍,以便观察与对比,代码如下:
具体运行效果如图2-6所示。
8)文本特效渲染
Text组件的style属性也支持一些特效渲染,例如阴影、描边、渐变色等。其中最直接的方式是利用shadows参数传入一组阴影,代码如下:
图2-6 字母留白与单词留白的渲染效果对比
这段代码使用了一个BoxShadow,并将颜色设置为灰色,位移为10单位(向右和向下各偏移10单位),再设置8单位的模糊效果,最终运行效果如图2-7所示。
图2-7 文字阴影效果
值得一提的是,这里TextStyle的shadows参数是一个列表类型,因此它可以支持多个阴影。例如,同时传入左上、左下、右上、右下这4个方向的阴影,就可以制造出类似描边的效果,代码如下:
运行效果如图2-8所示。
图2-8 同时使用4个阴影以模拟描边的效果
其实这里shadows属性的用法与Container组件或DecoratedBox组件的decoration属性中BoxDecoration类的boxShadow参数非常类似,读者也可以参考第14章“渲染与特效”中关于DecoratedBox的内容。
除了阴影之外,TextStyle还支持利用foreground和background两个参数直接调用底层Paint(刷子)渲染自定义特效,直接操作前景和背景的渲染。相对于前面所介绍的同时使用4个阴影的方法,借助于Paint自定义前景(foreground)可以绘制出更完美的描边效果,甚至还可以先用Stack组件叠放2个Text组件,一个利用加粗描边,另一个显示白色叠盖,最终达到镂空的效果,代码如下:
运行效果如图2-9所示。
图2-9 文字镂空效果
同样借助于Paint,还可为文字添加渐变色的效果。例如,利用background属性指定背景颜色为从左到右的黑白渐变,再利用foreground属性设置前景文本颜色为从上到下由白至黑的渐变,完整代码如下:
程序运行效果如图2-10所示。
图2-10 文本与背景的颜色渐变
3.textAlign和textDirection
Text组件内文字的对齐方式不是通过前面介绍的style属性设置的,而是通过textAlign属性设置的。这是因为文字对齐不属于文本渲染样式,而是属于排版方式。TextAlign是一个枚举类型,分别有left(左齐)、right(右齐)、center(居中)、justify(两端对齐)、start(起始对齐)、end(末尾对齐)这6种值。
这里textAlign属性默认值为start,一般不需要改动。同时出于国际化考虑,通常不推荐使用left(左齐)和right(右齐),而是用start(起始对齐)和end(末尾对齐),这样Flutter在系统默认语言为汉语或英语等从左到右阅读的语言时将会自动向左对齐,反之(如阿拉伯语或希伯来语)则向右对齐。
当需要改变默认阅读方向时可使用textDirection属性。例如,在一台将系统语言设置为汉语或英语的设备上,可以通过TextDirection.rtl强制改为从右到左阅读,以方便测试。
这里需要注意的是,textAlign用于设置文本在Text组件内的对齐方式,而不是Text组件本身在其父级组件中的对齐方式。换句话说,只有当Text组件的尺寸大于需要渲染的文本尺寸时,设置对齐方式才有意义。实战中,如果设定完textAlign属性后,发现文本并没有按照传入的TextAlign值渲染文本,则很可能是因为Text组件本身尺寸太小了,因为它默认会自动匹配文本的尺寸。
为了演示,这里在一个Column组件内垂直排列2个尺寸不同的Text组件,却同时将它们的对齐方式设置为“末尾对齐”,代码如下:
运行效果如图2-11所示。由于Text组件的尺寸不同,上面那个Text组件没有足够的空间调整内部文本的对齐方式,因此并不会展示出末尾对齐的视觉效果。
图2-11 当Text尺寸足够时才有文本对齐效果
实战中,对于短小的文字而言,虽然可以轻易地借助SizedBox等组件设置Text组件的尺寸,或者通过Column的CrossAxisAlignment.stretch等方式约束Text组件宽度,以达到可见的文本对齐效果,但通常更方便的做法是通过Center或Align等组件,或者Contrainer组件的alignment属性等方式,直接指定Text组件在父级组件中的摆放位置,以达到将文本居中或者靠右对齐的视觉效果。
4. maxLines和softWrap
当文本较长时,Text组件默认会将文本内容自动分成多行显示。如有需要,可通过softWrap控制是否允许多行显示。例如,传入softWrap:false就可以关闭自动断行功能,这样无论文本有多长,都会显示出长长的一行。
同时,maxLines属性可以设置Text组件最多可以显示几行,类型为整数。如maxLines:3表示Text组件最多只能渲染3行,若文本过长,则超出的部分将按照overflow参数所设置的方式处理。
5.overflow
当文本较长,且不需要渲染全部内容时,可用overflow属性设置超出的部分该如何处理。可传入的参数为TextOverflow类型,并提供了clip(裁剪)、fade(渐淡)、ellipsis(省略号)、visible(可见)这4种选择。
图2-12由上到下依次展示了同一个较长的文本的多行显示情况。后3个Text组件由maxLines参数设置了最多显示2行后,又依次设置了fade、ellipsis和clip这3种溢出TextOverflow值。
图2-12 文本溢出时的渲染效果对比
用于实现图2-12效果的完整代码如下:
这里值得一提的是,当overflow参数被设置为TextOverflow.ellipsis(省略号)时,即使没有特别传入maxLines规定的最大行数,也没有关闭softWrap自动换行,Text组件默认仍然只会显示一行,而不会自动换行。
实战中,除了overflow参数以外,另一种常见的解决文字溢出的方式是缩小文字的字号。例如,这里讨论的文字溢出的处理(如渐淡或显示省略号等)通常只适用于大篇幅的文字,如产品介绍等,但并不是所有文字都适合裁剪或显示为省略号。例如“用户偏好与系统设置”这几个字若因部分设备屏幕太小而显示不下,也并不适合渲染为“用户偏好与系……”。对于这些情况,通常更好的解决方法是在小尺寸的设备上显示较小的字号。在Flutter框架中,如果屏幕空间不足,则自动缩小尺寸的最简单的方法是直接使用FittedBox组件,对此不熟悉的读者可查阅第6章有关FittedBox组件的介绍。
6.textScaleFactor
文字缩放系数,可以接收一个小数类型,并在Text组件渲染文本时将style参数里设置的字号数值(或默认的字号数值14)与该系数相乘。例如,当字号为14号时,传入缩放系数2.0即可将字号放大至两倍,与直接设置字号28号效果一致,而传入缩放系数0.5即可将字号缩小至原来的一半,效果与直接将字号设置为7号相同。
这里需要注意的是,textScaleFactor的默认值并不总是1.0(无缩放效果)。恰恰相反,很多用户(尤其是中老年手机用户)会在设备的操作系统层面选择放大所有文字,以方便阅读。假设用户在安卓或苹果系统中设置了将全局文字放大至150%,则Flutter中所有Text组件的textScaleFactor默认值都会是1.5,因此在开发过程中需要注意,不能借助这个textScaleFactor参数做出部分文字的放大效果。例如,若开发人员选择将某布局的标题文字缩放系数改成1.2,期待标题比正文的字号大20%的效果,但当遇到某些用户已经设置了将全局文字放大至150%时,这里标题的放大1.2倍反而会小于正文默认的放大1.5倍,显示效果适得其反。
若实战中遇到某部分布局需要固定文字大小(无视用户在系统层面设置的全局文字缩放),则这里可以将textScaleFactor设置为1.0,以统一不同设备和用户设置下的显示效果,但这么做之前一定要斟酌部分视力不佳的用户,以及超大屏设备(如电视)上的用户体验。
若需要获得用户在系统层面设置的缩放比例,则可以借助MediaQuery组件查询。具体用法读者可参考第6章“进阶布局”中关于MediaQuery的相关内容。
7.semanticsLabel
语义标签是辅助功能的一部分,用来协助第三方软件为有障碍人士提供无障碍功能。例如,盲人一般会通过某些软件将屏幕上的内容朗读出来,而这里的语义标签就可以帮助屏幕朗读软件,以提供更友好的用户体验。用法非常简单,代码如下:
Text("$5.00",semanticsLabel: "五元整")
一般屏幕朗读软件会直接读出Text组件的文本内容,但若semanticsLabel不为空,则它们会选择使用这里的值替代原来的文本,直接朗读语义标签的内容,因此在上例中,朗读软件应直接读“五元整”而不是“人民币五点零零”或依赖其智能计算出其他读法。