影片可以說是 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
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/497675.html
標籤:其他
上一篇:Linux 磁盤型別和結構
下一篇:MariaDB的安裝與配置
