- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
PageMediaData 是 Flet 框架中描述"页面/窗口当前环境度量"的核心数据类型,封装了系统安全区、键盘避让区、设备像素比、屏幕方向与 24 小时制偏好等关键信息。本文将以 pagemediadata.md 关联的flet.PageMediaData为主线,结合 Python SDK 源码 与 Flutter 客户端实现,带你掌握如何通过page.media与page.on_media_change实现真正的响应式布局、键盘避让与刘海屏适配。
一、PageMediaData 是什么
根据 base_page.py 中的类定义,PageMediaData是一个数据类(@dataclass),"表示页面或窗口的环境度量(environmental metrics)"。它的核心特征是:
- 自动更新:每当平台窗口或布局发生变化时(旋转设备、缩放浏览器窗口、系统 UI 元素如键盘或安全区变化),该数据都会被刷新;
- 只读语义:数据由客户端(Flutter 端)采集并推送给 Python 端,业务代码应读取而非主动修改;
- 页面级属性:它通过
BasePage.media属性暴露给Page与MultiView,是所有顶层视图共享的环境信息。
# sdk/python/packages/flet/src/flet/controls/base_page.py(节选) @dataclass class PageMediaData: padding: Padding view_padding: Padding view_insets: Padding device_pixel_ratio: float orientation: Orientation always_use_24_hour_format: bool = False二、六个字段逐一拆解
PageMediaData包含 6 个字段,覆盖了响应式布局所需的全部环境信息。
1. padding:安全区外边距
The space surrounding the entire display, accounting for system UI like notches and status bars.
padding: Padding表示整个显示区域周围、被系统 UI(如刘海、状态栏、手势条)占据的空间。在带刘海屏的手机上,padding.top通常对应状态栏高度;在桌面窗口上则通常为零。它适合用来给页面内容预留"不被系统 UI 遮挡"的边距。
2. view_padding:始终预留的填充
Similar to
padding, but includes padding that is always reserved (even when the system UI is hidden).
view_padding: Padding与padding相似,但它包含"即使系统 UI 隐藏也始终预留"的部分。典型差异在于:某些设备在横屏或沉浸模式下会收起状态栏,此时padding会变小,而view_padding仍会保留系统认为需要预留的空间。
3. view_insets:被系统覆盖的区域
Areas obscured by system UI overlays, such as the on-screen keyboard or system gesture areas.
view_insets: Padding表示被系统 UI 浮层遮挡的区域,最常见的场景是软键盘弹出:当键盘弹起时,view_insets.bottom会变为键盘高度,用于把输入框顶出键盘区域。这是实现"聊天输入框随键盘上移"等交互的关键字段。
4. device_pixel_ratio:设备像素比
The number of device pixels for each logical pixel.
device_pixel_ratio: float表示每个逻辑像素(logical pixel)对应的物理像素(device pixel)数量。Retina 屏通常为 2.0 或 3.0,普通屏为 1.0。Flet 的尺寸单位基于逻辑像素,需要生成位图、计算物理尺寸或判断屏幕清晰度时可用此值换算。
5. orientation:页面方向
The orientation of the page.
orientation: Orientation为枚举类型,取值Orientation.PORTRAIT(竖屏)或Orientation.LANDSCAPE(横屏)。它是实现"横竖屏不同布局"最直接的条件依据。
6. always_use_24_hour_format:时间格式偏好
Whether to use 24-hour format when formatting time.
always_use_24_hour_format: bool(默认False)指示系统是否偏好 24 小时制。源码 docstring 特别注明了跨平台差异:
- Android:直接来自用户设置"Use 24-hour format",无论应用使用系统 locale 还是自定义 locale 都生效;
- iOS:当用户开启"24-Hour Time"设置,或系统 locale 默认使用 24 小时制时,该值为
True。
因此它不能简单视为"用户选择",而应视为"当前环境推荐的时间显示方式",用于自行格式化时间时保持一致。
三、在业务代码中使用 page.media 与 on_media_change
PageMediaData通过两个入口暴露给开发者,二者均定义在 BasePage:
| 入口 | 类型 | 说明 |
|---|---|---|
page.media | PageMediaData | 当前的环境度量快照,可随时读取 |
page.on_media_change | EventHandler[PageMediaData] | 媒体数据变化时触发的事件回调 |
注意media字段的初始默认值(base_page.py):padding/view_padding/view_insets全部为零、device_pixel_ratio=0、orientation=PORTRAIT。也就是说,在客户端第一次推送真实媒体数据之前,page.media是占位默认值,真实数据通常会在应用启动后很快到达。
响应式布局示例
import flet as ft def main(page: ft.Page): def on_media_change(e: ft.PageMediaData): # e 即最新的 PageMediaData status = f"方向: {page.media.orientation.name}, " status += f"安全区上边距: {page.media.padding.top}, " status += f"键盘遮挡高度: {page.media.view_insets.bottom}, " status += f"像素比: {page.media.device_pixel_ratio}, " status += f"24小时制: {page.media.always_use_24_hour_format}" status_text.value = status # 根据方向切换布局 column.controls = [ ft.Text("横屏模式" if page.media.orientation == ft.Orientation.LANDSCAPE else "竖屏模式"), status_text, ] page.update() page.on_media_change = on_media_change status_text = ft.Text("等待媒体数据...") column = ft.Column([status_text]) page.add(column) ft.app(main)事件对象e本身携带最新数据,同时page.media也会同步更新,二者在回调内是一致的。
键盘避让示例
利用view_insets.bottom判断软键盘是否弹出,并主动滚动输入框:
def on_media_change(e): if page.media.view_insets.bottom > 0: # 键盘已弹出,向上滚动列表让输入框可见 list_view.scroll_to(offset=page.media.view_insets.bottom, duration=200) page.update()四、底层原理:Flutter MediaQuery 的完整映射链路
PageMediaData并非凭空产生的,它在 Flutter 客户端由MediaQuery采集,再通过消息通道同步给 Python 端。整条链路可以在仓库 Dart 代码中完整还原。
1. 数据模型与序列化
Dart 侧的 page_media_data.dart 定义了同名PageMediaData类,其字段与 Python 端一一对应,并通过toMap()序列化为 Flet 协议消息:
Map<String, dynamic> toMap() => <String, dynamic>{ 'padding': padding.toMap(), 'view_padding': viewPadding.toMap(), 'view_insets': viewInsets.toMap(), 'device_pixel_ratio': devicePixelRatio, 'orientation': orientation.name, 'always_use_24_hour_format': alwaysUse24HourFormat, };其中PaddingData将 Flutter 的EdgeInsets拆解为top/right/bottom/left四个数值(page_media_data.dart),与 Python 端Padding结构一致。
2. 采集:PageMedia 组件
page_media.dart 中的PageMedia组件在每次帧回调中读取MediaQuery的六个维度,构造PageMediaData:
var newMedia = PageMediaData( padding: PaddingData(MediaQuery.paddingOf(context)), viewPadding: PaddingData(MediaQuery.viewPaddingOf(context)), viewInsets: PaddingData(MediaQuery.viewInsetsOf(context)), devicePixelRatio: MediaQuery.devicePixelRatioOf(context), orientation: MediaQuery.orientationOf(context), alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context), );可以看到,Python 端PageMediaData的 6 个字段正是 FlutterMediaQuery中 6 个对应 API 的直接映射,这也是 Flutter 生态中MediaQuery响应式能力的 Flet 化封装。
3. 推送:updateMedia 与 media_change 事件
采集到新数据后,flet_backend.dart 的updateMedia负责将其同步到 Python 端:
void updateMedia(PageMediaData newMedia, {Control? view}) { media = newMedia; updateControl(ctrl.id, {"media": newMedia.toMap()}); // Initial page media should only hydrate Python state. Later media changes // must reach Python even if no explicit "on_media_change" handler is set. triggerControlEventById(ctrl.id, "media_change", newMedia.toMap()); }这段源码透露了两个重要细节:
- 页面大小的变化会先经过 100ms 防抖(page_media.dart 中的
Debouncer),避免窗口拖动时高频推送; - 首次媒体数据只用于"水合"(hydrate)Python 端的状态,之后的变化即使没有注册
on_media_change处理器也会推送(triggerControlEventById无条件调用),确保page.media始终保持最新。
五、应用场景与实战建议
结合字段语义与事件机制,PageMediaData最常见的应用场景包括:
- 横竖屏响应式布局:监听
orientation,在PORTRAIT/LANDSCAPE间切换列数、隐藏/显示侧栏; - 刘海屏与状态栏适配:用
padding给 AppBar 下方内容预留安全区空间; - 软键盘避让:用
view_insets.bottom判断键盘状态并调整布局或滚动位置; - 高分屏适配:用
device_pixel_ratio决定是否加载更高分辨率资源; - 时间本地化:用
always_use_24_hour_format决定HH:mm还是h:mm a的显示格式。
几个值得注意的实践要点:
- 不要在
on_media_change里做重活:窗口调整、键盘弹收等操作可能高频触发回调,建议内部做节流或只做轻量属性更新,最后统一page.update(); - 区分
padding与view_padding:横屏沉浸模式下前者会减小,后者仍保留系统预留,二者差别正是"动态安全区"与"固定预留"的区别; - 桌面端字段多为零值:在桌面窗口上,键盘与安全区通常不存在,
view_insets与padding多为0,不要依赖这些字段判断桌面端布局; - 优先使用
page.media而非自定义变量:框架已保证page.media始终是最新状态,避免自己维护一份可能过期的副本。
六、测试与验证
仓库测试对PageMediaData的解析与同步行为有直接覆盖,可作为理解其数据流的参考:
- test_patch_dataclass.py:验证
page.media被解析为PageMediaData实例,且padding、view_padding、view_insets均为Padding类型;同时验证了初始消息中media的序列化结构({"padding": {...}, "view_padding": {...}, "view_insets": {...}}); - test_from_dict.py:验证
PageMediaData能正确从字典数据还原为强类型对象。
七、小结
PageMediaData是 Flet 暴露给 Python 开发者的"环境感知层":它将 FlutterMediaQuery的能力以数据类形式带到 Python 侧,让开发者无需触碰原生代码即可感知安全区、键盘、屏幕方向、像素比与时间格式偏好。配合page.on_media_change事件,一套代码即可优雅适配手机、平板与桌面窗口的千变万化——这正是 Flet "纯 Python 构建实时跨端应用"理念在响应式布局层面的具体落地。
想进一步深入,可以继续阅读:
- PageMediaData 官方类型文档
- Python 端定义与默认值
- Dart 端数据模型与序列化
- Dart 端采集与推送实现
- 媒体数据同步测试
- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
相关推荐
hass-config布局卡片深度解析:grid-layout与响应式媒体查询
hass config布局卡片深度解析:grid layout与响应式媒体查询 Home Assistant作为智能家居的核心平台,其界面设计直接影响用户体验。
物联网Quartz 5 页面布局完全指南:从 Section 槽位、Page Frame 到响应式断点的深度解析
Quartz 5 页面布局完全指南:从 Section 槽位、Page Frame 到响应式断点的深度解析 Quartz 是一个将 Markdown 内容转换为
前端开发工具CLIFileBrowser Quantum布局系统:响应式页面布局设计
FileBrowser Quantum布局系统:响应式页面布局设计 FileBrowser Quantum作为一款现代化的自托管文件管理器,其布局系统采用了先进
后端存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考