LVGL库入门教程 - 动画
动画可以说是 LVGL 中的特色之一,不过在使用动画前,请确保单片机具有足够的性能来维持足够的帧率。
transition:过渡动画
当一个控件的状态发生改变时,可以让样式也发生变化以提醒用户。通过过渡动画(transition)可以让样式的改变更自然。例如,按钮在点击时,以及开关在切换时,都具有一小段的过渡动画。
过渡动画使用 lv_style_transition_dsc_t
结构描述。为了要设置过渡动画,需要提供以下信息:
- 哪些属性需要过渡
- 过渡前的延时
- 过渡持续的时间
- 过渡动画(以回调函数的形式提供)
这些信息和结构成员是一一对应的。除了直接给结构成员赋值外,也可以使用以下初始化函数一次性设置:
void lv_style_transition_dsc_init(
lv_style_transition_dsc_t* tr,
const lv_style_prop_t props[],
lv_anim_path_cb_t path_cb,
uint32_t time,
uint32_t delay,
void* user_data);
第一个参数需要提供被初始化的过渡动画结构,第二个参数数组和字符串一样需要以 0
结尾。例如,假设需要实现这样一个过渡效果:点击时背景颜色发生改变并拉长,那么相应的初始化过程为:
static lv_style_transition_dsc_t trans;
static const lv_style_prop_t trans_props[] = {
LV_STYLE_WIDTH, LV_STYLE_HEIGHT, LV_STYLE_BG_COLOR, 0,
};
lv_style_transition_dsc_init(&trans, trans_props,
lv_anim_path_ease_in_out, 500, 0, NULL);
这里使用的过渡函数为 lv_anim_path_ease_in_out()
,这是一个内置的过渡效果,与之类似的过渡lv_anim_path_ease_out函数可以参考下表:
过渡函数 | 过渡效果 |
---|---|
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 |
和上一个类似,不过会比较快地多弹几次 |
lv_anim_path_step |
一步到位,和没动画的区别在于多了个延时 |
过渡动画是控件样式的一部分,可以将初始化得到的过渡动画描述应用到样式上:
static lv_style_t style_trans;
lv_style_init(&style_trans);
lv_style_set_transition(&style_trans, &trans);
过渡动画只有在两种样式切换时才会发生。例如,如果让以上样式应用在按下状态下:
lv_style_set_bg_color(&style_trans, lv_palette_main(LV_PALETTE_RED));
lv_style_set_width(&style_trans, 150);
lv_style_set_height(&style_trans, 60);
lv_obj_add_style(obj, &style_trans, LV_STATE_PRESSED);
那么只有在从其它状态变为按下时才会发生过渡:
注意松开时样式是突然转变的。如果要给这部分也添加一个过渡效果,可以给默认状态下的控件添加一个包含过渡的样式。
animate:通用动画
过渡只有在状态改变时才会发生,而动画可以在任意时刻进行。除此之外,两者的区别还有:过渡只是样式的一部分,而动画和样式之间是独立的。
实际上,过渡的底层也使用的是动画。
创建动画
为了创建动画,需要像样式一样声明一个动画类型并初始化:
lv_anim_t anim;
lv_anim_init(&anim);
由于动画是立即执行的,因此可以使用自动变量存储。然后,需要明确该动画将作用于哪一个控件:
lv_anim_set_var(&anim, obj);
接下来,可以设置动画的各种轨迹,包括:
- 动画需要改变什么属性
- 这些属性改变的范围
- 动画效果
- 延时和持续时间
动画的这些属性和过渡是类似的。例如,假设想做一个控件下落的动画,那么需要提供一个改变 y 坐标值的回调函数,这个函数可以直接使用 lv_obj_set_y()
,然后设定改变的始末值和运动轨迹,对应的代码为:
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_obj_set_y);
lv_anim_set_values(&anim, -100, 100);
lv_anim_set_path_cb(&anim, lv_anim_path_bounce);
lv_anim_set_time(&anim, 1000);
lv_anim_set_delay(&anim, 1000);
然后,可以在必要的时候执行动画:
lv_anim_start(&anim);
效果为:
关于延迟渲染
之前说过,样式是延迟渲染的,因此样式变量需要使用static
存储类型修饰符;而动画不是,动画从创建到执行是立即发生的。这也很好理解:样式在创建的过程中可能发生多次修改,因此需要确定最终的表现结果如何,再着手绘制,否则整个控件可能会重绘多次,占用大量无效的资源。
这种特点可能会带来许多意想不到的问题。例如,假设在lv_anim_set_values()
函数中去获取一个控件的位置、宽度等信息,由于它们都属于样式的一部分,此时还没有实际计算,因此得到的可能是默认值,造成动画始末效果偏离预期轨迹。
要解决这个问题,要么手动设置具体的值,要么让动画等到实际渲染发生了再执行,例如将其作为事件回调函数中的一部分。
更复杂的动画
以上创建的动画是单次不重复的,LVGL 提供了许多函数,可以为动画设置更复杂的属性。
这里介绍一个控件 bar ,它实质上就是没有 knob 部分的滑块,可以借用该控件来创建一个进度条(progress bar)动画。以下创建一个 bar 并将它的模式设定为 LV_BAR_MODE_RANGE
,这样就可以同时修改 indicator 两端的位置了:
lv_obj_t* bar = lv_bar_create(lv_scr_act());
lv_bar_set_mode(bar, LV_BAR_MODE_RANGE);
这里使用官方文档中提供的一个样式来使外观更好看,具体细节就无需解释了:
static lv_style_t style_bg;
static lv_style_t style_indic;
lv_style_init(&style_bg);
lv_style_set_border_color(&style_bg, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_border_width(&style_bg, 2);
lv_style_set_pad_all(&style_bg, 6);
lv_style_set_radius(&style_bg, 6);
lv_style_set_anim_time(&style_bg, 1000);
lv_style_init(&style_indic);
lv_style_set_bg_opa(&style_indic, LV_OPA_COVER);
lv_style_set_bg_color(&style_indic, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_radius(&style_indic, 3);
lv_obj_remove_style_all(bar);
lv_obj_add_style(bar, &style_bg, 0);
lv_obj_add_style(bar, &style_indic, LV_PART_INDICATOR);
lv_obj_set_size(bar, 200, 20);
然后就可以确定动画效果了。例如,这里期望的动画效果为:
那么首先可以编写一个改变属性的回调函数,例如改变 indicator 的范围:
static void anim_progress_load(void* obj, int32_t v) {
lv_bar_set_start_value(obj, v, LV_ANIM_ON);
lv_bar_set_value(obj, 20 + v, LV_ANIM_ON);
}
这些值在 0~80 范围内等速改变,持续时间 1.5 秒,无延时,对应的代码为:
lv_anim_set_exec_cb(&anim, anim_progress_load);
lv_anim_set_values(&anim, 0, 80);
lv_anim_set_path_cb(&anim, lv_anim_path_linear);
lv_anim_set_time(&anim, 1500);
lv_anim_set_delay(&anim, 0);
然后这里为其添加一个倒退和重复效果,这样动画就能来回播放了:
lv_anim_set_playback_time(&anim, 1500);
lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE);
实现的进度条动画就像以上 gif 展示的一样。除此之外,还可以修改更多动画的细节,例如:
函数 | 设置内容 |
---|---|
lv_anim_set_start_cb(anim, start_cb) |
在延时后、开始前执行一个函数 |
lv_anim_set_playback_delay(anim, delay) |
设置动画倒退前的延时 |
lv_anim_set_repeat_delay(anim, delay) |
设置动画重复前的延时 |
lv_anim_set_early_apply(&a, bool) |
是否将起始值应用到动画开始前,使动画执行时不会太突兀 |
更多的细节可以参考官方文档。
组合动画效果
有时候需要同时播放较多动画,此时如果逐个播放的话,需要逐个为动画设计延时,不方便安排。此时,可以使用 LVGL 提供的时间线(timeline)统一安排各个动画。
时间线的创建非常简单。首先,创建一系列动画,但先不调用 lv_anim_start()
让动画开始。
其次,创建一个时间线并将各个动画添加到时间线的某一时刻处:
lv_anim_timeline_t* anim_timeline = lv_anim_timeline_create();
lv_anim_timeline_add(anim_timeline, 0, &anim_axis);
lv_anim_timeline_add(anim_timeline, 100, &anim_obj_01);
lv_anim_timeline_add(anim_timeline, 1100, &anim_obj_02);
lv_anim_timeline_add(anim_timeline, 2100, &anim_obj_03);
lv_anim_timeline_add(anim_timeline, 300, &anim_label_01);
lv_anim_timeline_add(anim_timeline, 1300, &anim_label_02);
lv_anim_timeline_add(anim_timeline, 2300, &anim_label_03);
使用时间线时,无需为动画设计延时,只需要关注动画会在什么时刻播放,延时便会自动计算。
添加完毕后,再调用时间线的执行函数就可以了:
lv_anim_timeline_start(anim_timeline);
这样就可以创建很复杂的组合动画效果了:
使用时间线可以方便管理所有动画,可以将时间线上包含的所有动画停播、倒放、跳转等。以下列出了一些常用的时间线控制函数:
函数 | 用途 |
---|---|
lv_anim_timeline_stop(timeline) |
暂停播放当前的所有动画 |
lv_anim_timeline_set_reverse(timeline, bool) |
设置接下来的播放方向 |
lv_anim_timeline_set_progress(timeline, progress) |
跳转到播放进度 |
如果需要倒放,在设置了播放方向后还需要调用 lv_anim_timeline_start()
重新播放,并且会从当前位置倒放。
scroll:滚动动画
滚动的特点
滚动也是常见的一种动画效果。如果一个容器的尺寸不足以容纳它包含的控件,那么它就可以通过滚动来展示包含控件的所有部分。
为了使一个控件是可滚动的,它需要拥有标志 LV_OBJ_FLAG_SCROLLABLE
。清除该标志可以隐藏子控件的溢出部分。
滚动是可以冒泡的,如果一个控件已经滚动到底,再次对其尝试滚动将使滚动事件传播到父容器上。可以通过清除 LV_OBJ_FLAG_SCROLL_CHAIN
标志位去除这个性质。
可以通过 lv_obj_set_scroll_dir()
限制滚动的方向。例如:
lv_obj_set_scroll_dir(obj, LV_DIR_RIGHT);
那么就只能向右滚动到底,不能向左折回。
还可以通过以下几个函数利用代码执行滚动:
lv_obj_scroll_to(obj, x, y, anim_en);
lv_obj_scroll_by(obj, x, y, anim_en);
lv_obj_scroll_to_view(child, anim_en);
注意前两个函数的区别:前者是滚动到相应的位置,多次调用只有第一次实际有效;后者是模拟滚动的操作,实际滚动方向是相反的,并且多次调用效果可以叠加。除此之外,后者甚至可以滚动到超出子控件的范围之外。最后一个函数自动滚动到合适的位置,确保子控件可视。
这几个函数都不受滚动方向的约束。它们都具有第三个参数,用于指定滚动时是否提供滚动动画。
滚动动画
滚动是有动画的,默认情况下,滚动动画的特点表现在以下几点:
- 滚动是具有惯性的,意思是当输入设备停止交互时,控件还会继续向前滚动一小段距离。可以通过清除
LV_OBJ_FLAG_SCROLL_MOMENTUM
标志位取消这个特征 - 滚动是具有弹性的,当滚动到底时,继续尝试滚动会使控件超出一定范围,松开后回弹。可以通过清除
LV_OBJ_FLAG_SCROLL_ELASTIC
标志位取消这个特征 - 除此之外,以上介绍的两个代码实现滚动的函数,如果在第三个参数中应用滚动,那么会发生一小段 easy-out 的切换动画
还可以设置一种特殊的滚动效果 snap ,它使滚动时可以自动对齐。为了启用这种效果,需要添加 LV_OBJ_FLAG_SNAPPABLE
标志位,然后设置对齐的方式:
lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_START);
这样便可以按开始位置对齐了:
还可以配合 LV_OBJ_FLAG_SCROLL_ONE
标志位一次只滚过最多一个控件的位置。
在滚动时,会触发 LV_EVENT_SCROLL
事件,可以通过在该事件回调函数中对包含的子控件做变换,实现更复杂的滚动效果。
例如,以下在事件回调函数内,根据每个子控件当前位置的纵坐标对横坐标做一些变换:
static scrool_widget_cb(lv_event_t* e) {
lv_obj_t* cont = lv_event_get_target(e);
uint32_t child_cnt = lv_obj_get_child_cnt(cont);
for (uint8_t i = 0; i < child_cnt; i++) {
lv_obj_t* child = lv_obj_get_child(cont, i);
lv_obj_set_style_translate_x(child, child->coords.y1 * 0.5 - 60, 0);
}
}
然后让每次滚动时都做以上变换:
lv_obj_add_event_cb(cont, scrool_widget_cb, LV_EVENT_SCROLL, NULL);
这样就能实现斜方向的滚动效果了:
这里由于仅在事件中才修改按钮的水平位置,因此一开始控件的摆放不是倾斜的。要解决这个问题,可以添加以下代码:
lv_obj_scroll_to_view(lv_obj_get_child(cont, 0), LV_ANIM_OFF);
lv_event_send(cont, LV_EVENT_SCROLL, NULL);
前者使各个控件的坐标被计算,后者手动触发事件回调函数,利用计算出的坐标执行位置变换。
LVGL 的官方文档还给出了一个示例,可以实现类似圆形的旋转滚动,效果非常不错,不过涉及的计算较多,感兴趣的可以自行阅读官方文档。
滚动条
如果一个控件可以发生滚动,那么它就具有滚动条(scrollbar)。可以通过 lv_obj_set_scrollbar_mode()
函数修改滚动条的模式。例如,使用 LV_SCROLLBAR_MODE_OFF
模式可以使滚动条完全消失,就像上一张 gif 显示的那样。
滚动条是一个控件的 LV_PART_SCROLLBAR
部分,可以通过选择器给滚动条加上不同的样式。
首发于:http://frozencandles.fun/archives/425
参考资料/延伸阅读
https://docs.lvgl.io/master/overview/animation.html
https://docs.lvgl.io/master/overview/scroll.html
LVGL库入门教程 - 动画的更多相关文章
- LVGL库入门教程01-移植到STM32(触摸屏)
LVGL库移植STM32 LVGL库简介 LVGL(Light and Versatile Graphics Library)是一个免费.开源的嵌入式图形库,可以创建丰富.美观的界面,具有许多可以自定 ...
- LVGL库入门教程02-基本控件与交互
LVGL 本质上是一个 GUI 库,它包含大量的控件(widget),即按钮.标签.滑块.菜单栏这种具有一定人机交互特征的组合图形.LVGL 在设计时,采用了一定面向对象编程的设计思路,有效降低了代码 ...
- LVGL库入门教程04-样式
LVGL样式 LVGL样式概述 创建样式 在 LVGL 中,样式都是以对象的方式存在,一个对象可以描述一种样式.每个控件都可以独立添加样式,创建的样式之间互不影响. 可以使用 lv_style_t 类 ...
- LVGL库入门教程03-布局方式
LVGL布局方式 LVGL的布局 上一节介绍了如何在 LVGL 中创建控件.如果在创建控件时不给控件安排布局,那么控件默认会被放在父容器的左上角. 可以使用 lv_obj_set_pos(obj, x ...
- LVGL库入门教程 - 颜色和图像
颜色 构造颜色 在 LVGL 中,颜色以结构 lv_color_t 表示.在最开始移植整个工程时,曾经在 lv_conf.h 中修改过颜色深度: /*Color depth: 1 (1 byte pe ...
- 【转】Subversion快速入门教程-动画演示
如何快速建立Subversion服务器,并且在项目中使用起来,这是大家最关心的问题,与CVS相比,Subversion有更多的选择,也更加的容易,几个命令就可以建立一套服务器环境,可以使用起来,这里配 ...
- 轻量级C语言实现的minixml解析库入门教程
svn上的minixml源码下载. svn co http://svn.msweet.org/mxml/tags/release-2.7/ 按照下载回来的源代码进行编译和安装.本教程只针对新手做一个 ...
- (转)轻量级C语言实现的minixml解析库入门教程
svn上的minixml源码下载:svn co http://svn.msweet.org/mxml/tags/release-2.7/ 按照下载回来的源代码进行编译和安装.本教程只针对新手做一个引导 ...
- C语言实现的minixml解析库入门教程
minixml的中文说明手册:MiniXML中文文档.dochttp://wenku.baidu.com/view/25fd7d7f31b765ce050814f7.html xml源代码: < ...
随机推荐
- Bootstrap Blazor 组件库 Row 布局组件(栅格系统)
原文链接:https://www.cnblogs.com/ysmc/p/16133351.html 在 Bootstrap 中,栅格相信大家都很熟悉,简直就是布局神器啊,Bootstrap Blazo ...
- java第十二周作业
1.定义一个点类Point, 包含2个成员变量x.y分别表示x和y坐标,2个构造器Point()和Point( intx0,y0),以及一个movePoint (int dx,intdy)方法实现点的 ...
- 新的 css 子选择器
1. html 结构 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- 今天学弟问我pip如何永久换源?
pip如何永久换源 临时使用 我们在使用Python开发的时候,经常要下载第三方模块,最常用的方式就是直接pip install 模块名,但是默认是使用国外的源,从pypi仓库中查找目标模块,不管是网 ...
- Python抽象基类:ABC谢谢你,因为有你,温暖了四季!
Python抽象基类:ABC谢谢你,因为有你,温暖了四季! Python抽象基类:ABC谢谢你,因为有你,温暖了四季! 实例方法.类方法和静态方法 抽象类 具名元组 参考资料 最近阅读了<Pyt ...
- 创建第一个c程序
创建,组织,生成 ,生成. 1.我们先创建一个win32项目. 文件->新建->项目->Visual C++ ->Win32 输入项目名称 选择项目保存位置 很重要的一 ...
- zookeeper篇-zookeeper客户端和服务端的基础命令
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 前提:我把zookeepee安装在了服务器/usr/local/java ...
- Mac IntelliJ IDEA插件开发,IDEA Plugin SDK路径
On Mac, select application icon in /Applications/ 官方文档: Setting Up a Development Environment
- Windows平台安装SQLite3数据库
Windows平台安装SQLite3数据库 话不多说,开始! 访问SQLite官网下载资源 在搜索引擎中键入SQLite3关键字寻找官网入口或直接点击此处前往SQLite官网,官网界面如下: 点击页面 ...
- [题解] 51 nod 1340 地铁环线
不难看出这是一道差分约束的题目. 但是如果想按照通常的题目那样去建边的话,就会发现这句话--相邻两站的距离至少是1公里--建边后就直接让整个题出现了负环(默认是按求最短路建边),没法做了. 这时我们就 ...