表盘
1、界面构成
-
背景图片
-
温度海拔标签控件
-
电池图标文字控件(图片)
-
年月日标签控件
-
时分标签控件依托于容器 秒led依托于容器
-
运动图标图片
-
运动步数标签控件
一共分为三大部分,图片,标签控件,容器.这个页面详细说一下,后续的7个页面就不太详细介绍了,都是一些LVGL的基础知识,直接按照图示去做就行了.
按照前面说的,每个页面其实还是四个主要的事件,分别是初始化,循环,触发,退出四个事件.这里就不细说逻辑了,按照这4个事件简单介绍一下.以后的页面都是这个框架.
2、初始化事件
初始化函数仅当进入该页面时会调用一次.所以承担了该页面内所有元素的初始化设定工作.
2.1、图片
整幅页面一共3个图片,背景图片和运动图片及电池电量图片.
2.1.1、背景图片
这个没什么好说的,就是一个很简单的存放图片而已.
static void background_image_creat(void)
{
lv_obj_t * p_bg_img = NULL;
LV_IMG_DECLARE(IMG_BG);
p_bg_img = lv_img_create(app_window,NULL);
lv_img_set_src(p_bg_img,&IMG_BG);
lv_obj_align(p_bg_img,NULL,LV_ALIGN_CENTER,0,0);
}
2.1.2、运动图片
这也是一个很简单的图片,代码如下.
LV_IMG_DECLARE(ImgRun);
imgRun = lv_img_create(app_window, NULL);
lv_img_set_src(imgRun, &ImgRun);
lv_obj_align(imgRun, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 20, -20);
2.1.3、电池电量图片
这里的电池电量图片是LVGL内置的图标字体,一共使用了5个图标字体.初始化如下.
label_batt = lv_label_create(appWindow, NULL);
lv_label_set_recolor(label_batt, true);
lv_label_set_text(label_batt, "#FFFFFF "LV_SYMBOL_BATTERY_EMPTY"#");
lv_obj_align(label_batt, NULL, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
lv_obj_set_auto_realign(label_batt, true);
taskTopBarUpdate = lv_task_create(Task_TopBarUpdate, 2 * 1000, LV_TASK_PRIO_LOW, NULL);
Task_TopBarUpdate(taskTopBarUpdate);
初始化的时候还创建了一个2S周期的任务,主要作用是根据电池电量去判断显示哪一个图标字体.具体内容如下所示.
static void Task_TopBarUpdate(lv_task_t * task)
{
/*读取电池电压*/
float battVoltage = (float)get_adc_value() / 4095.0f * 3.3f * 2;
/*电池图标组*/
const char * battSymbol[] =
{
LV_SYMBOL_BATTERY_EMPTY,
LV_SYMBOL_BATTERY_1,
LV_SYMBOL_BATTERY_2,
LV_SYMBOL_BATTERY_3,
LV_SYMBOL_BATTERY_FULL
};
/*电压映射到图标索引*/
int symIndex = fmap(
battVoltage,
2.8f, 4.2f,
0, __Sizeof(battSymbol)
);
__LimitValue(symIndex, 0, __Sizeof(battSymbol) - 1);
lv_label_set_text_fmt(labelBatt, "#FFFFFF %s#", battSymbol[symIndex]);
}
2.2、容器
整幅页面也一共两个容器,其中一个容器就是在初始化的时候建立的用作背景板的容器,另外一个容器是中间的大红框部分.
第一个就不讲了,没什么好说的, 第二个容器的作用主要是为了时间跳变时候的动画准备的.主要说一下这个的实现.
这里初始化对应的内容就是最大的红框内的内容.
容器初始化
contTime = lv_cont_create(appWindow, NULL);
lv_cont_set_style(contTime, LV_CONT_STYLE_MAIN, &lv_style_transp);
lv_obj_set_size(contTime, 130, 80);
lv_obj_align(contTime, NULL, LV_ALIGN_CENTER, 0, 0);
秒初始化,使用了两个lvgl内置的led来回闪烁表示秒.
/*ledSec*/
static lv_style_t led_style;
led_style = lv_style_pretty_color;
led_style.body.main_color = LV_COLOR_RED;
led_style.body.grad_color = LV_COLOR_RED;
for(int i = 0; i < __Sizeof(ledSec); i++)
{
lv_obj_t * led = lv_led_create(contTime, NULL);
lv_led_set_style(led, LV_LED_STYLE_MAIN, &led_style);
lv_obj_set_size(led, 8, 10);
lv_obj_align(led, NULL, LV_ALIGN_CENTER, 0, i == 0 ? -10 : 10);
ledSec[i] = led;
}
小时、分钟初始化,这里一共使用了两个数组,这两个数组之间来回切换的动画配合这个小容器一起实现了时间变动时的动画效果.
static lv_obj_t * labelTime_Grp[4];
static lv_obj_t * labelTime_Grp2[4];
/*labelTime*/
LV_FONT_DECLARE(Morganite_100);
static lv_style_t time_style;
time_style = lv_style_plain;
time_style.text.font = &Morganite_100;
time_style.text.color = LV_COLOR_WHITE;
const lv_coord_t x_mod[4] = {-45, -20, 20, 45};
for(int i = 0; i < __Sizeof(labelTime_Grp); i++)
{
lv_obj_t * label = lv_label_create(contTime, NULL);
lv_label_set_style(label, LV_LABEL_STYLE_MAIN, &time_style);
lv_label_set_text(label, "0");
lv_obj_align(label, NULL, LV_ALIGN_CENTER, x_mod[i], 0);
labelTime_Grp[i] = label;
}
for(int i = 0; i < __Sizeof(labelTime_Grp2); i++)
{
lv_obj_t * label = lv_label_create(contTime, NULL);
lv_label_set_style(label, LV_LABEL_STYLE_MAIN, &time_style);
lv_label_set_text(label, "0");
lv_obj_align(label, labelTime_Grp[i], LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
labelTime_Grp2[i] = label;
}
/*时间清零*/
memset(&RTC_TimeLast, 0, sizeof(RTC_TimeLast));
/*注册时间标签更新任务,执行周期500ms*/
taskTimeUpdate = lv_task_create(Task_TimeUpdate, 500, LV_TASK_PRIO_MID, NULL);
Task_TimeUpdate(taskTimeUpdate);
这里创建了一个LVGL系统内
的任务,任务以2HZ的频率进行更新.主要作用是更新时间及添加时间变动时的动画效果.
2.3、 标签控件
这个就很简单了,也没什么复杂的逻辑,直接按照图示的贴上去就行.
备注: 这个项目做到现在感觉最难的地方更像是美工设计了…
3、循环事件
这里的页面刷新都放在了系统的一个线程中了,也没有什么特殊需求,所以并没有使用循环事件.
4、触发事件
这个触发事件其实是按键按下后的对应操作,在按键按下后会触发一个系统内的线程将按键消息封装打包发送,后续经过一系列处理会调用该事件,通过判断是哪个按键,按键的事件是什么进行对应操作.该页面的触发事件如下所示.
static void dial_plate_event(void *_p,int _l_val)
{
if((_p == p_ok_btn)&&(_l_val == LV_EVENT_RELEASED))
page.push(PAGE_MainMenu);
}
逻辑也很简单,确认键单次按下后,会进入main
页面.
5、退出事件
进入其他页面前做的最后一件事就是执行该页面内的退出函数,主要内容是清空本页面及对页面内的内存做出处理(如有必要).该页面示例如下.
static void dial_plate_exit()
{
/*关任务*/
lv_task_del(taskTimeUpdate);
lv_task_del(taskTopBarUpdate);
/*删除此页面上的子控件*/
lv_obj_clean(app_window);
}
备注: 剩余所有页面和该页面原理完全相同,如果没有特殊内容则将简略一笔带过.