- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
TextSpan是 Flet 中用于在Text控件内部构建富文本(Rich Text)的核心类型,它允许你在同一段文本中混排不同字体样式、添加下划线/删除线等装饰、嵌入可点击的超链接,甚至为每个文本片段注册鼠标悬停与点击事件。阅读本文后,你将掌握TextSpan的全部属性语义、嵌套规则、事件回调用法,以及它在 Flet 前端(Flutter)渲染层中的真实实现原理,可以直接在你的 Flet 应用中实现"富文本段落 + 可点击链接 + 交互式高亮"等实战效果。
本文所有属性与行为说明均以当前仓库 text_span.py 的源码定义为准,示例取自仓库官方示例 rich_text/main.py,底层渲染逻辑对照 text.dart 中的parseInlineSpan实现。
TextSpan 是什么:富文本的最小单元
TextSpan表示一段带有独立样式的文本片段(a text span),它的典型用法是作为flet.Text.spans列表的子元素出现。在 Flet 中,Text控件除了直接设置value显示纯文本外,还可以通过spans参数挂载一个或多个TextSpan,从而在一行文本内部实现"一段加粗、一段斜体、一段可点击"的混排效果。
从源码看,TextSpan继承自ActionControl(具备交互能力的基类),通过@control("TextSpan")装饰器注册为 Flet 控件体系中的一员,并使用 Flet 1.x 声明式语法定义属性。源码注释特别强调了一个重要前提:要让TextSpan对象真正有用,text和spans两个属性至少要设置其中一个。
import flet as ft def main(page: ft.Page): page.add( ft.Text( spans=[ ft.TextSpan( text="这里是斜体", style=ft.TextStyle(italic=True, color=ft.Colors.GREEN), ), ft.TextSpan( text="这里是加粗", style=ft.TextStyle(weight=ft.FontWeight.BOLD), ), ] ) ) ft.run(main)核心属性:text、style、spans 与 url
TextSpan的属性可以分为"内容与样式"和"交互与语义"两大类。先看最核心的四个。
text:片段文本内容
text: Optional[str]是该片段包含的文本字符串。源码中有一条值得注意的优先级规则:
如果同时定义了
text和spans,text优先。
也就是说,当你同时设置text与子spans时,text会成为展示内容,而spans中的子片段会被忽略。因此设计富文本结构时,要么用text承载叶子节点文本,要么用spans嵌套子片段,避免两者同时存在造成歧义。
style:片段样式
style: Optional[TextStyle]定义该文本片段的样式,其类型为TextStyle。TextStyle支持 Flet 中完整的文本样式体系,包括但不限于:
- 基础排版:
size(字号)、weight(字重,如FontWeight.BOLD)、italic(斜体)、font_family(字体族)、height(行高)、letter_spacing(字间距)、word_spacing(词间距); - 颜色类:
color(文字颜色)、bgcolor(背景色); - 装饰类:
decoration(下划线/删除线等,支持位或组合)、decoration_color(装饰线颜色)、decoration_style(装饰线风格,如 WAVY 波浪线)、decoration_thickness(装饰线粗细); - 高级效果:
shadow(阴影)、foreground(前景画笔,可绘制渐变或描边文字)。
例如官方示例 rich_text/main.py 中展示的波浪下划线:
ft.TextSpan( text="underlined red wavy", style=ft.TextStyle( decoration=ft.TextDecoration.UNDERLINE, decoration_color=ft.Colors.RED, decoration_style=ft.TextDecorationStyle.WAVY, ), )多个装饰线可以通过位或(|)组合,例如同时加上划线和下划线:
ft.TextSpan( text="overlined and underlined", style=ft.TextStyle( decoration=ft.TextDecoration.OVERLINE | ft.TextDecoration.UNDERLINE ), )spans:嵌套子片段
spans: Optional[list["TextSpan"]]允许TextSpan无限层级嵌套。这带来一个非常实用的能力:子片段只需声明与父片段不同的样式,其余样式自动继承父片段。官方示例展示了三层嵌套的典型结构:
ft.TextSpan( text="here goes italic", style=ft.TextStyle(italic=True, size=20, color=ft.Colors.GREEN), spans=[ ft.TextSpan( text="bold and italic", style=ft.TextStyle(weight=ft.FontWeight.BOLD), ), ft.TextSpan( text="just italic", spans=[ ft.TextSpan("smaller italic", ft.TextStyle(size=15)), ], ), ], )在这个例子中,外层片段是绿色斜体 20 号字,其下的"bold and italic"只需额外声明weight=BOLD,斜体与颜色自动继承;第三层的"smaller italic"只需声明size=15,同样继承外层的斜体样式。这种"差异式声明"让富文本的样式维护变得极其简洁。
url:让片段成为超链接
url: Optional[Union[str, Url]]设置后,点击该片段会在浏览器中打开对应 URL。源码注释补充了一个细节:如果同时提供了on_click事件回调,则先打开 URL,再触发回调。
配合on_enter/on_exit实现"链接悬停高亮"是官方示例中的经典用法:
def handle_link_highlight(e: ft.Event[ft.TextSpan]): e.control.style.color = ft.Colors.BLUE e.control.update() def handle_link_unhighlight(e: ft.Event[ft.TextSpan]): e.control.style.color = None e.control.update() page.add( ft.Text( disabled=False, spans=[ ft.TextSpan( text="Go to Google", style=ft.TextStyle(decoration=ft.TextDecoration.UNDERLINE), url="https://google.com", on_enter=handle_link_highlight, on_exit=handle_link_unhighlight, ) ], ), )注意回调参数e: ft.Event[ft.TextSpan]中的e.control就是触发事件的TextSpan实例,可以像普通控件一样修改其属性并调用update()推送刷新。
事件回调:click、enter 与 exit
TextSpan继承ActionControl,支持三个鼠标交互事件:
| 属性 | 类型 | 触发时机 |
|---|---|---|
on_click | ControlEventHandler["TextSpan"] | 点击该片段时 |
on_enter | ControlEventHandler["TextSpan"] | 鼠标指针进入该片段区域时 |
on_exit | ControlEventHandler["TextSpan"] | 鼠标指针离开该片段区域时 |
官方示例 rich_text/main.py 中直接使用 lambda 演示三个事件的触发:
ft.TextSpan( text="underlined and clickable", style=ft.TextStyle(decoration=ft.TextDecoration.UNDERLINE), on_click=lambda e: print(f"Clicked span: {e.control}"), on_enter=lambda e: print(f"Entered span: {e.control}"), on_exit=lambda e: print(f"Exited span: {e.control}"), )结合前文,on_enter/on_exit常被用来实现悬停视觉反馈(例如链接变蓝),on_click则可以用来响应点击并携带事件数据。从渲染层看,只有注册了事件回调(或设置了url)的片段才会挂载TapGestureRecognizer手势识别器和SystemMouseCursors.click手型光标,未注册事件的普通文本片段不会产生任何交互开销(详见下文 Dart 实现)。
无障碍语义:semantics_label 与 spell_out
TextSpan为屏幕阅读器(如 iOS 的 VoiceOver、Android 的 TalkBack)提供了两个辅助属性,体现了 Flet 对无障碍(accessibility)的支持。
semantics_label:替代朗读文本
s semantics_label: Optional[str]设置后,辅助技术的朗读内容将使用该值而不是片段实际文本。适合用于"屏幕上显示图标/缩写,但读屏时朗读完整语义"的场景。
需要注意的是,源码中的校验规则(__validation_rules__)明确要求:semantics_label只能在text非空时设置,否则抛出ValueError(校验消息为 "semantics_label can be set only when text is not None")。
ft.TextSpan( text="5:00 PM", semantics_label="下午五点", )spell_out:逐字符朗读
spell_out: Optional[bool]控制辅助技术是否逐字符拼读文本。若文本是"hello world"且该属性为True,读屏软件会读成 "h-e-l-l-o-space-w-o-r-l-d" 而不是完整的单词。这非常适合验证码、密码等需要逐字符确认的场景。
该属性的继承规则比较特殊,源码注释给出了三层逻辑:
- 若当前片段包含子
TextSpan,子片段默认继承该属性,除非子片段显式覆盖; - 若当前片段未设置,则继承父片段的设置;
- 若既无父级设置、自身也未设置,则默认不逐字拼读。
实战进阶:渐变文字与描边文字
利用TextSpan.style.foreground配合Paint,可以让文字片段呈现纯色之外的视觉效果。仓库官方示例 rich_text_gradient/main.py 展示了线性渐变文字:
ft.Text( spans=[ ft.TextSpan( text="Greetings, planet!", style=ft.TextStyle( size=40, weight=ft.FontWeight.BOLD, foreground=ft.Paint( gradient=ft.PaintLinearGradient( begin=(0, 20), end=(150, 20), colors=[ft.Colors.RED, ft.Colors.YELLOW], ) ), ), ), ], )而 rich_text_border_stroke/main.py 则用PaintingStyle.STROKE实现了描边文字,并利用Stack叠放一层灰色实心文字制造立体感:
ft.Stack( controls=[ ft.Text( spans=[ ft.TextSpan( text="Greetings, planet!", style=ft.TextStyle( size=40, weight=ft.FontWeight.BOLD, foreground=ft.Paint( color=ft.Colors.BLUE_700, stroke_width=6, style=ft.PaintingStyle.STROKE, ), ), ), ], ), ft.Text( spans=[ ft.TextSpan( text="Greetings, planet!", style=ft.TextStyle( size=40, weight=ft.FontWeight.BOLD, color=ft.Colors.GREY_300, ), ), ], ), ] )渲染层原理:TextSpan 如何映射到 Flutter
理解 Flet 的架构有助于把握TextSpan的行为边界:Python 侧的TextSpan只是声明式的数据描述,真正渲染时由 Flet 的 Flutter 客户端将其转换为 Flutter 原生的TextSpan。这个转换逻辑位于 text.dart 的parseInlineSpan函数(L65-L107)。
TextSpan? parseInlineSpan(Control span, ThemeData theme, [void Function(Control, String, [dynamic eventData])? sendControlEvent, BuildContext? context]) { span.notifyParent = true; var onClick = span.hasEventHandler("click"); var url = span.getUrl("url"); return TextSpan( text: span.getString("text"), style: span.getTextStyle("style", theme), spellOut: span.getBool("spell_out"), semanticsLabel: span.getString("semantics_label"), children: parseTextSpans( span.children("spans"), theme, sendControlEvent, context), mouseCursor: onClick && !span.disabled && sendControlEvent != null ? SystemMouseCursors.click : null, recognizer: (onClick || span.hasControlActions) && !span.disabled && sendControlEvent != null ? (TapGestureRecognizer() ..onTap = () { if (url != null) openWebBrowser(url); if (context != null) { runClientActions(context, span.get("action")); } if (onClick) sendControlEvent(span, "click"); }) : null, onEnter: ..., onExit: ..., ); }从这段实现可以确认几个关键行为:
- 递归解析:
parseTextSpans对spans子控件递归调用parseInlineSpan,天然支持无限层级嵌套,与 Python 侧文档描述的嵌套能力一致; - 点击流程:
TapGestureRecognizer的onTap依次执行三件事——打开url(若存在)、运行客户端action(若存在且处于 widget 树中)、向 Python 端发送click事件,这与TextSpan.url的文档注释"先开链接再触发回调"完全吻合; - 条件注册:只有当存在
click事件处理器、客户端 action 或url时才挂载手势识别器与手型光标,on_enter/on_exit也只在注册了对应处理器时才绑定,未设置任何交互的纯文本片段零交互开销; - 样式映射:
style属性由parseTextStyle解析为 FlutterTextStyle,支持字重、斜体、字体族、装饰组合(parseTextDecorations通过位掩码解码decoration值)、渐变/描边前景(parsePaint)等,与 Python 侧TextStyle属性一一对应。
使用注意事项小结
- 内容二选一:
text与spans同时设置时text优先,设计富文本树时让叶子用text、中间节点用spans嵌套; - 事件回调的
e.control:在on_click/on_enter/on_exit中修改样式后记得调用e.control.update()刷新界面; semantics_label前置条件:只能在text非空时设置,否则运行期抛出ValueError;- 超链接体验:只设置
url不会自动带下划线,建议配合decoration=TextDecoration.UNDERLINE并利用on_enter/on_exit做悬停反馈,模拟常规链接交互; - 渲染与性能:无交互属性的纯文本片段在 Flutter 端不注册手势识别器,富文本场景可以放心使用嵌套结构,样式差异越小、需要显式声明的属性越少。
围绕TextSpan的这些能力,配合 Text 文档页与 rich_text、rich_text_gradient、rich_text_border_stroke 三个官方示例,你可以在纯 Python 环境下构建出接近原生富文本控件体验的界面——从简单的多风格混排,到带悬停高亮的超链接、渐变标题、描边艺术字,TextSpan都是其中的基石。
- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
相关推荐
Flet Text 控件完全指南:样式、富文本与交互能力详解
Flet Text 控件完全指南:样式、富文本与交互能力详解 本文围绕 Flet 的核心控件 ft.Text 展开,结合官方控件文档、可运行的示例工程与底层 F
前端跨平台桌面应用移动开发iOS富文本链接交互:TTTAttributedLabel高级应用
iOS富文本链接交互:TTTAttributedLabel高级应用 你是否还在为UILabel无法实现文本链接交互而烦恼?是否希望应用中的文字能像网页一样点击跳
UI组件移动开发baloo性能优化:提升Go API测试效率的7个实用技巧
baloo性能优化:提升Go API测试效率的7个实用技巧 baloo是一款专为Go语言设计的HTTP API测试框架,以其简洁的语法和强大的断言能力深受开发者
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考