flet-cameraCameraStateEvent详解:相机状态事件字段、触发机制与实战用法
【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet
CameraStateEvent是 flet-camera 扩展包中用于承载相机控制器状态快照的核心事件类型。它通过Camera控件的on_state_change回调触发,把底层相机「是否已初始化、是否在录制、是否在拍照、闪光灯/曝光/对焦模式、预览尺寸、错误状态」等全量运行状态一次性同步给 Python 侧应用。读完本文,你将掌握该事件的全部字段语义、事件在 Python 与 Flutter 两端之间的触发链路,并能在自己的 Flet 相机应用中基于它构建状态驱动的 UI。
本文以 camerastateevent.md 这一 API 参考页为骨架,并结合 flet-camera 包源码与官方示例展开。该参考页由 Crocodocs 工具从 types.py 中
CameraStateEvent的 docstring 自动生成,因此字段定义以源码为准。
一、事件概览:CameraStateEvent 是什么
在 flet-camera 中,Camera控件(见 Camera 控件文档)负责相机预览、拍照、录像与图像流。相机控制器的状态是不断变化的:初始化完成、开始录像、暂停预览、切换对焦模式……这些变化需要一个统一的事件通道通知到 Python 应用,CameraStateEvent就是这条通道上的「状态快照信封」。
从源码看,CameraStateEvent是一个继承自ft.Event["Camera"]的 dataclass,事件源类型被标注为Camera:
@dataclass class CameraStateEvent(ft.Event["Camera"]): """Snapshot of the camera controller state.""" ...出处:types.py。
在Camera控件上通过on_state_change属性订阅该事件(camera.py):
on_state_change: Optional[ft.EventHandler[CameraStateEvent]] = None """Fires when the camera controller state changes."""事件本身不携带新旧状态对比,只携带「当前这一刻」的完整状态快照。应用侧需要自行保存上次状态并做差异判断(官方示例正是这么做的)。
二、字段全解析:21 个状态字段的语义与取值
CameraStateEvent包含 21 个字段,覆盖了相机运行时的全部关键状态。下表按「状态标志位 / 方向信息 / 模式与能力 / 预览与元数据 / 错误与设备信息」五组归类(字段定义与注释源自 types.py):
| 分组 | 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|---|
| 状态标志位 | is_initialized | bool | 必填 | 控制器是否已完成初始化 |
| 状态标志位 | is_recording_video | bool | 必填 | 是否正在进行视频录制 |
| 状态标志位 | is_recording_paused | bool | 必填 | 当前活动录制是否处于暂停状态 |
| 状态标志位 | is_taking_picture | bool | 必填 | 是否正在执行拍照(静态采集进行中) |
| 状态标志位 | is_streaming_images | bool | 必填 | 图像流式传输是否正在运行 |
| 状态标志位 | is_preview_paused | bool | 必填 | 预览是否已被手动暂停 |
| 状态标志位 | is_capture_orientation_locked | bool | 必填 | 采集方向是否已锁定 |
| 方向信息 | device_orientation | Optional[ft.DeviceOrientation] | None | 当前设备 UI 方向 |
| 方向信息 | locked_capture_orientation | Optional[ft.DeviceOrientation] | None | 锁定采集方向时使用的方向 |
| 方向信息 | recording_orientation | Optional[ft.DeviceOrientation] | None | 当前录制使用的方向 |
| 方向信息 | preview_pause_orientation | Optional[ft.DeviceOrientation] | None | 预览暂停时使用的方向 |
| 模式与能力 | flash_mode | Optional[FlashMode] | None | 当前闪光灯模式 |
| 模式与能力 | exposure_mode | Optional[ExposureMode] | None | 当前曝光模式 |
| 模式与能力 | focus_mode | Optional[FocusMode] | None | 当前对焦模式 |
| 模式与能力 | exposure_point_supported | Optional[bool] | None | 是否支持自定义曝光测光点 |
| 模式与能力 | focus_point_supported | Optional[bool] | None | 是否支持自定义对焦点 |
| 预览与元数据 | preview_size | Optional[CameraPreviewSize] | None | 预览尺寸(宽高,逻辑像素) |
| 预览与元数据 | aspect_ratio | Optional[ft.Number] | None | 预览宽高比 |
| 错误状态 | error_description | Optional[str] | None | 控制器出错时的错误描述 |
| 错误状态 | has_error | Optional[bool] | None | 控制器是否处于错误状态 |
| 设备信息 | description | Optional[CameraDescription] | None | 底层相机设备描述 |
关键字段说明
is_initialized/is_taking_picture/is_recording_*/is_streaming_images/is_preview_paused:这 6 个布尔标志位是驱动 UI 的核心。官方示例用它来启用/禁用拍照、录像、暂停、推流按钮,并同步显示「正在拍照…」「录制已暂停…」等状态文本(见下文实战章节)。- 方向相关 4 字段:
device_orientation是设备实时方向;locked_capture_orientation、recording_orientation、preview_pause_orientation则对应三个相互独立的方向锁定场景。例如在 Android 上锁定采集方向后,可通过locked_capture_orientation判断当前应使用的旋转角度。 - 模式与能力字段:
flash_mode、exposure_mode、focus_mode对应枚举FlashMode、ExposureMode、FocusMode(定义同在 types.py),例如FlashMode.OFF/AUTO/ALWAYS/TORCH、ExposureMode.AUTO/LOCKED、FocusMode.AUTO/LOCKED。exposure_point_supported、focus_point_supported则告知应用当前设备是否支持set_exposure_point()/set_focus_point()这类点按对焦/测光操作。 has_error+error_description:底层相机出现错误时,has_error为True,error_description携带人类可读的错误信息。官方示例在has_error为真时直接把error_description展示到状态文本中,这是相机应用中必须处理的兜底分支。description:携带与当前控制器绑定的CameraDescription(含name、lens_direction、lens_type、sensor_orientation)。官方示例利用它校验事件是否来自当前选中的相机,避免多个相机设备间的状态串扰。
三、触发机制:从 Flutter CameraValue 到 Python 事件
CameraStateEvent的字段并非凭空而来,而是直接映射自 Flutter 官方camera包中CameraController.value(类型为CameraValue)。flet-camera 在 Flutter 侧监听该 value 变化,再通过 Flet 的事件通道推送给 Python。
3.1 Flutter 侧:监听与序列化
在CameraControl的 State 中,控制器通过controller.addListener(_onControllerValueChanged)挂上监听(camera.dart):
void _onControllerValueChanged() { final controller = _controller; if (controller == null) { return; } if (widget.control.getBool("on_state_change", false)!) { widget.control .triggerEvent("state_change", cameraValueToMap(controller.value)); } if (mounted) { setState(() {}); } }关键点:
- 仅在 Python 侧订阅了
on_state_change时才触发事件(getBool("on_state_change", false)),没有监听者时零开销; - 事件名固定为
"state_change",对应 Python 侧属性on_state_change; - 无论是否触发事件,
setState都会执行,保证预览 UI 同步刷新。
序列化由cameraValueToMap()完成(utils/camera.dart),它把CameraValue的每个属性映射为与 Python 字段一一对应的键,并removeWhere((_, v) => v == null)剔除空值。例如:
Map<String, dynamic> cameraValueToMap(CameraValue value) { return { "is_initialized": value.isInitialized, "is_recording_video": value.isRecordingVideo, "is_recording_paused": value.isRecordingPaused, "is_taking_picture": value.isTakingPicture, "is_streaming_images": value.isStreamingImages, "is_preview_paused": value.isPreviewPaused, "is_capture_orientation_locked": value.isCaptureOrientationLocked, "locked_capture_orientation": value.lockedCaptureOrientation?.name, "recording_orientation": value.recordingOrientation?.name, "device_orientation": value.deviceOrientation.name, "flash_mode": value.flashMode.name, "exposure_mode": value.exposureMode.name, "focus_mode": value.focusMode.name, "exposure_point_supported": value.exposurePointSupported, "focus_point_supported": value.focusPointSupported, "preview_pause_orientation": value.previewPauseOrientation?.name, "preview_size": sizeToMap(value.previewSize), "aspect_ratio": value.previewSize != null ? value.aspectRatio : null, "error_description": value.errorDescription, "has_error": value.hasError, "description": cameraDescriptionToMap(value.description), }..removeWhere((_, v) => v == null); }可以看到preview_size被序列化为{"width": ..., "height": ...}字典,这正是 Python 侧CameraPreviewSize(@ft.value类)反序列化的输入;description则复用cameraDescriptionToMap()序列化为CameraDescription。方向、模式、闪光灯等枚举值统一以.name字符串传输。
3.2 Python 侧:反序列化为数据类
Python 侧事件处理由 Flet 框架将"state_change"事件载荷反序列化,自动构造CameraStateEvent实例并调用on_state_change回调。由于CameraStateEvent是@dataclass且字段名与载荷键一一对应,应用侧拿到的就是一个类型安全的ft.Event子类,可以直接通过属性访问,无需手动解析字典。
3.3 触发时机总结
从CameraController的语义可以推断,state_change事件在以下场景会被触发(这些操作在 camera.py 中均有对应方法,且 Flutter 侧均会改变CameraValue):
- 调用
initialize()完成初始化后(is_initialized变为True); - 调用
take_picture()、start_video_recording()/pause_video_recording()/resume_video_recording()/stop_video_recording()等拍摄操作前后; - 调用
start_image_stream()/stop_image_stream()切换图像流时(is_streaming_images变化); - 调用
pause_preview()/resume_preview()时(is_preview_paused变化); - 调用
lock_capture_orientation()/unlock_capture_orientation()以及设备旋转时(方向字段变化); - 调用
set_flash_mode()/set_exposure_mode()/set_focus_mode()等模式设置后; - 底层相机发生错误时(
has_error/error_description被填充)。
四、实战:基于 on_state_change 构建状态驱动相机 UI
仓库自带的官方示例 camera_playground/main.py 是CameraStateEvent最完整的用法示范。其核心思想是:不依赖调用方自己维护状态,而是让事件回调成为状态源。
4.1 订阅事件
async def on_state_change(e: fc.CameraStateEvent): if e.description == state.selected_camera: state.device_orientation = e.device_orientation state.is_recording = e.is_recording_video state.is_recording_paused = e.is_recording_paused state.is_streaming = e.is_streaming_images state.is_preview_paused = e.is_preview_paused sync_action_buttons() if e.has_error: status.value = f"Camera error: {e.error_description}" elif e.is_taking_picture: status.value = "Taking picture..." elif e.is_recording_paused: status.value = "Recording paused" elif e.is_recording_video: status.value = "Recording video..." elif e.is_streaming_images: status.value = "Streaming images..." elif e.is_preview_paused: status.value = "Preview paused" else: status.value = "Camera ready" page.update() preview.on_state_change = on_state_change出处:camera_playground/main.py。
这段代码示范了三个重要实践:
- 设备身份校验:用
e.description == state.selected_camera确认事件来自当前选中的相机,防止切换相机过程中旧控制器的事件污染 UI; - 状态收敛:把 5 个核心布尔标志位同步到应用级
state,并调用sync_action_buttons()统一刷新按钮的disabled/selected状态; - 优先级分支:按
has_error>is_taking_picture>is_recording_paused>is_recording_video>is_streaming_images>is_preview_paused的优先级输出状态文本——错误始终最优先展示。
4.2 最小可用示例
参照官方示例,一个最小化的订阅流程如下(需先按 Camera 控件文档 配置权限并安装flet-camera):
import flet as ft import flet_camera as fc async def main(page: ft.Page): cam = fc.Camera() status = ft.Text("Not initialized") async def on_state_change(e: fc.CameraStateEvent): if e.has_error: status.value = f"Error: {e.error_description}" elif e.is_recording_video: status.value = "Recording..." elif e.is_streaming_images: status.value = "Streaming..." else: status.value = f"Ready (initialized={e.is_initialized})" page.update() cam.on_state_change = on_state_change async def init_camera(e): cameras = await cam.get_available_cameras() if cameras: await cam.initialize( description=cameras[0], resolution_preset=fc.ResolutionPreset.MEDIUM, ) page.add( cam, status, ft.FilledButton("Initialize", on_click=init_camera), ) ft.run(main)4.3 与操作按钮的联动模式
官方示例中sync_action_buttons()展示了如何用事件字段驱动控件可用性,这种「事件驱动按钮状态」的模式值得直接复用:
def sync_action_buttons(): take_photo_btn.disabled = not state.is_initialized record_btn.disabled = not state.is_initialized pause_recording_btn.disabled = not state.is_recording stream_btn.disabled = not (state.is_initialized and state.is_streaming_supported) preview_btn.disabled = not state.is_initialized(完整版本见 camera_playground/main.py)
五、使用要点与注意事项
5.1 平台支持范围
Camera控件仅在 Android、iOS 与 Web 平台可用,before_update()会对非支持平台抛出FletUnsupportedPlatformException(camera.py)。因此基于CameraStateEvent的应用同样受此限制,桌面端(Windows/macOS/Linux)需要自行处理降级提示。
5.2 权限前置
移动端使用相机前必须请求相机权限(录制含音频的视频还需麦克风权限),否则控制器初始化会失败,并通过has_error/error_description反映到CameraStateEvent中。可参考 权限处理文档 使用PermissionHandler,或在pyproject.toml中声明预置权限包(见 发布文档 的「预定义跨平台权限包」一节)。
5.3 事件频率与空值语义
- 事件会在控制器状态每次变化时触发,而不是周期心跳;高频场景(如图像流运行期间方向或模式变化)下回调可能较频繁,回调内应避免重量级同步操作。
- Flutter 侧序列化时会剔除
null值,Python 侧可选字段(Optional[...])在对应能力不可用或尚未确定时即为None。访问方向、模式、尺寸等可选字段前建议判空。 preview_size与aspect_ratio在预览可用时才非空(Dart 侧value.previewSize != null才填充),若需布局依赖它们,请以is_initialized作为前置条件。
5.4 与 CameraImageEvent 的分工
CameraStateEvent描述「相机状态」,而 CameraImageEvent 描述「图像流中的一帧数据」(宽高、格式、编码字节、光圈、曝光时间、ISO 等),由on_stream_image回调承载。两者经常配合使用:用状态事件控制流开关、用图像事件渲染帧。官方示例即同时订阅了两个事件(camera_playground/main.py)。
六、小结
CameraStateEvent是 flet-camera 与 Flutter 底层CameraValue之间的状态桥:Dart 侧每次控制器状态变化时把全量快照序列化为字典(cameraValueToMap),Python 侧将其反序列化为 21 个字段的类型安全 dataclass 并派发给on_state_change回调。用好它,你可以在纯 Python 代码中构建出「初始化 → 拍照/录像/推流 → 错误处理」全链路状态驱动的相机界面,无需接触任何前端代码。完整的字段定义、枚举取值与配套 API 请查阅 types.py 与 Camera 控件文档。
【免费下载链接】fletBuild realtime web, mobile and desktop apps in Python only. No frontend experience required.项目地址: https://gitcode.com/gh_mirrors/fl/flet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考