Flet CupertinoNavigationBar 详解:用 Python 构建 iOS 风格底部导航栏
【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet
导读
CupertinoNavigationBar是 Flet 中一套 iOS 风格的底部导航栏控件,用于在应用底部提供持久、便捷的主目的地切换入口。本指南以官方文档为主线,结合仓库中的 Python 控件实现、Flutter 端渲染源码与集成测试,完整讲解该控件的属性、使用示例、事件处理与常见问题,帮助你用纯 Python 在桌面、移动端和 Web 应用中还原 iOS 原生观感的底部 Tab 栏。
1. 控件概述
CupertinoNavigationBar继承自LayoutControl,在 Python 端声明为flet.CupertinoNavigationBar(见 cupertino_navigation_bar.py)。它用于替代 Material 风格的NavigationBar,在需要打造 iOS 观感(如 Cupertino 主题应用)时,为页面底部提供一组可切换的导航目的地。
从 Flutter 端看,该控件最终渲染为 Flutter 的CupertinoTabBar(见 cupertino_navigation_bar.dart),因此其外观与交互行为与 iOS 原生 Tab Bar 高度一致。默认情况下,它通过page.navigation_bar属性挂载到页面底部(见 base_page.py),也可作为View或Pagelet的navigation_bar使用(见 view.py 与 pagelet.py)。
2. 基础示例
官方文档的第一个示例(完整代码位于 cupertino_navigation_bar/main.py)演示了最小可运行用法:
import flet as ft def main(page: ft.Page): page.title = "CupertinoNavigationBar Example" page.navigation_bar = ft.CupertinoNavigationBar( bgcolor=ft.Colors.AMBER_100, inactive_color=ft.Colors.GREY, active_color=ft.Colors.BLACK, on_change=lambda e: print("Selected tab:", e.control.selected_index), destinations=[ ft.NavigationBarDestination( icon=ft.Icons.EXPLORE_OUTLINED, selected_icon=ft.Icons.EXPLORE, label="Explore", ), ft.NavigationBarDestination( icon=ft.Icons.COMMUTE_OUTLINED, selected_icon=ft.Icons.COMMUTE, label="Commute", ), ft.NavigationBarDestination( icon=ft.Icons.BOOKMARK_BORDER, selected_icon=ft.Icons.BOOKMARK, label="Favorites", ), ], ) page.add( ft.SafeArea( content=ft.Text("Body!"), ) ) if __name__ == "__main__": ft.run(main)要点拆解:
- 通过
page.navigation_bar = ft.CupertinoNavigationBar(...)将导航栏挂到页面底部,无需手动布局; destinations至少需要2 个可见的NavigationBarDestination,否则会抛出ValueError(见下方校验逻辑);ft.NavigationBarDestination同时被 MaterialNavigationBar与 Cupertino 导航栏复用,二者共享同一套目的地定义(见 navigation_bar.py);- 内容区用
ft.SafeArea包裹,避免内容被底部导航栏遮挡。
下图是该示例的运行效果:
3. 交互式示例:切换页面内容
官方文档的第二个示例(完整代码位于 wired/main.py)演示了如何在点击导航项时更新页面内容:
import flet as ft def main(page: ft.Page): page.title = "CupertinoNavigationBar Example" body_text = ft.Text("Explore!") def handle_nav_destination_change(e: ft.Event[ft.CupertinoNavigationBar]): if e.control.selected_index == 0: body_text.value = "Explore!" elif e.control.selected_index == 1: body_text.value = "Find Your Way!" else: body_text.value = "Your Favorites!" page.navigation_bar = ft.CupertinoNavigationBar( bgcolor=ft.Colors.AMBER_100, inactive_color=ft.Colors.GREY, active_color=ft.Colors.BLACK, on_change=handle_nav_destination_change, destinations=[ ft.NavigationBarDestination( icon=ft.Icons.EXPLORE_OUTLINED, selected_icon=ft.Icons.EXPLORE, label="Explore", ), ft.NavigationBarDestination( icon=ft.Icons.COMMUTE_OUTLINED, selected_icon=ft.Icons.COMMUTE, label="Commute", ), ft.NavigationBarDestination( icon=ft.Icons.BOOKMARK_BORDER, selected_icon=ft.Icons.BOOKMARK, label="Favorites", ), ], ) page.add( ft.SafeArea( content=body_text, ) ) if __name__ == "__main__": ft.run(main)关键点:
on_change回调接收一个类型为ft.Event[ft.CupertinoNavigationBar]的事件对象;- 通过
e.control.selected_index读取当前选中项的索引(0 起),据此更新body_text的内容; - 注意
on_change在事件触发时同步回写selected_index,因此事件处理器中读到的selected_index就是刚被点击的项。
4. 核心属性速查
CupertinoNavigationBar的全部可配置属性,均可在 cupertino_navigation_bar.py 中查看,汇总如下:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
destinations | list[NavigationBarDestination] | 无(必填) | 导航目的地列表,至少 2 个可见项,否则抛ValueError |
selected_index | int | 0 | 当前选中项在destinations中的索引;越界会抛IndexError |
bgcolor | ColorValue | None | 导航栏自身的背景色 |
active_color | ColorValue | None | 选中项的图标与文字前景色 |
inactive_color | ColorValue | CupertinoColors.INACTIVE_GRAY | 未选中项的图标与文字前景色 |
border | Border | None | 导航栏的边框定义 |
icon_size | Number | 30 | 所有目的地图标的尺寸(单位:逻辑像素) |
on_change | ControlEventHandler | None | 选中目的地改变时触发 |
4.1 destinations 与 NavigationBarDestination
destinations中的每一项都是一个NavigationBarDestination(见 navigation_bar.py),它支持以下属性:
icon:必填。目的地的图标名称(如ft.Icons.EXPLORE)或一个控件(如ft.Icon(ft.Icons.BOOKMARK))。未选中时展示该图标;selected_icon:可选。选中时展示的替代图标。若未提供,则选中与未选中都显示icon。官方建议为可访问性选用“描边/填充”成对的图标——icon用描边版、selected_icon用填充版(例如ft.Icons.CLOUD与ft.Icons.CLOUD_QUEUE);label:可选。显示在图标下方的文字;bgcolor:可选。该目的地自身的背景色。
4.2 默认颜色
inactive_color的默认值来自CupertinoColors.INACTIVE_GRAY(即 Flutter 侧的CupertinoColors.inactiveGray),定义于 cupertino_colors.py。在 Flutter 端,若active_color未设置,会回退使用 Material 导航栏的indicator_color(见 cupertino_navigation_bar.dart),从而兼容自适应(adaptive)场景。
4.3 校验规则
Python 端的before_update会执行两项关键校验(见 cupertino_navigation_bar.py):
destinations中可见项少于 2 个时抛出ValueError;selected_index不在[0, 可见目的地数量 - 1]区间内时抛出IndexError,错误信息会明确提示取值范围。
这两项校验在控件更新(before_update)阶段执行,保证推送到前端的状态始终合法。
5. 底层渲染原理
了解底层实现有助于排查样式与事件问题。Flutter 端控件CupertinoNavigationBarControl(见 cupertino_navigation_bar.dart)的关键逻辑:
- 属性映射:
bgcolor、active_color、inactive_color、icon_size、border分别映射到CupertinoTabBar的对应参数;其中inactive_color在 Python 侧未显式赋值时,以CupertinoColors.inactiveGray兜底; - 图标与标签:每个
NavigationBarDestination被转换为BottomNavigationBarItem,icon/selected_icon通过buildIconOrWidget构建——这意味着图标既可以是内置ft.Icons名称,也可以是任意控件;label缺省时为空字符串; - 事件处理:点击项时,
_onTap先将新索引写回控件属性(selected_index),再触发change事件(见 cupertino_navigation_bar.dart),最终在 Python 端回调on_change。因此事件处理器中e.control.selected_index始终是最新值; - 禁用态:当控件
disabled=True时,onTap置空,导航栏整体不可点击。
6. 在真实项目中的验证:集成测试与截图
仓库中包含针对该控件的自动化测试(见 test_cupertino_navigation_bar.py),其构造参数与官方示例完全一致(bgcolor=AMBER_100、inactive_color=GREY、active_color=BLACK以及三个目的地),并通过assert_control_screenshot进行像素级截图比对。
该测试对应的基准截图存放在:
- macOS 平台:
sdk/python/packages/flet/integration_tests/controls/cupertino/golden/macos/cupertino_navigation_bar/cupertino_navigation_bar.png
此外,sdk/python/examples/controls/cupertino/cupertino_navigation_bar/media/目录下还提供了示例效果图:
basic.png:基础示例运行效果(见上文插图);adaptive.png:自适应(adaptive)变体效果图:
说明:
adaptive.png演示的是将 Cupertino 导航栏与自适应控件搭配时的观感——选中项使用主题强调色(蓝)、未选中项为灰色,图标更简约。它展示的不是新属性,而是导航栏在不同主题下的视觉反馈。
7. 进阶技巧与常见问题
7.1 多视图中的导航栏
CupertinoNavigationBar不仅可用于page.navigation_bar,还可以作为View.navigation_bar或Pagelet.navigation_bar的属性使用(见 view.py、pagelet.py)。这意味着可以在不同路由/视图中挂载不同的导航栏与目的地集合。
7.2 让图标随选中状态切换
参考基础示例,为每个目的地同时提供icon(描边版)与selected_icon(填充版)即可获得 iOS 原生的“选中变粗/填充”反馈。若省略selected_icon,两端显示同一图标,仅靠active_color/inactive_color区分状态。
7.3 保持选中索引一致
由于点击时 Python 端与 Flutter 端都会维护selected_index,当你在on_change中根据索引更新页面内容时,建议同时将selected_index作为状态同步到自己的业务模型中,避免在页面重建时导航栏选中态与内容不一致。
7.4 常见报错与对策
| 报错 | 原因 | 对策 |
|---|---|---|
ValueError:destinations 可见项少于 2 | destinations不足两项或全部被visible=False隐藏 | 保证至少提供 2 个可见NavigationBarDestination |
IndexError:selected_index越界 | selected_index超出[0, 可见数-1] | 将selected_index修正到合法范围后再更新控件 |
8. 总结
CupertinoNavigationBar让 Flet 开发者无需编写任何 Dart/原生代码,即可在 Python 侧获得与 iOS 原生一致的底部导航体验。围绕它你应当掌握:
- 通过
page.navigation_bar挂载,配合ft.NavigationBarDestination定义目的地; - 使用
bgcolor/active_color/inactive_color/icon_size/border定制外观; - 通过
on_change事件 +selected_index驱动页面内容切换; - 遵守“至少 2 个可见目的地”“selected_index 必须合法”的校验约束。
如需查看更多 Flet 控件文档,可继续阅读仓库 website/docs/controls 目录下的其他控制组件文档,例如同属 Cupertino 系列的 CupertinoAppBar。
【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考