LVGL 页面、交互与低重绘架构深挖
LVGL 页面、交互与低重绘架构深挖
为巽1. 页面路由
ui_page_manager.c 用静态 route 表把 page id 映射到 create/destroy/update。切页先 destroy 当前页,再 create 新页;同页切换只调用 update。当前包含 Boot、Menu、Cycling、MTB、Map、SD Route、History、History Detail、Weather、Sensors、Settings、Xiaozhi 和测试页。
页面拥有自己的 LVGL root、timer、event callback 和 PSRAM cache。destroy 必须删除 timer、注销按键 handler、删除 root、释放缓存,避免页面切换后隐藏对象仍参与布局。
2. 中央 UI 数据
ui_app.c 维护 system_data_t。全局 update timer 每 1000 ms 读取运动快照、系统时间、BLE 连接、电量,并调用当前页面 update。运动字段由 valid_flags 控制;传感器离线会清零对应字段并把状态设为 OFFLINE。UI 不直接读 BLE 队列、SD 文件或音频设备。
时间来自 app_system_time 快照,速度由 cm/s 转 km/h,距离由 cm 转 km。这样所有页面使用同一时刻的单位换算和有效性判断。
3. Xiaozhi 页面
页面基准 466x466,四组互斥视图:扫描、Wi-Fi 列表、密码、连接状态。切换视图时给未显示 group 加 LV_OBJ_FLAG_HIDDEN,阻止布局和绘制。扫描动画只有一个 33 ms timer,页面离开立即删除;普通 Wi-Fi/Xiaozhi 状态每 500 ms 刷新,且仅在 snapshot version 变化时更新文字。
扫描动画是三个相隔 800 ms 的空心圆环,最大直径 466,不使用模糊/渐变。密码页使用 LVGL textarea/keyboard,长度限制 8~63。成功状态保持 1 s 后返回进入前页面。
4. 地图页面
旧 page_map.c 仍负责内置 map/export.c 测试路线;正式完整路线使用 page_map_sd_route.c。两者都把 draw callback 限制在单独 draw_layer,只失效地图层。
正式 SD Route:状态 timer=500 ms,导航 timer=20 ms,地图最小绘制间隔=33 ms。导航 timer 读取运动 snapshot,version 不变则不重算旋转和路线屏幕点。底图、路线、箭头、建筑分别有范围/LOD 过滤,建筑视野大于 1500 m 时整类跳过。
地图 root 捕获 pressed/pressing/released/press_lost,拖动累计 4 px 才更新中心;跨入新网格后请求 map_store 异步加载。缩放只重算已有世界点,不访问 SD。页面跨回 Cycling/MTB 时保存 route_id 和导航视觉,重新进入时校验 route_id/generation 后恢复。
5. 圆屏安全区域
页面统一以 466x466 为基准,按钮、文字和 overlay 均在 root 内定位。导航位置锚点 y=360,顶部转向信息和底部速度/距离分开布局;返回、缩放、定位控件使用固定尺寸。任何不可见页面、卡片或 overlay 都显式设置 HIDDEN,避免被遮挡对象继续参与渲染。
6. 事件与线程规则
LVGL API 只在 adapter 锁保护的 UI 线程调用。业务 task 触发页面变化时使用 lv_async_call() 或控制器接口,把操作延后到 LVGL 线程。按键系统在独立 dispatch task 中识别单击/双击/长按,页面注册短生命周期 handler 并在 destroy 注销。
7. 刷新预算
静态页面依赖 1 s 全局 timer;Xiaozhi 状态 500 ms;SD 地图准备 500 ms;导航数据 20 ms 但版本不变不重绘;主动播报事件检测 1 s。没有页面循环读 SD、没有在 20 ms 路径做 GCJ 转换、没有高频全屏 invalidate。
8. 面试追问
为什么不用多个页面常驻? 页面对象和 timer 常驻会增加布局、绘制和内存压力;page manager 的 create/destroy 让不可见页面真正释放。
为什么 update timer 仍是 1 s? 页面需要的高频路径由页面专用 timer 管理,中央数据同步只做低频快照;避免每个页面各自创建重复业务 timer。
如何避免地图闪烁? 新地图在后台完成并以 generation 一次性交换,旧几何缓存继续显示;路线屏幕点只有在必要时重建。



