4、LVGL样式系统(Style):从概念到动画
样式系统,说白了就是LVGL的「皮肤」。
我刚开始接触LVGL时,觉得样式不就是改改颜色吗?后来才发现,样式系统的设计哲学,直接决定了你的UI是「精致」还是「粗糙」。今天咱们就把样式这块彻底讲透。
4.1 样式的概念
在LVGL里,样式是一组属性的集合。你可以把它想象成一件「衣服」——给按钮穿上红色,给标签穿上蓝色。
每个样式对象(lv_style_t)可以包含多个属性,比如背景色、边框宽度、圆角半径等等。你创建好样式,然后把它「应用」到控件上。
核心思想:样式与控件分离。样式是独立的,可以被多个控件共享。
我习惯把样式比作「CSS类」。你写CSS时,不会在每个标签里写style属性,而是定义一个class,然后复用。LVGL的样式也是这个道理。
4.2 样式属性详解
样式属性太多了,我挑几个最常用的讲。完整的列表可以去官方文档查,但下面这些你一定会用到。
| 属性类别 | 常用属性 | 说明 |
|---|---|---|
| 背景 | bg_color, bg_opa, bg_grad_color |
背景色、透明度、渐变色 |
| 边框 | border_color, border_width, border_side |
边框颜色、宽度、显示哪几条边 |
| 文本 | text_color, text_font, text_align |
文字颜色、字体、对齐方式 |
| 尺寸 | width, height, pad_top, pad_left |
宽高、内边距 |
| 圆角 | radius |
圆角半径,设为LV_RADIUS_CIRCLE就是圆形 |
| 阴影 | shadow_color, shadow_width, shadow_ofs_x |
阴影颜色、宽度、偏移 |
举个例子,创建一个带渐变背景的按钮样式:
lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2196F3));
lv_style_set_bg_grad_color(&style_btn, lv_color_hex(0x1565C0));
lv_style_set_bg_grad_dir(&style_btn, LV_GRAD_DIR_VER);
lv_style_set_radius(&style_btn, 8);
lv_style_set_border_width(&style_btn, 0);
嗯,这里要注意:lv_style_set_xxx函数只设置一个属性,不会影响其他属性。所以你可以分多次设置,也可以一次性设置多个。
4.3 样式状态(State)
样式状态,是LVGL里非常强大的一个特性。
一个控件可以处于不同状态,比如默认、按下、选中、禁用等。每种状态都可以有独立的样式。
常见的状态有:
LV_STATE_DEFAULT:默认状态LV_STATE_PRESSED:按下状态LV_STATE_FOCUSED:聚焦状态LV_STATE_DISABLED:禁用状态LV_STATE_CHECKED:选中状态(比如复选框)
你可以为同一个控件在不同状态下设置不同样式。比如按钮按下时颜色变深:
lv_style_t style_btn_pr;
lv_style_init(&style_btn_pr);
lv_style_set_bg_color(&style_btn_pr, lv_color_hex(0x1565C0));
lv_obj_add_style(btn, &style_btn, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &style_btn_pr, LV_STATE_PRESSED);
我曾经踩过一个坑:给按钮添加按下样式时,忘了设置LV_STATE_PRESSED,结果按下去没反应。后来才发现,状态必须明确指定,默认不会自动继承。
避坑指南:状态样式的优先级高于默认样式。如果你给LV_STATE_PRESSED设置了背景色,按下时就会覆盖默认背景色。但如果你只设置了边框颜色,那背景色还是会用默认的。
4.4 样式继承与层叠
样式可以继承,也可以层叠。这是LVGL样式系统的精髓。
继承:子控件可以继承父控件的样式属性。比如你给屏幕设置了字体,屏幕上的所有标签都会用这个字体,除非它们自己设置了。
层叠:多个样式可以叠加到同一个控件上。后添加的样式优先级更高。
举个例子:
lv_style_t style_base;
lv_style_init(&style_base);
lv_style_set_text_font(&style_base, &lv_font_montserrat_14);
lv_style_t style_red;
lv_style_init(&style_red);
lv_style_set_text_color(&style_red, lv_color_hex(0xFF0000));
lv_obj_add_style(label, &style_base, 0);
lv_obj_add_style(label, &style_red, 0);
这里style_red后添加,所以文字颜色用红色,但字体还是用style_base的。说白了,就是「后浪推前浪」——后添加的样式覆盖前面同名的属性。
我个人的习惯是:把公共属性放在基础样式里,把特殊属性放在独立样式里。这样代码复用率高,也容易维护。
4.5 本地样式与全局样式
本地样式和全局样式,区别在于作用范围。
本地样式:直接通过lv_obj_set_style_xxx函数设置,只影响当前控件。
lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF0000), LV_STATE_DEFAULT);
全局样式:通过lv_style_t对象设置,可以被多个控件共享。
lv_style_t style_btn;
lv_style_set_bg_color(&style_btn, lv_color_hex(0xFF0000));
lv_obj_add_style(btn1, &style_btn, 0);
lv_obj_add_style(btn2, &style_btn, 0);
你想想看,如果我有100个按钮都要用同样的样式,用本地样式就得写100次。用全局样式,一次定义,到处使用。是不是省事多了?
小技巧:全局样式可以放在一个单独的文件里,比如styles.c。这样所有控件都能引用,也方便统一修改主题。
4.6 样式动画
样式动画,是让UI「活」起来的关键。
LVGL的动画系统可以让你平滑地改变样式属性。比如按钮按下时背景色渐变,而不是瞬间跳变。
基本用法:
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, btn);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_style_bg_color);
lv_anim_set_values(&a, lv_color_hex(0x2196F3), lv_color_hex(0x1565C0));
lv_anim_set_time(&a, 300);
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
lv_anim_start(&a);
这里要注意几个参数:
set_var:指定动画作用的对象set_exec_cb:指定要改变的属性函数set_values:起始值和结束值set_time:动画持续时间(毫秒)set_path_cb:动画曲线,比如ease_out就是先快后慢
我曾经做过一个开关控件,切换时背景色从灰色渐变到绿色,同时滑块位置也平滑移动。用户反馈说「手感很好」。其实背后就是两个动画同时运行——一个改背景色,一个改位置。
核心思路:动画不是魔法,就是在一段时间内不断改变某个属性的值。LVGL帮你做了插值计算,你只需要告诉它「从哪到哪,花多长时间」。
嗯,这里还要提一下:动画可以组合使用。比如同时改变位置和透明度,实现淡入淡出效果。也可以设置延迟,让动画按顺序播放。
最后说一句:样式动画虽然好用,但别滥用。每个动画都会占用CPU资源。如果你有几十个控件同时做动画,帧率可能会掉。我一般只在交互反馈和页面切换时使用动画,日常状态保持静态。
好了,样式系统就讲到这里。下一章咱们聊聊「主题管理」,看看怎么把样式组织成一套完整的视觉体系。