简介#
LVGL Pro 允许你在编辑器中可视化地设计 UI 组件,但某些效果需要超出样式范围的运行时逻辑。本教程将介绍如何构建一个实现滚动平移效果的自定义控件——列表项根据滚动位置水平偏移,形成类似滚轮的 UI 效果。
你将在 XML 中定义控件结构,导出生成的 C 代码,并连接滚动事件回调函数来驱动视觉效果。
滚动平移示例展示了如何在滚动过程中动态重新定位子元素,以创建弧形或滚轮式的视觉效果。
术语说明#
在开始之前,了解 LVGL Pro 如何组织 UI 元素非常重要。
内置控件#
LVGL 包含现成的控件,如 lv_arc、lv_button 和 lv_label。
- 可在编辑器中直接使用
- 无需自定义实现
组件(Component)#
组件是由一个或多个对象组成的可复用 UI 模块。
- 完全在编辑器中定义
- 专注于布局和样式
- 无需自定义 C 逻辑
控件(Widget)#
控件在组件基础上扩展了用 C 实现的自定义行为。
在以下情况使用控件:
- 仅通过样式无法实现所需行为
- 需要事件驱动或动态逻辑
滚动平移需要运行时计算,因此非常适合作为自定义控件来实现。
滚动平移效果概述#
滚动平移效果使列表呈现滚轮般的行为:
- 列表项根据垂直位置水平偏移
- 靠近中心的项目更为突出
- 滚动过程中持续更新效果
创建基础控件#
- 打开 LVGL Pro
- 创建一个名为
wd_list的新控件 - 设计布局并根据需要应用样式
辅助组件:按钮#
滚动列表为每一行使用一个简单的按钮组件。定义此组件以便将其用作控件的子元素:
<component>
<previews>
<preview width="320" height="240" style_bg_color="0xeee" />
</previews>
<api>
<prop name="label" type="string" default="Label 1" />
</api>
<styles>
<style name="style_base" width="100%" pad_ver="20" radius="40" />
</styles>
<view extends="lv_button">
<style name="style_base" />
<lv_label text="$label" align="center" />
</view>
</component>xml定义控件 XML#
创建控件结构并暴露一个配置属性。
<widget>
<previews>
<preview width="320" height="240" style_bg_color="0xeee" />
</previews>
<api>
<prop name="translate_scroll" type="bool" default="false" />
</api>
<styles>
<style
name="style_base"
width="100%"
height="100%"
pad_all="10"
pad_row="10"
layout="flex"
flex_flow="column"
/>
</styles>
<view extends="lv_obj" scrollbar_mode="off">
<style name="style_base" />
</view>
</widget>xmltranslate_scroll 属性允许在不修改实现的情况下启用或禁用该效果。
导出生成的代码#
在 LVGL Pro 中使用 Export Code only 生成控件文件:
- wd_list.c在此添加自定义逻辑
- wd_list.h
- wd_list_gen.c自动生成,请勿编辑
- wd_list_gen.h自动生成,请勿编辑
- wd_list_private_gen.h自动生成,请勿编辑
- wd_list_xml_parser.c
以 _gen.* 结尾的文件会在导出时被覆盖。请始终在 wd_list.c 中添加自定义逻辑。
实现滚动逻辑#
添加滚动事件回调函数,根据每个子元素与容器中心的距离计算水平平移量:
static void scroll_event_cb(lv_event_t * e)
{
lv_obj_t * cont = lv_event_get_target_obj(e);
lv_area_t cont_a;
lv_obj_get_coords(cont, &cont_a);
int32_t cont_y_center = cont_a.y1 + lv_area_get_height(&cont_a) / 2;
int32_t r = lv_obj_get_height(cont) * 7 / 10;
int32_t child_cnt = (int32_t)lv_obj_get_child_count(cont);
for(int32_t i = 0; i < child_cnt; i++) {
lv_obj_t * child = lv_obj_get_child(cont, i);
lv_area_t child_a;
lv_obj_get_coords(child, &child_a);
int32_t child_y_center = child_a.y1 + lv_area_get_height(&child_a) / 2;
int32_t diff_y = LV_ABS(child_y_center - cont_y_center);
int32_t x;
if(diff_y >= r) {
x = r;
} else {
uint32_t x_sqr = r * r - diff_y * diff_y;
lv_sqrt_res_t res;
lv_sqrt(x_sqr, &res, 0x8000);
x = r - res.i;
}
lv_obj_set_style_translate_x(child, x, 0);
}
}c绑定事件#
绑定滚动回调函数并配置行为:
void wd_list_constructor_hook(lv_obj_t *obj)
{
wd_list_t * widget = (wd_list_t *)obj;
lv_obj_add_event_cb(obj, scroll_event_cb, LV_EVENT_SCROLL, widget);
lv_obj_add_event_cb(obj, scroll_event_cb, LV_EVENT_CHILD_CHANGED, widget);
lv_obj_set_scroll_dir(obj, LV_DIR_VER);
}cLV_EVENT_SCROLL在滚动过程中更新位置LV_EVENT_CHILD_CHANGED在添加或删除项目时更新布局- 滚动方向限制为垂直
绑定 API 属性#
将 translate_scroll XML 属性与 C 实现关联,以便在编辑器中进行切换。
XML 解析器#
void wd_list_xml_apply(lv_xml_parser_state_t * state, const char ** attrs)
{
void * item = lv_xml_state_get_item(state);
lv_xml_obj_apply(state, attrs);
for(int i = 0; attrs[i]; i += 2) {
const char * name = attrs[i];
const char * value = attrs[i + 1];
if(lv_streq("translate_scroll", name)) {
wd_list_set_translate_scroll(item, lv_xml_to_bool(value));
}
}
}c控件 API#
void wd_list_set_translate_scroll(lv_obj_t * wd_list, bool translate_scroll)
{
wd_list_t * widget = (wd_list_t *)wd_list;
widget->translate_scroll = translate_scroll;
lv_obj_send_event(wd_list, LV_EVENT_SCROLL, widget);
}c更新回调函数#
更新 scroll_event_cb 以响应 translate_scroll 属性:
static void scroll_event_cb(lv_event_t * e)
{
lv_obj_t * cont = lv_event_get_target_obj(e);
wd_list_t * widget = (wd_list_t *)lv_event_get_user_data(e);
/* ... 与之前相同的计算逻辑 ... */
lv_obj_set_style_translate_x(child, x, 0);
lv_obj_set_style_translate_x(child, widget->translate_scroll ? x : 0, 0);
/* ... */
}c效果展示#
从 LVGL Pro 导出代码并重新编译项目。该控件现在可以在任何 XML 布局中使用:
总结#
LVGL Pro 中的自定义控件弥合了可视化设计与运行时行为之间的差距。需要记住的关键原则:
- 组件处理静态布局和样式,在不需要自定义逻辑时使用
- 控件通过 C 回调函数扩展组件,实现动态行为
- 将自定义代码放在
wd_list.c中,切勿修改_gen.*文件 - 通过 XML API 属性暴露可配置的行为,以便在编辑器中进行控制

