又隔了一段时间了,上次发还是7月3号,正好两个月了🙃。
这篇简单过一下初始化的部分和控制模块。
想要全部代码的话问我就行。
初始化
代码如下:
void setup()
{
/* 调试串口 */
Serial.begin( 115200 );
/* 无线串口 */
Serial2.begin(460800);
/* 背光PWM调节引脚 */
ledcAttachPin(BL_PIN, 8);
ledcSetup(8, 100, 8);
ledcWrite(8, 128);
/* 关闭可能的wifi连接 */
WiFi.disconnect(false, true);
WiFi.mode(WIFI_OFF);
/* 初始化LVGL */
lv_init();
#if LV_USE_LOG != 0
lv_log_register_print_cb( my_print ); /* register print function for debugging */
#endif
tft.begin(); /* TFT init */
tft.setRotation( 1 ); /* Landscape orientation, flipped */
/*Set the touchscreen calibration data,
the actual data for your display can be aquired using
the Generic -> Touch_calibrate example from the TFT_eSPI library*/
uint16_t calData[5] = { 140, 3773, 282, 3550, 7 };
tft.setTouch( calData );
lv_disp_draw_buf_init( &draw_buf, buf1, NULL, screenWidth * 10 );
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init( &disp_drv );
/*Change the following line to your display resolution*/
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register( &disp_drv );
/*Initialize the (dummy) input device driver*/
static lv_indev_drv_t indev_drv;
lv_indev_drv_init( &indev_drv );
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register( &indev_drv );
/* Create simple label */
#if 0
lv_obj_t *label = lv_label_create( lv_scr_act() );
lv_label_set_text( label, LVGL_Arduino.c_str() );
lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );
#else
main_page();
#endif
Serial.println( "Setup done" );
}
arduino的初始化从setup
()函数开始,除了对引入的LVGL进行初始化以外,还需要对一些用到的外设进行配置。
首先是对两个uart做初始化,一个用于调试,一个用于无线通信。调试的波特率设的挺正常的,无线通信的为了速度快点,设到了NRF模块能接受的最高波特率460800,8N1配置下传1个字节大概19.5us。
然后对背光IO初始化,这里使用了22引脚。
#define BL_PIN 22
LEDC是ESP32的一个LED PWM控制器,我们这里就是用来控制背光亮度的。
用起来很简单,首先将通道8绑定到上面定义的22引脚上,接下来对通道8的操作就会作用到22引脚了。然后配置一下通道8的频率和分辨率,频率可以简单的理解为该通道一秒会给出多少次驱动脉冲,分辨率对我们来说,就是一个范围。例如我这里代码设置的是8,那么控制这个通道的亮度范围就是0-(2^8-1),也就是0-255。例如我们想控制这个通道对应的引脚的灯灭,就通过ledcWrite
往通道里面写0,控制这个通道对应的引脚的灯最亮,就通过ledcWrite往通道里面写255。这里初始化为128,也就是亮度为50%。
/* 背光PWM调节引脚 */
ledcAttachPin(BL_PIN, 8);
ledcSetup(8, 100, 8);
ledcWrite(8, 128);
在初始化完成之后,就会调用main_page
()函数来对三个主界面进行初始化。
main_page()代码如下:
void main_page(void)
{
/* 初始化tab栏样式 */
lv_style_init(&style_tab_view);
lv_style_set_bg_opa(&style_tab_view, LV_OPA_COVER);
lv_style_set_bg_color(&style_tab_view, lv_palette_lighten(LV_PALETTE_LIGHT_BLUE, 4));
/* 设置颜色梯度,实际效果不佳 */
// lv_style_set_bg_grad_color(&style_tab_view, lv_color_white());
// lv_style_set_bg_grad_dir(&style_tab_view, LV_GRAD_DIR_HOR);
/* 设置tab滚动文字栏样式 */
lv_style_init(&style_tab_view_text);
lv_style_set_text_color(&style_tab_view_text, lv_palette_main(LV_PALETTE_RED));
/* 创建tab */
tv = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 50);
/* 在tab左侧创建固定文字区 */
lv_obj_t * tab_btn = lv_tabview_get_tab_btns(tv);
lv_obj_set_style_text_font(tab_btn, &myfont_16, 0);
lv_obj_add_style(tab_btn, &style_tab_view, 0);
lv_obj_set_style_pad_left(tab_btn, LV_HOR_RES / 2, 0);
lv_obj_t * logo = lv_img_create(tab_btn);
lv_obj_set_style_radius(logo, LV_RADIUS_CIRCLE, 0);
LV_IMG_DECLARE(NCHU);
lv_img_set_src(logo, &NCHU);
lv_obj_align(logo, LV_ALIGN_LEFT_MID, -LV_HOR_RES / 2 + 25, 0);
lv_obj_t * label = lv_label_create(tab_btn);
lv_label_set_text(label, "LVGL V8");
lv_obj_align_to(label, logo, LV_ALIGN_OUT_RIGHT_TOP, 10, 0);
label = lv_label_create(tab_btn);
lv_label_set_text(label, "ESP32");
lv_obj_align_to(label, logo, LV_ALIGN_OUT_RIGHT_BOTTOM, 10 ,0);
mode_label = lv_label_create(tab_btn);
lv_obj_add_style(mode_label, &style_tab_view_text, 0);
lv_label_set_long_mode(mode_label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(mode_label, 75);
lv_label_set_text(mode_label, "模式待选择...");
lv_obj_align_to(mode_label, logo, LV_ALIGN_OUT_RIGHT_MID, 85, 0);
lv_obj_t * t1 = lv_tabview_add_tab(tv, "主界面");
lv_obj_t * t2 = lv_tabview_add_tab(tv, "分析");
lv_obj_t * t3 = lv_tabview_add_tab(tv, "配置");
main_create(t1);
analytics_create(t2);
config_create(t3);
static const char * btns[] ={"手动", "自动", ""};
lv_obj_t * mode_box = lv_msgbox_create(NULL, "模式选择", "请选择小车运行模式:)", btns, false);
lv_obj_set_style_text_font(mode_box, &myfont_16, 0);
lv_obj_add_event_cb(mode_box, mb_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_center(mode_box);
}
其实大部分是参考的官方demo。首先创建tab栏。顶部的是固定不变的,就是下图上面这一行。
样式是LVGL里最重要的模块之一。首先对一个样式初始化,然后设置样式的背景颜色。这里在设置背景颜色之前必须要调用
lv_style_set_bg_opa
函数将背景的透明度设置一下。因为默认的透明度是0,也就是完全透明,不设置一下这个就完全看不见了自己配的颜色了。
/* 初始化tab栏样式 */
lv_style_init(&style_tab_view);
lv_style_set_bg_opa(&style_tab_view, LV_OPA_COVER);
lv_style_set_bg_color(&style_tab_view, lv_palette_lighten(LV_PALETTE_LIGHT_BLUE, 4));
在创建了对应的控件后,就可以通过lv_obj_add_style
函数将初始化好的样式添加到对应的控件上,这样就可以对控件实现各种各样的效果。
/* 创建tab */
tv = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 50);
/* 在tab左侧创建固定文字区 */
lv_obj_t * tab_btn = lv_tabview_get_tab_btns(tv);
lv_obj_set_style_text_font(tab_btn, &myfont_16, 0);
lv_obj_add_style(tab_btn, &style_tab_view, 0);
这里创建了一个图片控件,将本地的一个图像文件导入进来,也就是上图中左上角的那个我们学校的校徽。这个也是官网提供的图片转换工具,后面单独讲。
lv_obj_t * logo = lv_img_create(tab_btn);
lv_obj_set_style_radius(logo, LV_RADIUS_CIRCLE, 0);
LV_IMG_DECLARE(NCHU);
lv_img_set_src(logo, &NCHU);
其他就是创建一些文本控件显示信息,还有设置它们的位置。然后通过lv_tabview_add_tab
函数给tab控件添加三个子tab窗口,这样就可以实现不同窗口的功能分区了。
lv_obj_t * t1 = lv_tabview_add_tab(tv, "主界面");
lv_obj_t * t2 = lv_tabview_add_tab(tv, "分析");
lv_obj_t * t3 = lv_tabview_add_tab(tv, "配置");
通过三个单独的函数对这三个界面进行初始渲染。
main_create(t1);
analytics_create(t2);
config_create(t3);
三个界面完成后就创建一个弹出框magbox,用于限制用户必须选择功能。
static const char * btns[] ={"手动", "自动", ""};
lv_obj_t * mode_box = lv_msgbox_create(NULL, "模式选择", "请选择小车运行模式:)", btns, false);
lv_obj_set_style_text_font(mode_box, &myfont_16, 0);
lv_obj_add_event_cb(mode_box, mb_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_center(mode_box);
也就是这个框。可以看到这里是没有取消按钮的,所以在没有选择其中一个按钮之前,用户没办法对这个系统做任何操作。lv_msgbox_create
函数的最后一个形参可以用来选择是否添加取消按钮,选择false就不会添加取消按钮。
控制模块
整个界面的代码如下:
static void main_create(lv_obj_t * parent)
{
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(parent, lv_palette_lighten(LV_PALETTE_LIGHT_BLUE, 4), 0);
lv_obj_set_style_bg_grad_color(parent, lv_palette_lighten(LV_PALETTE_DEEP_PURPLE, 4), 0);
lv_obj_set_style_bg_grad_dir(parent, LV_GRAD_DIR_VER, 0);
lv_obj_set_style_bg_grad_stop(parent, 160, 0);
/* 按键动画 */
static const lv_style_prop_t props[] = {LV_STYLE_BG_COLOR, LV_STYLE_BORDER_COLOR, LV_STYLE_BORDER_WIDTH, (lv_style_prop_t)0};
static lv_style_transition_dsc_t trans_def;
lv_style_transition_dsc_init(&trans_def, props, lv_anim_path_linear, 100, 200, NULL);
static lv_style_transition_dsc_t trans_pr;
lv_style_transition_dsc_init(&trans_pr, props, lv_anim_path_linear, 500, 0, NULL);
/* 控制按钮样式 */
lv_style_init(&style_control_btn);
lv_style_set_border_color(&style_control_btn, lv_color_white());
lv_style_set_border_width(&style_control_btn, 2);
lv_style_set_shadow_width(&style_control_btn, 10);
lv_style_set_shadow_ofs_x(&style_control_btn, 4);
lv_style_set_shadow_ofs_y(&style_control_btn, 4);
lv_style_set_shadow_opa(&style_control_btn, LV_OPA_60);
lv_style_set_radius(&style_control_btn, LV_RADIUS_CIRCLE);
lv_style_set_transition(&style_control_btn, &trans_def);
/* 按键按下后样式 */
lv_style_init(&style_control_btn_pr);
lv_style_set_bg_color(&style_control_btn_pr, lv_palette_main(LV_PALETTE_RED));
lv_style_set_border_width(&style_control_btn_pr, 6);
lv_style_set_border_color(&style_control_btn_pr, lv_palette_darken(LV_PALETTE_RED, 3));
lv_style_set_transition(&style_control_btn_pr, &trans_pr);
/* 图像panel样式 */
lv_style_init(&style_image_panel);
lv_style_set_border_width(&style_image_panel, 2);
lv_style_set_border_opa(&style_image_panel, LV_OPA_COVER);
lv_style_set_border_color(&style_image_panel, lv_palette_main(LV_PALETTE_GREY));
lv_style_set_radius(&style_image_panel, 10);
lv_style_set_bg_opa(&style_image_panel, LV_OPA_0);
/* 图像外框样式 */
lv_style_init(&style_outline);
lv_style_set_bg_opa(&style_outline, LV_OPA_80);
lv_style_set_bg_color(&style_outline, lv_color_white());
/* 控制小车按钮panel */
lv_obj_t * control_panel = lv_obj_create(parent);
lv_obj_set_width(control_panel, LV_SIZE_CONTENT);
lv_obj_set_height(control_panel, LV_PCT(100));
lv_obj_set_style_radius(control_panel, 10, 0);
lv_obj_set_style_border_color(control_panel, lv_palette_main(LV_PALETTE_GREY), 0);
lv_obj_set_style_bg_opa(control_panel, LV_OPA_0, 0);
/* 控制器上方的label标识 */
lv_obj_t * controler_label = lv_label_create(control_panel);
lv_obj_set_style_text_font(controler_label, &myfont_16, 0);
lv_label_set_text(controler_label, "控制器");
/* 前进按钮 */
lv_obj_t* btn_forward = lv_btn_create(control_panel);
lv_obj_set_height(btn_forward, LV_SIZE_CONTENT);
lv_obj_set_size(btn_forward, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_forward, &style_control_btn, 0);
lv_obj_add_style(btn_forward, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_forward, control_btn_event_cb, LV_EVENT_ALL, &user_data1);
lv_obj_t * control_label = lv_label_create(btn_forward);
lv_label_set_text(control_label, LV_SYMBOL_UP);
lv_obj_center(control_label);
/* 后退按钮 */
lv_obj_t* btn_back = lv_btn_create(control_panel);
lv_obj_set_height(btn_back, LV_SIZE_CONTENT);
lv_obj_set_size(btn_back, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_back, &style_control_btn, 0);
lv_obj_add_style(btn_back, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_back, control_btn_event_cb, LV_EVENT_ALL, &user_data2);
control_label = lv_label_create(btn_back);
lv_label_set_text(control_label, LV_SYMBOL_DOWN);
lv_obj_center(control_label);
/* 左转按钮 */
lv_obj_t* btn_left = lv_btn_create(control_panel);
lv_obj_set_height(btn_left, LV_SIZE_CONTENT);
lv_obj_set_size(btn_left, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_left, &style_control_btn, 0);
lv_obj_add_style(btn_left, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_left, control_btn_event_cb, LV_EVENT_ALL, &user_data3);
control_label = lv_label_create(btn_left);
lv_label_set_text(control_label, LV_SYMBOL_LEFT);
lv_obj_center(control_label);
/* 右转按钮 */
lv_obj_t* btn_right = lv_btn_create(control_panel);
lv_obj_set_height(btn_right, LV_SIZE_CONTENT);
lv_obj_set_size(btn_right, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_right, &style_control_btn, 0);
lv_obj_add_style(btn_right, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_right, control_btn_event_cb, LV_EVENT_ALL, &user_data4);
control_label = lv_label_create(btn_right);
lv_label_set_text(control_label, LV_SYMBOL_RIGHT);
lv_obj_center(control_label);
/* 图像显示panel */
lv_obj_t * image_panel = lv_obj_create(parent);
lv_obj_remove_style_all(image_panel);
lv_obj_set_width(image_panel, LV_PCT(100));
lv_obj_set_height(image_panel, LV_PCT(100));
lv_obj_add_style(image_panel, &style_image_panel, 0);
/* 图像的外框 */
lv_obj_t * iamge_outline = lv_obj_create(image_panel);
lv_obj_remove_style_all(iamge_outline);
lv_obj_set_size(iamge_outline, 188, 120);
lv_obj_add_style(iamge_outline, &style_outline, 0);
/* 图像的内框 */
track_line_left = lv_line_create(iamge_outline);
lv_obj_set_style_line_color(track_line_left, lv_palette_main(LV_PALETTE_RED), 0);
track_line_right = lv_line_create(iamge_outline);
lv_obj_set_style_line_color(track_line_right, lv_palette_main(LV_PALETTE_RED), 0);
/* 图像上方的label标识 */
lv_obj_t * iamge_label = lv_label_create(image_panel);
lv_obj_set_style_text_font(iamge_label, &myfont_16, 0);
lv_label_set_text(iamge_label, "图像");
/* 整体布局 */
static lv_coord_t col_dsc[] = { LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST };
static lv_coord_t row_dsc[] = { LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST };
/* image框布局 */
static lv_coord_t grid_1_col_dsc[] = { 11, LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST };
static lv_coord_t grid_1_row_dsc[] = { 30, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST };
/* 控制框布局 */
static lv_coord_t grid_2_col_dsc[] = { 48, 48, 48, LV_GRID_TEMPLATE_LAST };
static lv_coord_t grid_2_row_dsc[] = { 10, 48, 48, 48, LV_GRID_TEMPLATE_LAST };
lv_obj_set_grid_dsc_array(parent, col_dsc, row_dsc);
lv_obj_set_grid_cell(control_panel, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 0, 1);
lv_obj_set_grid_dsc_array(control_panel, grid_2_col_dsc, grid_2_row_dsc);
lv_obj_set_grid_cell(btn_forward, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_START, 1, 1);
lv_obj_set_grid_cell(btn_left, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 2, 1);
lv_obj_set_grid_cell(btn_right, LV_GRID_ALIGN_START, 2, 1, LV_GRID_ALIGN_START, 2, 1);
lv_obj_set_grid_cell(btn_back, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_START, 3, 1);
lv_obj_set_grid_cell(image_panel, LV_GRID_ALIGN_STRETCH, 1, 1, LV_GRID_ALIGN_STRETCH, 0, 1);
lv_obj_set_grid_dsc_array(image_panel, grid_1_col_dsc, grid_1_row_dsc);
lv_obj_set_grid_cell(iamge_label, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_END, 0, 1);
lv_obj_set_grid_cell(iamge_outline, LV_GRID_ALIGN_CENTER, 1, 1, LV_GRID_ALIGN_CENTER, 1, 1);
}
可以通过这一段代码来实现颜色的渐变,首先设置背景颜色,然后通过lv_obj_set_style_bg_grad_color
函数来设置想要渐变成什么颜色,lv_obj_set_style_bg_grad_dir
函数可以设置渐变的方向是竖直还是横向,lv_obj_set_style_bg_grad_stop
可以设置两种颜色的分割线的坐标。
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(parent, lv_palette_lighten(LV_PALETTE_LIGHT_BLUE, 4), 0);
lv_obj_set_style_bg_grad_color(parent, lv_palette_lighten(LV_PALETTE_DEEP_PURPLE, 4), 0);
lv_obj_set_style_bg_grad_dir(parent, LV_GRAD_DIR_VER, 0);
lv_obj_set_style_bg_grad_stop(parent, 160, 0);
可以通过这一段代码来设置动画,动画也是LVGL的一大特色。lv_style_transition_dsc_init
第四个参数代表动画所经历的时间,第五个参数代表动画在转变之前的延时时间。
/* 按键动画 */
static const lv_style_prop_t props[] = {LV_STYLE_BG_COLOR, LV_STYLE_BORDER_COLOR, LV_STYLE_BORDER_WIDTH, (lv_style_prop_t)0};
static lv_style_transition_dsc_t trans_def;
lv_style_transition_dsc_init(&trans_def, props, lv_anim_path_linear, 100, 200, NULL);
static lv_style_transition_dsc_t trans_pr;
lv_style_transition_dsc_init(&trans_pr, props, lv_anim_path_linear, 500, 0, NULL);
通过lv_style_set_transition
将动画的句柄配置进对应的样式中,在按下之后就可以呈现出所配置的动画效果。比如这里配置了默认的样式,然后又配置了按下后的样式,那么在按下后,LVGL就会根据上文代码中的lv_style_transition_dsc_init
的第四个和第五个参数来对默认样式到按下后样式进行转换。
/* 按键按下后样式 */
lv_style_init(&style_control_btn_pr);
lv_style_set_bg_color(&style_control_btn_pr, lv_palette_main(LV_PALETTE_RED));
lv_style_set_border_width(&style_control_btn_pr, 6);
lv_style_set_border_color(&style_control_btn_pr, lv_palette_darken(LV_PALETTE_RED, 3));
lv_style_set_transition(&style_control_btn_pr, &trans_pr);
每个按钮初始化之后绑定同一个回调函数,通过传入的参数来判别按钮类型,然后在回调函数中进行处理。
/* 前进按钮 */
lv_obj_t* btn_forward = lv_btn_create(control_panel);
lv_obj_set_height(btn_forward, LV_SIZE_CONTENT);
lv_obj_set_size(btn_forward, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_forward, &style_control_btn, 0);
lv_obj_add_style(btn_forward, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_forward, control_btn_event_cb, LV_EVENT_ALL, &user_data1);
lv_obj_t * control_label = lv_label_create(btn_forward);
lv_label_set_text(control_label, LV_SYMBOL_UP);
lv_obj_center(control_label);
/* 后退按钮 */
lv_obj_t* btn_back = lv_btn_create(control_panel);
lv_obj_set_height(btn_back, LV_SIZE_CONTENT);
lv_obj_set_size(btn_back, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_back, &style_control_btn, 0);
lv_obj_add_style(btn_back, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_back, control_btn_event_cb, LV_EVENT_ALL, &user_data2);
control_label = lv_label_create(btn_back);
lv_label_set_text(control_label, LV_SYMBOL_DOWN);
lv_obj_center(control_label);
/* 左转按钮 */
lv_obj_t* btn_left = lv_btn_create(control_panel);
lv_obj_set_height(btn_left, LV_SIZE_CONTENT);
lv_obj_set_size(btn_left, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_left, &style_control_btn, 0);
lv_obj_add_style(btn_left, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_left, control_btn_event_cb, LV_EVENT_ALL, &user_data3);
control_label = lv_label_create(btn_left);
lv_label_set_text(control_label, LV_SYMBOL_LEFT);
lv_obj_center(control_label);
/* 右转按钮 */
lv_obj_t* btn_right = lv_btn_create(control_panel);
lv_obj_set_height(btn_right, LV_SIZE_CONTENT);
lv_obj_set_size(btn_right, control_btn_rad, control_btn_rad);
lv_obj_add_style(btn_right, &style_control_btn, 0);
lv_obj_add_style(btn_right, &style_control_btn_pr, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn_right, control_btn_event_cb, LV_EVENT_ALL, &user_data4);
control_label = lv_label_create(btn_right);
lv_label_set_text(control_label, LV_SYMBOL_RIGHT);
lv_obj_center(control_label);
最后是一个布局配置。怎么把上面所有的控件放在我们想要的位置上呢?如果只通过相对位置或绝对位置来一个一个的配置,也可以,但是这样的话当控件比较多时,要修改某个控件的时候,就需要对所有其他的也进行修改,非常麻烦、所以这里我们使用一种常见的LVGL也拥有的布局格式:网格布局。
如其名,网格布局就是把一块区域分割成网格,然后把控件填充进对应的网格里面,实现该区域控件的总体管理。
首先设置整体的布局,控制模块所在的tab页面有两个子区域:控制模块和显示模块。按照下面的代码把这一整块tab页面分成了一个左右两列的区域。
当lv_coord_t修饰的变量或常量作为布局类型时,必须要在布局分割结束的最后加上LV_GRID_TEMPLATE_LAST。
/* 整体布局 */
static lv_coord_t col_dsc[] = { LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST };
static lv_coord_t row_dsc[] = { LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST };
这样分完之后的结果就是这样:
之后针对控制框进行网格分配。下面的代码会把这个子界面分成一个四行三列的网格。前面的数字代表着这一行或这一列占据的长度或宽度。
/* 控制框布局 */
static lv_coord_t grid_2_col_dsc[] = { 48, 48, 48, LV_GRID_TEMPLATE_LAST };
static lv_coord_t grid_2_row_dsc[] = { 10, 48, 48, 48, LV_GRID_TEMPLATE_LAST };
这样分完之后,效果如下图:
接下来使用这段代码就可以把对应的控件填充进对应的网格里了。首先需要使用lv_obj_set_grid_dsc_array
来配置接下来的lv_obj_set_grid_cell
函数的作用域,也就是调用了lv_obj_set_grid_dsc_array
函数之后,接下来的网格配置操作lv_obj_set_grid_cell
就会基于lv_obj_set_grid_dsc_array
中的父网格。也就是上图中左边那一块四行三列的网格。所以配置网格前一定要调用一次lv_obj_set_grid_dsc_array
来选择父网格。
lv_obj_set_grid_cell
第一个形参是控件句柄,第二个形参是当前控件在列方向上,即竖直方向的对齐方式。第三个参数代表当前控件所放置在这个网格里面的多少列,第四个参数代表该控件占据多少列网格位置。第五第六第七个参数和前面一样,但是代表的是在水平方面的三个指标。
lv_obj_set_grid_dsc_array(control_panel, grid_2_col_dsc, grid_2_row_dsc);
lv_obj_set_grid_cell(btn_forward, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_START, 1, 1);
lv_obj_set_grid_cell(btn_left, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 2, 1);
lv_obj_set_grid_cell(btn_right, LV_GRID_ALIGN_START, 2, 1, LV_GRID_ALIGN_START, 2, 1);
lv_obj_set_grid_cell(btn_back, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_START, 3, 1);
例如这里的四个按钮,根据上面的代码所放置在的位置和占位如下图。首先btn_forward
控件,看lv_obj_set_grid_cell
函数的第三个和第六个形参,可以找到防止的位置,即(1,1);btn_left
位置为(0,2);btn_right位置为(2,2);btn_back位置为(1,3)。他们占对应方向的占位网格长度都是1。.
至于占位网格长度,举个例子,如果上述代码的btn_forward
控件改为下面的代码,即列上的占位长度为2,那么最后的效果会像下图这样。
lv_obj_set_grid_cell(btn_forward, LV_GRID_ALIGN_START, 1, 2, LV_GRID_ALIGN_START, 1, 1);
总结一下就是,lv_obj_set_grid_cell(obj, align_l, l, ls, align_r, r, rs)
函数会将obj
控件安放在父网格布局下的对应位置,位置信息计算如下:在竖向上,开始位置为参数l
,占据长度为l * ls
,对齐方式为align_l
;在横向上,开始位置为参数r
,占据长度为r * rs
,对齐方式为align_r
;最后的控件所占据的网格范围就是从(l,r)到(l * ls,r * rs)。
小结
大概的流程就是这样,写了几个我认为比较重要的点,其他的代码还是挺好理解的。这样至少以后要用的话不会忘记这几个函数是干嘛的🙃。
效果可以去arduino-esp32:LVGL项目(一)整体框架这里看。