文件 `lv_refr.c` 是 LVGL 图形库中负责屏幕刷新和对象重绘的核心模块文件。以下是文件中定义的所有函数的详细解说,以及如何在一个示例中使用这些函数。
函数解说
1. _lv_refr_init 和 _lv_refr_deinit:
这两个函数分别用于初始化和反初始化屏幕刷新子系统。在实际应用中,它们通常在系统启动时调用 `_lv_refr_init`,并在系统关闭前调用 `_lv_refr_deinit`。
函数原型
void _lv_refr_init(void)
{
}
void _lv_refr_deinit(void)
{
}
2. lv_refr_now:
这个函数用于强制立即刷新屏幕。它可以用来同步动画或者在某些特定条件下立即更新显示内容。lv_refr_now
函数是 LVGL 图形库中用于立即触发屏幕刷新的函数。与定期刷新不同,这个函数可以强制立即更新屏幕上的所有更改,通常在用户交互或其他事件后调用,以确保用户界面的响应性和一致性。
函数原型
/**
* 立即触发屏幕刷新
* @param disp 指向显示屏的指针,如果为 NULL,则刷新所有显示屏
*/
void lv_refr_now(lv_disp_t * disp)
{
// 如果没有指定显示屏,则刷新所有显示屏
if(disp == NULL) {
lv_disp_t * d;
d = lv_disp_get_next(NULL);
while(d) {
if(d->refr_timer) _lv_display_refr_timer(d->refr_timer);
d = lv_disp_get_next(d);
}
} else {
// 如果指定了显示屏,则只刷新该显示屏
if(disp->refr_timer) _lv_display_refr_timer(disp->refr_timer);
}
}
-
函数参数:
lv_disp_t * disp
: 指向特定显示屏的指针,如果为 NULL,则表示刷新所有显示屏。
-
函数逻辑:
- 函数首先检查是否提供了显示屏指针。如果没有提供,函数将遍历所有显示屏,并为每个显示屏调用其刷新定时器回调函数
_lv_display_refr_timer
。 - 如果提供了显示屏指针,函数将只为该显示屏调用刷新定时器回调函数。
- 函数首先检查是否提供了显示屏指针。如果没有提供,函数将遍历所有显示屏,并为每个显示屏调用其刷新定时器回调函数
-
使用场景:
lv_refr_now
函数通常在对象属性发生变化、用户交互发生或需要立即更新屏幕显示时被调用。
-
注意事项:
- 这个函数可能会导致性能开销,因为它强制立即进行屏幕刷新,而不是等待下一个定期刷新周期。
- 函数依赖于显示屏的刷新定时器回调函数
_lv_display_refr_timer
来执行实际的刷新操作。 - 在调用
lv_refr_now
时,应确保不会过度刷新屏幕,以免造成不必要的性能负担。
通过 lv_refr_now
函数,LVGL 能够提供及时的屏幕更新,确保用户界面的响应性和一致性。这是实现流畅用户体验的重要机制之一。
3. lv_obj_redraw:
重绘指定对象及其子对象的函数。它会计算对象的裁剪区域,并发送绘制事件。如果对象设置了 `LV_OBJ_FLAG_OVERFLOW_VISIBLE` 标志,它还会重绘溢出部分。`lv_obj_redraw` 函数是 LVGL 图形库中用于强制重绘(redraw)指定对象的函数。当对象的属性发生变化、对象被覆盖后需要重新显示,或者需要立即更新显示内容时,通常会调用这个函数。下面是对 `lv_obj_redraw` 函数的详细分析:
函数原型
/**
* 重绘一个对象及其所有子对象。
* @param layer 指向需要重绘的图层的指针。
* @param obj 指向需要重绘的对象的指针。
*/
void lv_obj_redraw(lv_layer_t * layer, lv_obj_t * obj)
{
// 保存原始裁剪区域,以便之后恢复
lv_area_t clip_area_ori = layer->clip_area;
// 计算对象的坐标,并增加绘制尺寸(考虑到对象的外部装饰,如边框)
lv_area_t obj_coords_ext;
lv_obj_get_coords(obj, &obj_coords_ext);
int32_t ext_draw_size = _lv_obj_get_ext_draw_size(obj);
lv_area_increase(&obj_coords_ext, ext_draw_size, ext_draw_size);
// 计算对象在裁剪区域内的坐标
lv_area_t clip_coords_for_obj;
if(!_lv_area_intersect(&clip_coords_for_obj, &clip_area_ori, &obj_coords_ext)) return;
// 如果对象不在当前裁剪区域内,则不需要重绘
// 更新当前裁剪区域为对象在裁剪区域内的坐标
layer->clip_area = clip_coords_for_obj;
// 发送绘制事件,通知对象需要重绘
lv_obj_send_event(obj, LV_EVENT_DRAW_MAIN_BEGIN, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_MAIN, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_MAIN_END, layer);
#if LV_USE_REFR_DEBUG
// 如果启用了调试模式,绘制一个彩色边框来标识重绘区域
lv_color_t debug_color = lv_color_make(lv_rand(0, 0xFF), lv_rand(0, 0xFF), lv_rand(0, 0xFF));
lv_draw_rect_dsc_t draw_dsc;
lv_draw_rect_dsc_init(&draw_dsc);
draw_dsc.bg_color = debug_color;
draw_dsc.bg_opa = LV_OPA_20;
draw_dsc.border_width = 1;
draw_dsc.border_opa = LV_OPA_30;
draw_dsc.border_color = debug_color;
lv_draw_rect(layer, &draw_dsc, &obj_coords_ext);
#endif
// 根据对象的溢出属性决定使用的坐标
const lv_area_t * obj_coords;
if(lv_obj_has_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE)) {
obj_coords = &obj_coords_ext;
} else {
obj_coords = &obj->coords;
}
// 计算子对象的裁剪区域
lv_area_t clip_coords_for_children;
bool refr_children = true;
if(!_lv_area_intersect(&clip_coords_for_children, &clip_area_ori, obj_coords)) {
refr_children = false;
}
// 如果子对象在裁剪区域内,则重绘子对象
if(refr_children) {
uint32_t i;
uint32_t child_cnt = lv_obj_get_child_count(obj);
if(child_cnt == 0) {
// 如果没有子对象,直接调用后绘制事件
layer->clip_area = clip_coords_for_obj;
lv_obj_send_event(obj, LV_EVENT_DRAW_POST_BEGIN, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_POST, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_POST_END, layer);
} else {
layer->clip_area = clip_coords_for_children;
// 遍历并重绘所有子对象
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = obj->spec_attr->children[i];
refr_obj(layer, child);
}
// 调用后绘制事件
lv_obj_send_event(obj, LV_EVENT_DRAW_POST_BEGIN, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_POST, layer);
lv_obj_send_event(obj, LV_EVENT_DRAW_POST_END, layer);
}
}
// 恢复原始裁剪区域
layer->clip_area = clip_area_ori;
}
参数说明
- `layer`: 指向需要重绘的图层的指针。在 LVGL 中,对象(`lv_obj_t`)通常属于某个特定的图层(`lv_layer_t`),例如顶层或者某些特定的子层。
- `obj`: 指向需要重绘的对象的指针。
功能描述
`lv_obj_redraw` 函数的主要作用是告诉 LVGL 需要重新绘制指定的对象。这个函数会触发一系列的绘制事件和回调,最终导致对象及其子对象的重绘。
工作原理
1. 计算裁剪区域: 函数首先会计算对象的裁剪区域,这是对象在图层中可见的部分。它会考虑对象的大小、位置以及任何可能的裁剪设置。
2. 发送绘制事件: 接着,函数会向对象发送绘制事件,如 `LV_EVENT_DRAW_MAIN_BEGIN`、`LV_EVENT_DRAW_MAIN` 和 `LV_EVENT_DRAW_MAIN_END`。这些事件通常会导致对象的 `draw` 方法被调用,从而执行实际的绘制操作。
3. 处理子对象: 如果对象有子对象,并且对象设置了 `LV_OBJ_FLAG_OVERFLOW_VISIBLE` 标志,那么这些子对象也会被重绘。这确保了当父对象部分可见时,其子对象也能正确显示。
4. 调试模式下的额外操作: 如果 LVGL 编译时启用了调试模式(`LV_USE_REFR_DEBUG`),函数会在重绘的区域周围绘制一个彩色边框,以便于开发者观察重绘的范围。
5. 更新显示: 最后,如果对象的图层是活动的图层,那么重绘的内容会被更新到显示设备上。这可能涉及到与显示设备驱动程序的交互,例如调用 `draw_buf_flush` 函数来刷新缓冲区。
使用场景
- 属性变更: 当对象的颜色、大小或其他属性发生变化时,可以通过调用 `lv_obj_redraw` 来更新显示效果。
- 动态内容: 对于动态变化的内容(如进度条、滚动列表等),在内容更新后调用 `lv_obj_redraw` 可以确保显示内容与实际数据保持同步。
- 交互反馈: 在用户交互(如按钮按下、滑动等)后,可能需要更新对象的显示状态,此时也可以使用 `lv_obj_redraw`。
注意事项
- 性能考虑: 频繁调用 `lv_obj_redraw` 可能会影响性能,特别是在有大量对象需要重绘的情况下。因此,应当合理安排重绘时机,避免不必要的性能开销。
- 对象层次: 在某些情况下,重绘一个对象可能会影响到其子对象或相邻对象的显示。因此,在调用 `lv_obj_redraw` 时,需要考虑到对象之间的层次关系和相互影响。
通过以上分析,我们可以看到 `lv_obj_redraw` 是 LVGL 中用于管理对象重绘的重要工具。正确使用这个函数可以帮助开发者创建出高效且响应迅速的用户界面。
4. _lv_inv_area:
用于标记一个需要无效化的区域。这个函数会检查屏幕是否支持无效化,并更新内部的无效化区域列表。_lv_inv_area
函数是 LVGL 图形库中用于将一个指定区域标记为无效的内部函数。这个函数是屏幕刷新机制的一部分,用于指示 LVGL 系统在下一次刷新周期中需要重绘这个区域。
函数原型
/**
* 将一个区域标记为无效,以便在下一次刷新时重绘
* @param disp 指向显示屏的指针,如果为 NULL,则使用默认的显示屏
* @param area_p 指向要标记为无效的区域的指针,如果为 NULL,则标记整个屏幕
*/
void _lv_inv_area(lv_display_t * disp, const lv_area_t * area_p)
{
// 如果没有指定显示屏,则使用默认的显示屏
if(!disp) disp = lv_display_get_default();
if(!disp) return; // 如果没有找到显示屏,则直接返回
// 检查显示屏是否允许无效化区域
if(!lv_display_is_invalidation_enabled(disp)) return;
// 确保在渲染过程中不允许无效化区域
LV_ASSERT_MSG(!disp->rendering_in_progress, "Invalidate area is not allowed during rendering.");
// 如果 area_p 为 NULL,则清除所有无效区域
if(area_p == NULL) {
disp->inv_p = 0; // 重置无效区域计数
return;
}
// 获取屏幕的分辨率
lv_area_t scr_area;
scr_area.x1 = 0;
scr_area.y1 = 0;
scr_area.x2 = lv_display_get_horizontal_resolution(disp) - 1;
scr_area.y2 = lv_display_get_vertical_resolution(disp) - 1;
// 计算与屏幕相交的区域
lv_area_t com_area;
bool suc = _lv_area_intersect(&com_area, area_p, &scr_area);
if(suc == false) return; // 如果区域不与屏幕相交,则直接返回
// 如果是全刷新模式,则只保存整个屏幕区域
if(disp->render_mode == LV_DISPLAY_RENDER_MODE_FULL) {
disp->inv_areas[0] = scr_area; // 将整个屏幕区域设置为第一个无效区域
disp->inv_p = 1; // 设置无效区域计数为 1
lv_display_send_event(disp, LV_EVENT_REFR_REQUEST, NULL); // 发送刷新请求事件
return;
}
// 发送无效化区域事件到显示屏
lv_result_t res = lv_display_send_event(disp, LV_EVENT_INVALIDATE_AREA, &com_area);
if(res != LV_RESULT_OK) return;
// 检查新区域是否已经存在于无效区域列表中
uint16_t i;
for(i = 0; i < disp->inv_p; i++) {
if(_lv_area_is_in(&com_area, &disp->inv_areas[i], 0) != false) return;
}
// 将新区域添加到无效区域列表中
lv_area_t * tmp_area_p = &com_area;
if(disp->inv_p >= LV_INV_BUF_SIZE) { // 如果无效区域缓冲区已满,则只保留整个屏幕区域
disp->inv_p = 0;
tmp_area_p = &scr_area;
}
lv_area_copy(&disp->inv_areas[disp->inv_p], tmp_area_p);
disp->inv_p++; // 增加无效区域计数
// 发送刷新请求事件
lv_display_send_event(disp, LV_EVENT_REFR_REQUEST, NULL);
}
-
函数参数:
lv_display_t * disp
: 指向显示屏的指针,如果为 NU