当前位置: 首页 > news >正文

【LVGL自学笔记暂存】

前言

之前一直在用cdroid和qt,没有用过其他图形框架,因工作原因需要用到,今天自学了一下lvgl在此记录一下自己的笔记(仅个人记录)

屏幕切换

1. 使用对象显示/隐藏(推荐简单场景)

这是最简单直接的页面切换方式:

// 创建两个页面
lv_obj_t * page1 = lv_obj_create(lv_scr_act());
lv_obj_t * page2 = lv_obj_create(lv_scr_act());// 初始化状态:显示page1,隐藏page2
lv_obj_clear_flag(page1, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(page2, LV_OBJ_FLAG_HIDDEN);// 切换函数
void switch_page(lv_obj_t * show_page, lv_obj_t * hide_page) {lv_obj_clear_flag(show_page, LV_OBJ_FLAG_HIDDEN);lv_obj_add_flag(hide_page, LV_OBJ_FLAG_HIDDEN);
}// 切换到page2
switch_page(page2, page1);

优点

  • 实现简单
  • 保留页面状态
  • 内存占用固定

缺点

  • 所有页面常驻内存
  • 不适合页面很多的情况

2. 使用lv_scr_load()切换屏幕(推荐常规使用)

// 创建两个屏幕
lv_obj_t * screen1 = lv_obj_create(NULL);
lv_obj_t * screen2 = lv_obj_create(NULL);// 在screen1上添加内容
lv_obj_t * label1 = lv_label_create(screen1);
lv_label_set_text(label1, "Screen 1");// 在screen2上添加内容
lv_obj_t * label2 = lv_label_create(screen2);
lv_label_set_text(label2, "Screen 2");// 加载第一个屏幕
lv_scr_load(screen1);// 切换到第二个屏幕
lv_scr_load(screen2);

优点

  • LVGL官方推荐方式
  • 自动管理屏幕生命周期
  • 内存效率较高

缺点

  • 切换时会销毁原屏幕(除非手动保存)

3. 使用lv_scr_load_anim()添加动画效果

// 创建两个屏幕
lv_obj_t * screen1 = lv_obj_create(NULL);
lv_obj_t * screen2 = lv_obj_create(NULL);// 添加内容...// 带动画的屏幕切换
lv_scr_load_anim(screen2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, false);

可用动画效果

  • LV_SCR_LOAD_ANIM_NONE - 无动画
  • LV_SCR_LOAD_ANIM_OVER_LEFT - 从右侧滑入
  • LV_SCR_LOAD_ANIM_OVER_RIGHT - 从左侧滑入
  • LV_SCR_LOAD_ANIM_OVER_TOP - 从底部滑入
  • LV_SCR_LOAD_ANIM_OVER_BOTTOM - 从顶部滑入
  • LV_SCR_LOAD_ANIM_MOVE_LEFT - 向左移动
  • LV_SCR_LOAD_ANIM_MOVE_RIGHT - 向右移动
  • LV_SCR_LOAD_ANIM_MOVE_TOP - 向上移动
  • LV_SCR_LOAD_ANIM_MOVE_BOTTOM - 向下移动
  • LV_SCR_LOAD_ANIM_FADE_ON - 淡入

4. 使用选项卡视图(lv_tabview)

适合需要底部/顶部导航栏的多页面应用:

// 创建选项卡视图
lv_obj_t * tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 50);// 添加3个选项卡
lv_obj_t * tab1 = lv_tabview_add_tab(tabview, "Tab 1");
lv_obj_t * tab2 = lv_tabview_add_tab(tabview, "Tab 2");
lv_obj_t * tab3 = lv_tabview_add_tab(tabview, "Tab 3");// 在每个选项卡中添加内容
lv_obj_t * label1 = lv_label_create(tab1);
lv_label_set_text(label1, "This is Tab 1");// 编程切换选项卡
lv_tabview_set_act(tabview, 1, LV_ANIM_ON);  // 切换到第2个选项卡

5. 页面管理器

对于复杂应用,可以实现一个页面管理器:

typedef enum {PAGE_HOME,PAGE_SETTINGS,PAGE_ABOUT,PAGE_COUNT
} page_id_t;lv_obj_t * pages[PAGE_COUNT] = {NULL};void page_init() {for(int i = 0; i < PAGE_COUNT; i++) {pages[i] = lv_obj_create(NULL);// 初始化各页面内容...}
}void switch_to_page(page_id_t page) {static page_id_t current_page = PAGE_HOME;if(page >= 0 && page < PAGE_COUNT && page != current_page) {lv_scr_load_anim(pages[page], LV_SCR_LOAD_ANIM_FADE_ON, 200, 0, false);current_page = page;}
}

2. 使用lv_scr_load()切换屏幕(推荐常规使用)

// 创建两个屏幕
lv_obj_t * screen1 = lv_obj_create(NULL);
lv_obj_t * screen2 = lv_obj_create(NULL);// 在screen1上添加内容
lv_obj_t * label1 = lv_label_create(screen1);
lv_label_set_text(label1, "Screen 1");// 在screen2上添加内容
lv_obj_t * label2 = lv_label_create(screen2);
lv_label_set_text(label2, "Screen 2");// 加载第一个屏幕
lv_scr_load(screen1);// 切换到第二个屏幕
lv_scr_load(screen2);

关于lv_scr_load()lv_scr_load_anim()

  • lv_scr_load()确实是直接切换页面(无动画)

  • lv_scr_load_anim()支持带动画效果的切换

  • // 示例:默认切换(前一个屏幕被自动释放)
    lv_obj_t *screen1 = lv_obj_create(NULL);  // 创建屏幕1
    lv_obj_t *screen2 = lv_obj_create(NULL);  // 创建屏幕2
    lv_scr_load(screen1);                    // 显示屏幕1
    lv_scr_load(screen2);                    // 切换到屏幕2,screen1被自动删除
    
  • 切换后会释放前一个屏幕的内存(除非手动保留)

  • 方法1:使用lv_obj_clear_flag(obj, LV_OBJ_FLAG_AUTO_DELETE)
    lv_obj_t *screen1 = lv_obj_create(NULL);
    lv_obj_clear_flag(screen1, LV_OBJ_FLAG_AUTO_DELETE);  // 禁止自动删除
    lv_scr_load(screen1);// 切换到screen2时,screen1不会被删除
    lv_obj_t *screen2 = lv_obj_create(NULL);
    lv_scr_load(screen2);  // screen1仍保留在内存中
    
    方法2:全局变量保存屏幕对象
    lv_obj_t *screens[2];  // 全局保存void create_screens() {screens[0] = lv_obj_create(NULL);  // 屏幕1screens[1] = lv_obj_create(NULL);  // 屏幕2lv_scr_load(screens[0]);
    }void switch_screen(int id) {
  • 需要补充的理解:

// 典型用法示例
lv_obj_t * screen1 = lv_obj_create(NULL);  // 必须创建为独立屏幕
lv_obj_t * screen2 = lv_obj_create(NULL);// 直接切换
lv_scr_load(screen1);// 带动画切换(从右侧滑入,耗时300ms)
lv_scr_load_anim(screen2, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 300, 0, false);

Q:手动保留会导致内存泄漏吗?
A:不会,但需在应用退出时手动调用lv_obj_del()释放所有屏幕。

Q:如何判断对象是否已被删除?
A:LVGL内部会标记已删除对象,访问已被删除的对象可能导致崩溃。

Q:保留多个屏幕会显著增加内存占用吗?
A:取决于页面复杂度,简单页面(如纯色背景+少量控件)通常占用较少内存。

父对象

1. 父对象的基本选择方式

创建对象时必须指定父对象,决定了对象的显示层级和生命周期:

// 创建父容器
lv_obj_t *parent = lv_obj_create(lv_scr_act()); // 创建子对象(按钮),父对象为parent
lv_obj_t *btn = lv_btn_create(parent); 

2. LVGL父子关系核心特点

(1) 生命周期管理(与Qt对比)
特性LVGLQt
对象删除删除父对象会自动删除所有子对象同LVGL
内存所有权严格父子层级管理可通过QPointer等弱引用打破
独立存活子对象不能脱离父对象独立存在子对象可重新设置父对象
(2) 关键行为
  • 自动删除:当父对象被删除时,所有子对象递归删除

    lv_obj_del(parent); // 会自动删除btn及其他子对象
    
  • 层级限制:子对象永远显示在父对象的绘图区域内(裁剪效果)


3. 父对象选择策略

(1) 典型父对象类型
父对象类型适用场景示例
屏幕对象(NULL)顶级页面lv_obj_create(NULL)
普通容器分组相关控件表单、工具条
滚动容器需要滚动的区域lv_scroll_create(parent)
窗口对象弹出层/对话框lv_win_create(parent)
(2) 选择建议
// ✅ 推荐做法:明确层级关系
lv_obj_t *main_screen = lv_obj_create(NULL);
lv_obj_t *toolbar = lv_obj_create(main_screen);
lv_obj_t *btn = lv_btn_create(toolbar);// ❌ 避免混乱的父子关系
lv_obj_t *btn1 = lv_btn_create(lv_scr_act());
lv_obj_t *btn2 = lv_btn_create(btn1); // 非常规用法!

4. 特殊管理技巧

(1) 临时解除父子关系
// 将子对象移动到新父对象
lv_obj_set_parent(child, new_parent);// 临时移出(需自行管理内存)
lv_obj_remove_from_parent(child);
lv_obj_del(child); // 必须手动删除
(2) 防止自动删除
lv_obj_t *child = lv_label_create(parent);
lv_obj_clear_flag(child, LV_OBJ_FLAG_AUTO_DELETE); // 禁用自动删除
lv_obj_del(parent); // 此时child不会被自动删除

5. 与Qt的关键差异

特性LVGLQt
对象树遍历只能向下遍历(lv_obj_get_child)支持双向遍历(QObject::parent())
信号/槽绑定无自动连接机制有元对象系统自动管理
样式继承部分属性继承完整样式表继承
内存模型严格父子所有权支持多种所有权模型

6. 实际应用示例

(1) 动态界面管理
// 创建可复用的组件
lv_obj_t *create_button_group(lv_obj_t *parent) {lv_obj_t *cont = lv_obj_create(parent);lv_btn_create(cont);lv_btn_create(cont);return cont; // 返回容器对象
}// 使用时明确父子关系
lv_obj_t *main_page = lv_obj_create(NULL);
lv_obj_t *btn_group = create_button_group(main_page);
(2) 安全删除策略
void safe_remove(lv_obj_t *obj) {if(lv_obj_is_valid(obj)) {  // 检查对象是否有效lv_obj_t *parent = lv_obj_get_parent(obj);if(parent) lv_obj_del(obj);  // 正常删除else {lv_obj_remove_from_parent(obj);lv_obj_del(obj);  // 处理无父对象情况}}
}

补充:嵌套容器

1. 两种方式的本质区别

方式直接挂载到屏幕嵌套容器
代码示例lv_label_create(lv_scr_act())lv_label_create(container)
适用场景快速原型开发、简单界面正式项目、复杂UI
管理成本需单独操作每个控件可批量操作容器内所有子对象
渲染性能较高(扁平结构)略低(需处理嵌套关系)
内存占用较低稍高(多一个容器对象)

2. 嵌套容器的核心价值

(1) 布局管理效率提升
// 统一调整容器内所有子对象
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW); // 自动排列所有子Label
lv_obj_set_style_pad_all(container, 10, 0);        // 统一设置内边距
(2) 动态控制显隐
// 一键隐藏/显示整个功能模块
lv_obj_add_flag(settings_panel, LV_OBJ_FLAG_HIDDEN); // 隐藏所有设置项Label
(3) 样式继承优化
// 容器设置公共样式
lv_obj_set_style_text_font(container, &my_font, 0);
// 子Label自动继承字体,无需重复设置
lv_obj_t *label1 = lv_label_create(container);
lv_obj_t *label2 = lv_label_create(container); 

3. 实际项目中的层级设计

(1) 推荐层级结构
屏幕 (lv_scr_act())
└── 主容器├── 标题栏容器│   ├── 标题Label│   └── 图标Button└── 内容区容器└── 表单容器├── 输入框Label└── 输入框TextArea
(2) 典型代码实现
// 1. 创建主容器
lv_obj_t *main_cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(main_cont, lv_pct(100), lv_pct(100));// 2. 创建标题栏子容器
lv_obj_t *header = lv_obj_create(main_cont);
lv_obj_set_size(header, lv_pct(100), 50);// 3. 在标题栏添加Label
lv_obj_t *title = lv_label_create(header);
lv_label_set_text(title, "系统设置");// 4. 内容区子容器
lv_obj_t *content = lv_obj_create(main_cont);
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);// 5. 在内容区添加多个Label
for(int i=0; i<3; i++){lv_obj_t *item = lv_label_create(content);lv_label_set_text_fmt(item, "配置项 %d", i+1);
}

4. 何时选择直接挂载?

(1) 适用场景
  • 快速验证UI效果
  • 只有一个Label的简单界面
  • 内存极度受限的嵌入式设备
(2) 示例代码
// 临时调试用的Label
lv_obj_t *debug_label = lv_label_create(lv_scr_act());
lv_label_set_text(debug_label, "传感器值: 25.6℃");
lv_obj_align(debug_label, LV_ALIGN_TOP_RIGHT, -10, 10);

5. 进阶技巧:动态父对象切换

// 将Label从容器A移动到容器B
lv_obj_set_parent(my_label, container_B);// 移动后自动保持相对位置
lv_obj_align(my_label, LV_ALIGN_CENTER, 0, 0);

窗口管理

1. 核心方案对比

维度双屏幕方案 (lv_scr_load)单屏幕+双窗口方案 (lv_win)
内存占用较高(同时保留两个屏幕)较低(窗口共享同一屏幕)
渲染性能切换时全屏重绘(开销大)局部更新(仅窗口内容变化)
状态保留需手动保存状态窗口隐藏时自动保留状态
代码复杂度简单直接需处理窗口层叠关系
适用场景全屏页面切换(如设置页→主页)局部弹窗/浮动窗口(如对话框+主界面)

2. 内存占用实测数据

测试环境:STM32F429,LVGL v8.3,320x240分辨率

  • 双屏幕方案

    lv_obj_t *scr1 = lv_obj_create(NULL); // 约150B
    lv_obj_t *scr2 = lv_obj_create(NULL); // 约150B
    

    总内存:300B(不含内容)

  • 单屏幕+双窗口

    lv_obj_t *scr = lv_scr_act();
    lv_obj_t *win1 = lv_win_create(scr);  // 约500B
    lv_obj_t *win2 = lv_win_create(scr);  // 约500B
    lv_obj_add_flag(win2, LV_OBJ_FLAG_HIDDEN);
    

    总内存:500B(但可动态释放隐藏窗口)


3. 性能关键指标

指标双屏幕方案单屏幕+双窗口
切换时间(ms)15-25(全屏重绘)5-10(局部更新)
输入延迟较高(需重建输入焦点)较低(保持焦点链)
动画流畅度适合全屏动画适合局部微交互

4. 具体场景推荐

(1) 必须用双屏幕的场景
  • 全屏应用切换(如手机Launcher→设置)

    // 初始化
    lv_obj_t *home_scr = lv_obj_create(NULL);
    lv_obj_t *settings_scr = lv_obj_create(NULL);// 切换
    lv_scr_load_anim(home_scr, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, false);
    
(2) 推荐单屏幕+窗口的场景
  • 浮动UI系统(如HUD+弹窗)

    // 主窗口
    lv_obj_t *main_win = lv_win_create(lv_scr_act());// 弹窗(默认隐藏)
    lv_obj_t *dialog = lv_win_create(lv_scr_act());
    lv_obj_add_flag(dialog, LV_OBJ_FLAG_HIDDEN);// 显示弹窗
    lv_obj_clear_flag(dialog, LV_OBJ_FLAG_HIDDEN);
    lv_obj_move_foreground(dialog);
    

5. 混合方案示例

需求:主界面常驻,临时页面可快速切换

// 主屏幕(常驻)
lv_obj_t *main_scr = lv_obj_create(NULL);
lv_scr_load(main_scr);// 临时页面(动态创建/删除)
void show_temp_page() {lv_obj_t *temp_scr = lv_obj_create(NULL);lv_scr_load_anim(temp_scr, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, true);// 返回时自动删除
}// 或使用窗口方案
void show_temp_win() {lv_obj_t *win = lv_win_create(main_scr);lv_obj_set_size(win, 200, 150);lv_obj_center(win);
}

6. 决策流程图

在这里插入图片描述


7. 终极建议

  • 内存<100KB:强制使用单屏幕+动态窗口(避免OOM)
  • 内存>256KB:双屏幕方案(开发更简单)
  • 高频切换:单屏幕+lv_obj_swap()隐藏/显示
  • 触控响应优先:窗口方案(保持焦点链)

示例代码(最优性能方案):

// 预创建两个屏幕,但不同时激活
lv_obj_t *scr1 = lv_obj_create(NULL);
lv_obj_t *scr2 = lv_obj_create(NULL);
lv_obj_clear_flag(scr1, LV_OBJ_FLAG_AUTO_DELETE);// 快速切换(保留scr1状态)
void switch_to_scr2() {lv_scr_load_anim(scr2, LV_SCR_LOAD_ANIM_OVER_LEFT, 200, 0, false);lv_obj_del(scr1); // 手动控制删除
}

事件响应机制**(基于回调的事件分发模型**)

1. 事件系统核心架构

在这里插入图片描述


2. 事件处理全流程

(1) 事件触发阶段
  • 输入设备(触摸屏/编码器/按键)产生原始数据
  • LVGL驱动层 将原始数据转换为标准事件(如LV_EVENT_PRESSED
  • 事件入队:事件被放入 异步事件队列(默认深度16)
(2) 事件分发阶段
  • 命中测试:通过 lv_obj_hit_test() 确定目标对象
  • 冒泡传递:事件沿父对象链向上传递(除非调用 lv_event_stop_bubbling()
  • 回调执行:依次执行对象上的注册回调
(3) 默认处理阶段
  • 若用户未处理事件,LVGL执行 内置默认行为(如按钮按下状态切换)

3. 关键API及用法

(1) 事件回调注册
// 注册事件回调(支持LV_EVENT_ALL监听所有事件)
lv_obj_add_event_cb(obj, event_cb, LV_EVENT_CLICKED, user_data);// 回调函数原型
static void event_cb(lv_event_t * e) {lv_obj_t * target = lv_event_get_target(e);  // 获取触发对象lv_event_code_t code = lv_event_get_code(e); // 获取事件类型void * user_data = lv_event_get_user_data(e);// 获取用户数据switch(code) {case LV_EVENT_CLICKED:// 处理点击事件break;case LV_EVENT_VALUE_CHANGED:// 处理值变化break;}
}
(2) 事件控制方法
API作用示例
lv_event_send(obj, code)手动发送事件模拟点击:lv_event_send(btn, LV_EVENT_CLICKED)
lv_event_stop_bubbling(e)停止事件冒泡在回调中调用阻止父对象处理
lv_event_mark_deleted(e)标记对象待删除(安全删除)在回调中删除触发对象时使用

4. 事件类型详解

(1) 输入设备事件
事件类型触发条件典型应用场景
LV_EVENT_PRESSED对象被按下按钮按下态样式切换
LV_EVENT_RELEASED对象被释放点击确认操作
LV_EVENT_LONG_PRESSED长按(默认1秒)弹出上下文菜单
LV_EVENT_GESTURE手势识别(滑动/缩放)图库浏览
(2) 控件特定事件
事件类型对应控件数据获取方法
LV_EVENT_VALUE_CHANGED滑块/开关/下拉列表lv_slider_get_value(e->target)
LV_EVENT_DRAW_PART_BEGIN自定义绘制控件通过e->draw_part修改绘制参数
LV_EVENT_SCROLL滚动容器lv_obj_get_scroll_top(e->target)

5. 高级特性

(1) 事件过滤器
// 只处理特定事件(减少回调触发次数)
lv_obj_add_event_cb_filter(obj, [](lv_event_t * e) {return (e->code == LV_EVENT_CLICKED) ? LV_EVENT_RES_PASS : LV_EVENT_RES_IGNORE;
});
(2) 异步事件处理
// 在事件回调中延迟执行
lv_async_call([](void * data) {lv_obj_add_state(data, LV_STATE_CHECKED);
}, obj);
(3) 自定义事件
// 定义自定义事件码(>LV_EVENT_LAST)
#define MY_EVENT_CUSTOM  (LV_EVENT_LAST + 1)// 发送自定义事件
lv_event_send(obj, MY_EVENT_CUSTOM, custom_data);

6. 性能优化技巧

(1) 事件回调优化
// 错误做法:频繁注册/删除回调
void update_ui() {lv_obj_remove_event_cb(btn, old_cb);  // 避免!lv_obj_add_event_cb(btn, new_cb, ...);
}// 正确做法:重用回调
static void smart_cb(lv_event_t * e) {if(condition) handler_A(e);else handler_B(e);
}
(2) 事件数据共享
// 通过user_data传递结构体
typedef struct {lv_obj_t * slider;lv_obj_t * label;
} MyData;MyData * data = lv_mem_alloc(sizeof(MyData));
lv_obj_add_event_cb(btn, event_cb, LV_EVENT_ALL, data);
(3) 输入设备优化
// 减少不必要的事件上报
lv_indev_set_read_cb(indev, [](lv_indev_data_t * data) {static int last_pos;if(abs(data->point.x - last_pos) < 5) return false; // 忽略微小移动last_pos = data->point.x;return true;
});

7. 调试与问题排查

(1) 事件流监控
// 添加全局事件监视器
lv_obj_add_event_cb(lv_scr_act(), [](lv_event_t * e) {printf("Event %d on %p\n", e->code, e->target);
}, LV_EVENT_ALL, NULL);
(2) 常见问题解决
  • 事件未触发

    1. 检查对象是否设置 LV_OBJ_FLAG_CLICKABLE
    2. 确认父对象未设置 LV_OBJ_FLAG_HIDDENLV_OBJ_FLAG_CLICK_FOCUSABLE
  • 内存泄漏
    在回调中动态分配的数据需通过 LV_EVENT_DELETE 事件释放:

    if(e->code == LV_EVENT_DELETE) {lv_mem_free(e->user_data);
    }
    

    拓展 LV_STATE_CHECKED

    LV_STATE_CHECKED 是对象状态标志之一,用于表示控件被选中/激活/切换的状态

    1. 本质与作用

    • 类型lv_state_t 枚举值(32位状态位掩码)
    • 二进制值0x00000002(第2位)
    • 典型应用控件
      • 复选框(lv_checkbox)
      • 开关(lv_switch)
      • 单选按钮(lv_radio)
      • 选项卡(lv_tabview)
      • 自定义可选中控件

    2. 状态管理API

    方法作用示例
    lv_obj_add_state(obj, state)添加状态标志lv_obj_add_state(btn, LV_STATE_CHECKED)
    lv_obj_clear_state(obj, state)清除状态标志lv_obj_clear_state(sw, LV_STATE_CHECKED)
    lv_obj_has_state(obj, state)检查状态是否存在if(lv_obj_has_state(cb, LV_STATE_CHECKED)) {...}

    3. 视觉表现机制

    当对象处于LV_STATE_CHECKED状态时,LVGL会自动应用对应的样式属性:

    /* 在样式表中定义选中状态样式 */
    static lv_style_t style_checked;
    lv_style_init(&style_checked);
    lv_style_set_bg_color(&style_checked, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_text_color(&style_checked, lv_color_white());/* 将样式绑定到控件的选中状态 */
    lv_obj_add_style(btn, &style_checked, LV_STATE_CHECKED);
    

    典型样式属性变化

    • 背景色改变
    • 文字/图标颜色变化
    • 添加选中标记(如对勾图标)
    • 控件位置微调(如开关滑块位移)

    4. 与其他状态的关系

    状态标志常与CHECKED组合的场景复合效果
    LV_STATE_PRESSED按钮在选中状态下被按下深色背景+按压动画
    LV_STATE_DISABLED禁用已选中的控件灰色外观+保留选中标记
    LV_STATE_FOCUSED键盘导航至已选中控件焦点边框+选中样式
    // 典型复合状态处理
    if(lv_obj_has_state(obj, LV_STATE_CHECKED | LV_STATE_FOCUSED)) {// 处理既被选中又获得焦点的状态
    }
    

    5. 实际应用示例

    (1) 自定义可选中按钮
    lv_obj_t *btn = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn, [](lv_event_t *e) {lv_obj_t *obj = lv_event_get_target(e);if(lv_obj_has_state(obj, LV_STATE_CHECKED)) {lv_obj_clear_state(obj, LV_STATE_CHECKED);} else {lv_obj_add_state(obj, LV_STATE_CHECKED);}
    }, LV_EVENT_CLICKED, NULL);
    
    (2) 动态样式更新
    // 根据选中状态改变标签文本
    lv_obj_t *label = lv_label_create(btn);
    lv_obj_add_event_cb(btn, [](lv_event_t *e) {lv_obj_t *label = lv_obj_get_child(e->target, 0);if(lv_obj_has_state(e->target, LV_STATE_CHECKED)) {lv_label_set_text(label, "ON");} else {lv_label_set_text(label, "OFF");}
    }, LV_EVENT_VALUE_CHANGED, NULL);
    

    6. 内部实现原理

    1. 状态存储:每个lv_obj_t内部维护state变量(uint32_t

    2. 样式匹配:对象渲染时,LVGL按优先级匹配当前状态组合的样式:

      匹配顺序:
      LV_STATE_CHECKED | LV_STATE_PRESSED → LV_STATE_CHECKED → LV_STATE_DEFAULT
      
    3. 事件触发:状态变化时会发送LV_EVENT_VALUE_CHANGED事件


    7. 开发者注意事项

    1. 不要直接赋值状态

      // 错误做法
      obj->state = LV_STATE_CHECKED; // 正确做法
      lv_obj_add_state(obj, LV_STATE_CHECKED);
      
    2. 自定义控件需处理状态

      // 在draw事件中检查状态
      if(lv_obj_has_state(obj, LV_STATE_CHECKED)) {// 绘制选中特效
      }
      
    3. 性能优化:对频繁切换的对象,使用lv_obj_clear_state而非lv_obj_set_state(避免冗余样式重算)


    与其他GUI库的对比

    特性LVGLQtAndroid
    状态标志位掩码组合QStyle::State枚举View.STATE_*常量
    样式继承部分属性继承完全继承通过Theme定义
    状态持久性显式添加/清除通常由控件自动管理由视图系统管理

定时器

一、LVGL 定时器类型

定时器类型创建方式触发机制典型应用场景
系统定时器lv_timer_create()基于系统滴答(tick)触发通用周期性任务
动画定时器lv_anim_create()绑定到对象属性变化UI 动画效果
异步调用lv_async_call()下次主循环执行跨线程安全操作

二、系统定时器 (lv_timer)

1. 基本使用
// 创建定时器(单位:毫秒)
lv_timer_t * timer = lv_timer_create(timer_cb, 500, NULL); // 回调函数
void timer_cb(lv_timer_t * timer) {static int count = 0;lv_label_set_text_fmt(label, "Count: %d", count++);// 运行10次后删除if(count >= 10) lv_timer_del(timer);
}
2. 关键API
API作用
lv_timer_create(cb, period, user_data)创建定时器
lv_timer_del(timer)立即删除定时器
lv_timer_pause(timer)暂停(保留剩余周期)
lv_timer_resume(timer)恢复运行
lv_timer_set_repeat_count(timer, n)设置重复次数(-1为无限)
3. 内部机制
  • 时间基准:依赖 lv_tick_inc() 函数更新的系统滴答
  • 调度方式:在主循环 (lv_task_handler) 中检查触发
  • 优先级:定时器按创建顺序执行,无优先级抢占

三、动画定时器 (lv_anim)

1. 基本使用
lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_obj_set_x); // 设置属性
lv_anim_set_var(&anim, obj);      // 目标对象
lv_anim_set_values(&anim, 0, 100); // 起始/结束值
lv_anim_set_time(&anim, 1000);     // 持续时间(ms)
lv_anim_set_path_cb(&anim, lv_anim_path_linear); // 动画曲线
lv_anim_start(&anim);              // 启动
2. 动画曲线类型
曲线函数效果
lv_anim_path_linear线性变化
lv_anim_path_ease_in先慢后快
lv_anim_path_ease_out先快后慢
lv_anim_path_overshoot超过终点再回弹
3. 动画控制
// 暂停/继续特定对象的动画
lv_anim_pause(obj, anim_exec_cb); 
lv_anim_resume(obj, anim_exec_cb);// 删除所有动画
lv_anim_delete(obj, NULL); 

四、异步调用 (lv_async_call)

用于线程安全操作:

void async_task(void * data) {lv_obj_t * label = (lv_obj_t *)data;lv_label_set_text(label, "Updated from async");
}// 在其他线程/中断中调用
lv_async_call(async_task, label); // 下次主循环执行

五、定时器机制原理

  1. 时间管理

    • 底层依赖 lv_tick_inc(ms) 函数更新全局时间戳
    • 需在硬件定时器中断中调用(如每1ms调用一次)
  2. 执行流程

    在这里插入图片描述

    1. 性能特征
      • 定时器回调在 主循环上下文 执行
      • 单个回调阻塞会导致后续定时器延迟
      • 默认最多支持 LV_TIMER_HANDLER_MAX_NUM(默认32)个定时器

    六、最佳实践

    1. 资源管理

      // 定时器自动删除模式(运行1次)
      lv_timer_t * timer = lv_timer_create(cb, 1000, NULL);
      lv_timer_set_repeat_count(timer, 1);
      
    2. 高精度控制

      // 手动计算时间补偿
      uint32_t last_run = lv_tick_get();
      void timer_cb(lv_timer_t * t) {uint32_t now = lv_tick_get();uint32_t elapsed = now - last_run;last_run = now;// 使用elapsed做精确控制...
      }
      
    3. 错误处理

      if(!lv_timer_create(cb, period, data)) {LV_LOG_ERROR("Timer creation failed!");
      }
      

    七、常见问题解决方案

    1. 定时器不触发

      • 检查是否调用了 lv_tick_inc()
      • 确认 lv_task_handler() 在主循环中执行
    2. 动画卡顿

      • 减少同时运行的动画数量
      • 使用 lv_anim_set_early_apply(&anim, true) 立即应用首帧
    3. 内存泄漏

      • 在对象删除事件中清理关联定时器:

        lv_obj_add_event_cb(obj, [](lv_event_t * e) {if(e->code == LV_EVENT_DELETE) {lv_anim_delete(e->target, NULL);}
        }, LV_EVENT_ALL, NULL);
        

动画

1. lv_anim_t 动画对象的本质

typedef struct _lv_anim_t {lv_anim_exec_xcb_t exec_cb;  // 属性设置函数(如lv_obj_set_x)void * var;                  // 目标对象(通过lv_anim_set_var设置)int32_t start;               // 起始值(lv_anim_set_values)int32_t end;                 // 结束值(lv_anim_set_values)uint32_t time;               // 持续时间ms(lv_anim_set_time)/* 其他内部管理字段... */
} lv_anim_t;

2. 关键属性设置的作用

(1) 目标对象 lv_anim_set_var(&anim, obj)
  • 功能:指定动画要作用的具体UI对象

  • 底层影响

  • 在这里插入图片描述

  • 典型值lv_obj_t* 类型指针(如按钮、标签等)

(2) 起止值 lv_anim_set_values(&anim, 0, 100)
  • 功能:定义动画变化的数值范围
  • 关键机制
    • 根据当前时间进度,通过动画路径函数(如lv_anim_path_linear)计算中间值
    • 最终值通过exec_cb函数应用到目标对象
  • 单位:与执行函数相关(如lv_obj_set_x的单位是像素)
(3) 持续时间 lv_anim_set_time(&anim, 1000)
  • 功能:控制动画从开始到结束的总时间

  • 内部处理

    // 伪代码:计算当前值
    float progress = (current_time / total_time) * path_curve(time_ratio);
    int32_t current_val = start + (end - start) * progress;
    
  • 性能影响:时间越长,每帧变化幅度越小,动画越平滑


3. 动画启动流程

您提到的三个属性设置后,还需要关键一步才会真正启动动画

lv_anim_start(&anim);  // 必须调用此函数动画才会生效

完整触发流程

  1. lv_anim_start 将动画添加到LVGL全局动画链表
  2. 主循环 (lv_task_handler) 检测到动画需要更新
  3. 根据当前时间计算进度值(依赖 lv_tick_inc 提供的时间基准)
  4. 调用 exec_cb 函数更新对象属性
  5. 触发对象重绘(自动调用 lv_obj_invalidate

4. 属性设置与视觉效果的关系

属性视觉表现示例(移动动画)数值变化示例(1秒内)
var=obj, values=0→100对象从x=0移动到x=100[0, 20, 40, 60, 80, 100]
time=2000移动速度减半[0, 10, 20,…,100]
path_cb=ease_out先快后慢的移动[0, 50, 80, 95, 99, 100]

5. 必须设置的四大属性

一个可运行的动画至少需要配置以下四项

lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_obj_set_x); // 1.设置属性函数
lv_anim_set_var(&anim, obj);                                   // 2.目标对象
lv_anim_set_values(&anim, 0, 100);                             // 3.起止值
lv_anim_set_time(&anim, 1000);                                 // 4.持续时间
lv_anim_start(&anim);                                          // 启动!

6. 完整动画生命周期

在这里插入图片描述


7. 常见问题解决方案

Q:设置参数后动画未启动?

  • 确认调用了 lv_anim_start()
  • 检查 lv_task_handler() 是否在主循环中执行
  • 验证目标对象是否有效(未被删除)

Q:动画效果不符合预期?

  • 检查 exec_cb 是否匹配属性(如用 lv_obj_set_x 控制Y轴位置无效)
  • 确认时间单位是毫秒(1000=1秒
  • 调试路径函数:改用 lv_anim_path_linear 测试基础功能

Q:如何暂停/继续动画?

// 暂停特定属性动画
lv_anim_pause(obj, (lv_anim_exec_xcb_t)lv_obj_set_x);// 继续播放
lv_anim_resume(obj, (lv_anim_exec_xcb_t)lv_obj_set_x);

拓展 lv_anim_path_linear

1. 本质与作用

  • 功能:定义动画属性值的变化规律(如线性、缓入缓出等)

  • 类型lv_anim_path_cb_t 函数指针

  • 默认选项

    lv_anim_path_linear   // 线性匀速
    lv_anim_path_ease_in  // 先慢后快(加速)
    lv_anim_path_ease_out // 先快后慢(减速)
    lv_anim_path_ease_in_out // 两头慢中间快
    lv_anim_path_overshoot  // 超过终点再回弹
    lv_anim_path_bounce     // 弹性反弹效果
    

2. 使用方法

(1) 设置线性动画
lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_path_cb(&anim, lv_anim_path_linear); // 关键设置
lv_anim_start(&anim);

效果:属性值从起点到终点匀速变化(数学公式:value = start + (end - start) * (current_time / total_time)

(2) 设置非线性动画
lv_anim_set_path_cb(&anim, lv_anim_path_ease_out);

效果:动画开始时变化快,结束时逐渐变慢


3. 动态曲线对比

曲线类型公式示意图适用场景
linear───────进度条、机械运动
ease_in⠀⠀⠀\自由落体、加速启动
ease_out/⠀⠀⠀刹车效果、平滑停止
ease_in_out⠀⠀/\自然物体运动
overshoot───↗───↙──强调动作、弹窗出现
bounce─↑─↓─↑─弹性控件、趣味交互

4. 底层实现原理

当调用 lv_anim_set_path_cb(&anim, lv_anim_path_linear) 时:

  1. LVGL会将此函数指针存入动画对象

  2. 在动画每一帧计算时调用该函数:

    // 伪代码示例
    int32_t current_value = path_cb(start, end, time_ratio);
    
  3. lv_anim_path_linear 的实现:

    int32_t lv_anim_path_linear(const lv_anim_t * anim) {return anim->start + (anim->end - anim->start) * anim->time_ratio;
    }
    

5. 自定义动画曲线

(1) 创建自定义路径函数
int32_t my_custom_path(const lv_anim_t * anim) {// 二次函数曲线float t = anim->time_ratio; // 当前进度[0.0-1.0]return anim->start + (anim->end - anim->start) * t * t;
}// 使用自定义曲线
lv_anim_set_path_cb(&anim, my_custom_path);
(2) 使用贝塞尔曲线
#include "lv_anim_bezier.h"int32_t bezier_path(const lv_anim_t * anim) {// 控制点:P0(0,0), P1(0.3,0.8), P2(1,1)float t = anim->time_ratio;float val = lv_bezier3(t, 0, 0.3, 0.8, 1); return anim->start + (anim->end - anim->start) * val;
}

6. 实际效果验证

可以通过以下代码实时观察不同曲线的值变化:

lv_obj_t * chart = lv_chart_create(lv_scr_act());
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_var(&anim, chart);
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)anim_test_cb);
lv_anim_set_values(&anim, 0, 100);
lv_anim_set_time(&anim, 1000);
lv_anim_set_path_cb(&anim, lv_anim_path_ease_out); // 切换不同曲线观察
lv_anim_start(&anim);void anim_test_cb(lv_obj_t * chart, int32_t val) {lv_chart_set_next_value(chart, lv_chart_get_series_next(chart, NULL), val);
}

7. 性能注意事项

  1. 计算开销(从低到高):

    linear < ease_in/out < overshoot < bounce
    
  2. 优化建议

    • 对大量同时运行的动画使用linear
    • 关键UI动画可使用复杂曲线
    • 避免在性能受限的平台频繁使用bounce
http://www.lryc.cn/news/612922.html

相关文章:

  • LINUX-批量文件管理及vim文件编辑器
  • VBA之Word应用第四章第一节:段落集合Paragraphs对象(一)
  • 11-netty基础-手写rpc-支持多序列化协议-03
  • 从零开始构建情绪可视化日记平台 - React + TypeScript + Vite
  • 芯谷科技--高效噪声降低解决方案压缩扩展器D5015
  • 30-Hive SQL-DML-Load加载数据
  • 微算法科技(NASDAQ:MLGO)利用集成学习方法,实现更低成本、更稳健的区块链虚拟货币交易价格预测
  • 51单片机
  • 数据推荐|标贝科技方言自然对话数据集 构建语音交互新基建
  • 全球化2.0 | 泰国IT服务商携手云轴科技ZStack重塑云租赁新生态
  • 最新教程 | CentOS 7 内网环境 Nginx + ECharts 页面离线部署手册(RPM 安装方式)
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第七天(Vue)(二)
  • 如何为WordPress启用LiteSpeed缓存
  • HTML已死,HTML万岁——重新思考DOM的底层设计理念
  • 炫酷圆形按钮调色器
  • Ubuntu 系统 Docker 启动失败(iptables/nf\_tables)
  • 应急响应复现
  • Android 原生与 Flutter 通信完整实现 (Kotlin 版)
  • JPA 分页查询与条件分页查询
  • 《深入理解 WSGI:解锁 Python Web 应用背后的奥秘》
  • Java+Vue合力开发固定资产条码管理系统,移动端+后台管理,集成资产录入、条码打印、实时盘点等功能,助力高效管理,附全量源码
  • 前端性能优化:从请求到资源的精细调控
  • Event Stream输出优化:Vue3节流函数的正确实现
  • 【大前端】vite忽略指定前缀的静态资源
  • 【插件式微服务架构系统分享】之 解耦至上:gateway 网关与APISIX 网关的不同分工
  • 一文解读“Performance面板”前端性能优化工具基础用法!
  • SpringAI
  • 数据结构---循环队列(补充 应用实例)、哈希表(哈希存储、哈希冲突、解决方法、举例实现)
  • Linux Docker 新手入门:一文学会配置镜像加速器
  • 躺平发育小游戏微信抖音流量主小程序开源