动画可以说是 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库入门教程 - 动画的更多相关文章

  1. LVGL库入门教程01-移植到STM32(触摸屏)

    LVGL库移植STM32 LVGL库简介 LVGL(Light and Versatile Graphics Library)是一个免费.开源的嵌入式图形库,可以创建丰富.美观的界面,具有许多可以自定 ...

  2. LVGL库入门教程02-基本控件与交互

    LVGL 本质上是一个 GUI 库,它包含大量的控件(widget),即按钮.标签.滑块.菜单栏这种具有一定人机交互特征的组合图形.LVGL 在设计时,采用了一定面向对象编程的设计思路,有效降低了代码 ...

  3. LVGL库入门教程04-样式

    LVGL样式 LVGL样式概述 创建样式 在 LVGL 中,样式都是以对象的方式存在,一个对象可以描述一种样式.每个控件都可以独立添加样式,创建的样式之间互不影响. 可以使用 lv_style_t 类 ...

  4. LVGL库入门教程03-布局方式

    LVGL布局方式 LVGL的布局 上一节介绍了如何在 LVGL 中创建控件.如果在创建控件时不给控件安排布局,那么控件默认会被放在父容器的左上角. 可以使用 lv_obj_set_pos(obj, x ...

  5. LVGL库入门教程 - 颜色和图像

    颜色 构造颜色 在 LVGL 中,颜色以结构 lv_color_t 表示.在最开始移植整个工程时,曾经在 lv_conf.h 中修改过颜色深度: /*Color depth: 1 (1 byte pe ...

  6. 【转】Subversion快速入门教程-动画演示

    如何快速建立Subversion服务器,并且在项目中使用起来,这是大家最关心的问题,与CVS相比,Subversion有更多的选择,也更加的容易,几个命令就可以建立一套服务器环境,可以使用起来,这里配 ...

  7. 轻量级C语言实现的minixml解析库入门教程

    svn上的minixml源码下载.  svn co http://svn.msweet.org/mxml/tags/release-2.7/ 按照下载回来的源代码进行编译和安装.本教程只针对新手做一个 ...

  8. (转)轻量级C语言实现的minixml解析库入门教程

    svn上的minixml源码下载:svn co http://svn.msweet.org/mxml/tags/release-2.7/ 按照下载回来的源代码进行编译和安装.本教程只针对新手做一个引导 ...

  9. C语言实现的minixml解析库入门教程

    minixml的中文说明手册:MiniXML中文文档.dochttp://wenku.baidu.com/view/25fd7d7f31b765ce050814f7.html xml源代码: < ...

随机推荐

  1. Bootstrap Blazor 组件库 Row 布局组件(栅格系统)

    原文链接:https://www.cnblogs.com/ysmc/p/16133351.html 在 Bootstrap 中,栅格相信大家都很熟悉,简直就是布局神器啊,Bootstrap Blazo ...

  2. java第十二周作业

    1.定义一个点类Point, 包含2个成员变量x.y分别表示x和y坐标,2个构造器Point()和Point( intx0,y0),以及一个movePoint (int dx,intdy)方法实现点的 ...

  3. 新的 css 子选择器

    1. html 结构 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  4. 今天学弟问我pip如何永久换源?

    pip如何永久换源 临时使用 我们在使用Python开发的时候,经常要下载第三方模块,最常用的方式就是直接pip install 模块名,但是默认是使用国外的源,从pypi仓库中查找目标模块,不管是网 ...

  5. Python抽象基类:ABC谢谢你,因为有你,温暖了四季!

    Python抽象基类:ABC谢谢你,因为有你,温暖了四季! Python抽象基类:ABC谢谢你,因为有你,温暖了四季! 实例方法.类方法和静态方法 抽象类 具名元组 参考资料 最近阅读了<Pyt ...

  6. 创建第一个c程序

    创建,组织,生成 ,生成. 1.我们先创建一个win32项目. 文件->新建->项目->Visual C++ ->Win32   输入项目名称   选择项目保存位置 很重要的一 ...

  7. zookeeper篇-zookeeper客户端和服务端的基础命令

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 前提:我把zookeepee安装在了服务器/usr/local/java ...

  8. Mac IntelliJ IDEA插件开发,IDEA Plugin SDK路径

    On Mac, select application icon in /Applications/ 官方文档: Setting Up a Development Environment

  9. Windows平台安装SQLite3数据库

    Windows平台安装SQLite3数据库 话不多说,开始! 访问SQLite官网下载资源 在搜索引擎中键入SQLite3关键字寻找官网入口或直接点击此处前往SQLite官网,官网界面如下: 点击页面 ...

  10. [题解] 51 nod 1340 地铁环线

    不难看出这是一道差分约束的题目. 但是如果想按照通常的题目那样去建边的话,就会发现这句话--相邻两站的距离至少是1公里--建边后就直接让整个题出现了负环(默认是按求最短路建边),没法做了. 这时我们就 ...