目录
1. 移植文件
2. 移除多余代码
3. 输入设备初始化
4. 输入设备读回调函数
4.1 LV_INDEV_TYPE_POINTER
4.2 LV_INDEV_TYPE_KEYPAD
4.3 LV_INDEV_TYPE_ENCODER
4.4 LV_INDEV_TYPE_BUTTON
5. 事件
6. 实例
7 Group
7.1 创建Group
7.2 与输入设备关联
7.3 添加对象到Group
7.4 键值与Group的联系
8. GD32450I_EVAL输入设备
7.1 初始化
7.2 读入键值
7.3 读键处理
7.4 实例
7.4.1 初始化3个Button
7.4.2 初始化Group
7.4.3 初始化输入设备
LVGL 支持 4 种类型的输入设备(这4种类型的设备可以同时存在):
- Point:触摸屏或鼠标
- Keypad :键盘
- Encoder :编码器
- Button :按键
1. 移植文件
对应的移植文件在lvgl文件夹里面\examples\porting文件夹下的lv_port_indev_template.c和lv_port_indev_template.h。同样将2个文件修改文件名为:lv_port_indev.c和lv_port_indev.h。
打开其中的宏定义
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*Copy this file as "lv_port_indev.h" and set this value to "1" to enable content*/
#if 1
2. 移除多余代码
在 lv_port_indev.c
中,包含了 5 种设备的 API ,但它们不可能都用到,因此需要裁剪无用的函数和定义。尤其是在初始化函数 lv_port_indev_init()
中,如果不去除无用设备的初始化语句,那么在调用时可能会出现问题。
源码在注释中已经着重强调了不同 API 的分区,只需要根据分区保留需要的代码即可。
这里采用最简单的方法,在lv_port_indev_init中移除多余的初始化代码。
3. 输入设备初始化
对应touchpad_init,mouse_init,keypad_init,encoder_init,button_init。
以Visual Studio模拟器为例,它用到了3个输入设备。初始化部分在lv_win32_init中(没有使用文件lv_port_indev.c和lv_port_indev.h)。
static lv_indev_drv_t pointer_driver;
lv_indev_drv_init(&pointer_driver);
pointer_driver.type = LV_INDEV_TYPE_POINTER;
pointer_driver.read_cb = lv_win32_pointer_driver_read_callback;
lv_win32_pointer_device_object = lv_indev_drv_register(&pointer_driver);
static lv_indev_drv_t keypad_driver;
lv_indev_drv_init(&keypad_driver);
keypad_driver.type = LV_INDEV_TYPE_KEYPAD;
keypad_driver.read_cb = lv_win32_keypad_driver_read_callback;
lv_win32_keypad_device_object = lv_indev_drv_register(&keypad_driver);
static lv_indev_drv_t encoder_driver;
lv_indev_drv_init(&encoder_driver);
encoder_driver.type = LV_INDEV_TYPE_ENCODER;
encoder_driver.read_cb = lv_win32_encoder_driver_read_callback;
lv_win32_encoder_device_object = lv_indev_drv_register(&encoder_driver);
4. 输入设备读回调函数
输入设备的回调函数需要用户根据硬件自己实现,例如上例中的lv_win32_pointer_driver_read_callback、lv_win32_keypad_driver_read_callback和lv_win32_encoder_driver_read_callback。
这个回调函数的原型是:
void (*read_cb)(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
参数含义:
indev_drv - 输入参数,即输入设备句柄
data - 输出参数,
typedef struct {
lv_point_t point; /**< For LV_INDEV_TYPE_POINTER the currently pressed point*/
uint32_t key; /**< For LV_INDEV_TYPE_KEYPAD the currently pressed key*/
uint32_t btn_id; /**< For LV_INDEV_TYPE_BUTTON the currently pressed button*/
int16_t enc_diff; /**< For LV_INDEV_TYPE_ENCODER number of steps since the previous read*/
lv_indev_state_t state; /**< LV_INDEV_STATE_REL or LV_INDEV_STATE_PR*/
bool continue_reading; /**< If set to true, the read callback is invoked again*/
} lv_indev_data_t;
point - 对应鼠标或触摸屏,包括x,y坐标。
key - 对应键盘,即键值。
btn_id - 对应按钮,即按钮ID
enc_diff - 对应滚轮或旋钮,即旋转值
state - 输入设备的状态
/** States for input devices*/
typedef enum {
LV_INDEV_STATE_RELEASED = 0,
LV_INDEV_STATE_PRESSED
} lv_indev_state_t;
#define LV_INDEV_STATE_REL LV_INDEV_STATE_RELEASED
#define LV_INDEV_STATE_PR LV_INDEV_STATE_PRESSED
continue_reading - 通知是否需要连续读入设备,设置为true时会再次回调函数。
4.1 LV_INDEV_TYPE_POINTER
触摸屏和鼠标都属于这种类型,鼠标是只支持一种点击类型,即左点击,所以只返回一个点的x, y坐标,以模拟器为例:
更新鼠标的状态,是按下还是释放。
data->state = (lv_indev_state_t)(
g_mouse_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL);
返回x,y坐标值。
data->point.x = MulDiv(
GET_X_LPARAM(g_mouse_value),
USER_DEFAULT_SCREEN_DPI,
WIN32DRV_MONITOR_ZOOM * g_dpi_value);
data->point.y = MulDiv(
GET_Y_LPARAM(g_mouse_value),
USER_DEFAULT_SCREEN_DPI,
WIN32DRV_MONITOR_ZOOM * g_dpi_value);
4.2 LV_INDEV_TYPE_KEYPAD
键盘类型,返回的是按键的键值。
以模拟器为例:
更新按键的状态,是按下还是释放。
data->state = (lv_indev_state_t)(
g_keyboard_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL);
返回键值,注意LVGL键值和电脑键值不一样。
WPARAM KeyboardValue = g_keyboard_value;
switch (KeyboardValue)
{
case VK_UP:
data->key = LV_KEY_UP;
break;
case VK_DOWN:
data->key = LV_KEY_DOWN;
break;
case VK_LEFT:
data->key = LV_KEY_LEFT;
break;
case VK_RIGHT:
data->key = LV_KEY_RIGHT;
break;
...
default:
if (KeyboardValue >= 'A' && KeyboardValue <= 'Z')
{
KeyboardValue += 0x20;
}
data->key = (uint32_t)KeyboardValue;
break;
}
4.3 LV_INDEV_TYPE_ENCODER
鼠标滚轮和旋转编码器属于这种类型,返回的是步进值。
更新编码器的状态,是按下还是释放。
data->state = (lv_indev_state_t)(
g_mousewheel_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL);
返回步进值。
data->enc_diff = g_mousewheel_value;
4.4 LV_INDEV_TYPE_BUTTON
按键类型的输入设备和键盘类型的输入设备区别是按键是对应屏幕上的位置,当这个物理按键被压下时就相当于一个触摸屏点击。
在模拟器中添加一个BUTTON的输入设备,通过键盘模拟实现。
在win32drv.c中添加
EXTERN_C lv_indev_t* lv_win32_button_device_object = NULL;
在win32drv.h中添加
EXTERN_C lv_indev_t* lv_win32_button_device_object;
在win32drv.c中添加初始化代码
static lv_indev_drv_t button_driver;
lv_indev_drv_init(&button_driver);
button_driver.type = LV_INDEV_TYPE_BUTTON;
button_driver.read_cb = lv_win32_button_driver_read_callback;
lv_win32_button_device_object = lv_indev_drv_register(&button_driver);
添加button对应屏幕坐标组
static const lv_point_t btn_points[3] = {
{30, 30}, /*Button 0 -> x:30; y:30*/
{230, 30}, /*Button 1 -> x:230; y:30*/
{430, 30}, /*Button 2 -> x:430; y:30*/
};
lv_indev_set_button_points(lv_win32_button_device_object, btn_points);
这里对应3个button,分别用键盘的F1,F2,F3控制,即F1按下时即(30, 30)被点击。数组btn_points里面的每一个元素的位置即是Button的ID号。
增加回调函数:
static void lv_win32_button_driver_read_callback(
lv_indev_drv_t* indev_drv,
lv_indev_data_t* data)
{
UNREFERENCED_PARAMETER(indev_drv);
data->state = (lv_indev_state_t)(
g_keyboard_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL);
WPARAM KeyboardValue = g_keyboard_value;
switch (KeyboardValue)
{
case VK_F1:
data->btn_id = 0;
break;
case VK_F2:
data->btn_id = 1;
break;
case VK_F3:
data->btn_id = 2;
break;
default:
data->state = LV_INDEV_STATE_REL;
break;
}
}
注意这里btn_id不能赋值非法值, 即不能越界数组btn_points,否则会运行错误。
5. 事件
通过lv_obj_add_event_cb给对象添加事件处理程序函数。一个对象可以通过多次调用这个函数添加多个事件处理程序,它们将按添加时的相同顺序调用。
struct _lv_event_dsc_t * lv_obj_add_event_cb(struct _lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data);
参数含义:
obj - 对象句柄。
event_cb - 事件处理回调函数。
typedef void (*lv_event_cb_t)(lv_event_t * e);
filter - 事件代码,例如LV_EVENT_CLICKED表示点击事件,具体可以看lv_event.h中的枚举lv_event_code_t。
user_data - 事件处理回调函数的参数数据
6. 实例
3个方框,分别按F1,F2,F3键,对应的方框内显示字符串“Pressed”,松开后显示“Released”。
初始化方框:
lv_obj_t* rect1 = lv_obj_create(lv_scr_act());
lv_obj_t* rect2 = lv_obj_create(lv_scr_act());
lv_obj_t* rect3 = lv_obj_create(lv_scr_act());
lv_obj_set_size(rect1, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect1, LV_ALIGN_TOP_LEFT, 20, 20);
lv_obj_add_event_cb(rect1, event_press_cb1, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(rect1, event_release_cb1, LV_EVENT_RELEASED, NULL);
lv_obj_set_size(rect2, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect2, LV_ALIGN_TOP_LEFT, 220, 20);
lv_obj_add_event_cb(rect2, event_press_cb2, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(rect2, event_release_cb2, LV_EVENT_RELEASED, NULL);
lv_obj_set_size(rect3, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect3, LV_ALIGN_TOP_LEFT, 420, 20);
lv_obj_add_event_cb(rect3, event_press_cb3, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(rect3, event_release_cb3, LV_EVENT_RELEASED, NULL);
每个方框设置事件Pressed和Released的回调函数。
在每个方框内显示字符串:
tst_input_label1 = lv_label_create(rect1);
lv_obj_set_size(tst_input_label1, LV_PCT(50), LV_PCT(20));
lv_obj_align(tst_input_label1, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(tst_input_label1, "Released");
tst_input_label2 = lv_label_create(rect2);
lv_obj_set_size(tst_input_label2, LV_PCT(50), LV_PCT(20));
lv_obj_align(tst_input_label2, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(tst_input_label2, "Released");
tst_input_label3 = lv_label_create(rect3);
lv_obj_set_size(tst_input_label3, LV_PCT(50), LV_PCT(20));
lv_obj_align(tst_input_label3, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(tst_input_label3, "Released");
回调函数更新对应的label显示字符串:
static void event_press_cb1(lv_event_t* e)
{
lv_label_set_text(tst_input_label1, "Pressed");
}
static void event_release_cb1(lv_event_t* e)
{
lv_label_set_text(tst_input_label1, "Released");
}
static void event_press_cb2(lv_event_t* e)
{
lv_label_set_text(tst_input_label2, "Pressed");
}
static void event_release_cb2(lv_event_t* e)
{
lv_label_set_text(tst_input_label2, "Released");
}
static void event_press_cb3(lv_event_t* e)
{
lv_label_set_text(tst_input_label3, "Pressed");
}
static void event_release_cb3(lv_event_t* e)
{
lv_label_set_text(tst_input_label3, "Released");
}
7 Group
当使用键盘或者编码器进行左右选择时,LVGL是通过Group的方式来获知下一个被选中的对象。所以将多个对象放入同一个Group内可以更方便的实现输入设备的控制。
7.1 创建Group
lv_group_t * group = lv_group_create();
7.2 与输入设备关联
lv_indev_set_group(indev, group);
其中indev是输入设备初始化时调用lv_indev_drv_register返回的值。
7.3 添加对象到Group
lv_group_add_obj(group, obj);
注意并不是所有的对象都可以加入Group,例如基础对象lv_obj_t就不行。加入后按左右按键并没有反应。
7.4 键值与Group的联系
一些特殊的键值会和Group产生直接的联系,最常见的键是 LV_KEY_NEXT/PREV
,LV_KEY_ENTER
和 LV_KEY_UP/DOWN/LEFT/RIGHT,
因为大多数对象就可以用它们完全控制。
8. GD32450I_EVAL输入设备
GD32450I_EVAL有3个按键,
将Wakeup作为LEFT键,Tamper作为RIGHT键,USER作为ENTER键。
7.1 初始化
static void keypad_init(void)
{
/*Your code comes here*/
gpio_mode_set(IO_KEY_WAKEUP, GPIO_MODE_INPUT, GPIO_PUPD_NONE, ((uint32_t)1 << PIN_KEY_WAKEUP));
gpio_mode_set(IO_KEY_TAMPER, GPIO_MODE_INPUT, GPIO_PUPD_NONE, ((uint32_t)1 << PIN_KEY_TAMPER));
gpio_mode_set(IO_KEY_USER, GPIO_MODE_INPUT, GPIO_PUPD_NONE, ((uint32_t)1 << PIN_KEY_USER));
}
初始化按键对应IO为输入。
7.2 读入键值
static uint32_t keypad_get_key(void)
{
/*Your code comes here*/
if(RESET == gpio_input_bit_get(IO_KEY_WAKEUP, (uint32_t)1 << PIN_KEY_WAKEUP))
{
return 1;
}
else if(RESET == gpio_input_bit_get(IO_KEY_TAMPER, (uint32_t)1 << PIN_KEY_TAMPER))
{
return 2;
}
else if(RESET == gpio_input_bit_get(IO_KEY_USER, (uint32_t)1 << PIN_KEY_USER))
{
return 3;
}
return 0;
}
7.3 读键处理
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
/*Get whether the a key is pressed and save the pressed key*/
uint32_t act_key = keypad_get_key();
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
/*Translate the keys to LVGL control characters according to your key definitions*/
switch(act_key) {
case 1:
act_key = LV_KEY_PREV;
break;
case 2:
act_key = LV_KEY_NEXT;
break;
case 3:
act_key = LV_KEY_ENTER;
break;
}
last_key = act_key;
}
else {
data->state = LV_INDEV_STATE_REL;
}
data->key = last_key;
}
这里键值1,2对应按键PREV和NEXT,不是LEFT和RIGHT。这样才能左右选择Group的对象。
7.4 实例
7.4.1 初始化3个Button
static lv_obj_t *rect1, *rect2, *rect3; //全局变量
rect1 = lv_btn_create(lv_scr_act());
lv_obj_set_size(rect1, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect1, LV_ALIGN_TOP_LEFT, 10, 20);
rect2 = lv_btn_create(lv_scr_act());
lv_obj_set_size(rect2, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect2, LV_ALIGN_TOP_LEFT, 150, 20);
rect3 = lv_btn_create(lv_scr_act());
lv_obj_set_size(rect3, LV_PCT(20), LV_PCT(20));
lv_obj_align(rect3, LV_ALIGN_TOP_LEFT, 290, 20);
7.4.2 初始化Group
将这3个button加入到group中。
static lv_group_t *groupRect; //全局变量
groupRect = lv_group_create();
extern lv_indev_t * indev_keypad;
lv_indev_set_group(indev_keypad, groupRect);
lv_group_add_obj(groupRect, rect1);
lv_group_add_obj(groupRect, rect2);
lv_group_add_obj(groupRect, rect3);
7.4.3 初始化输入设备
在合适的位置调用lv_port_indev_init()。
lv_init();
lv_port_disp_init();
lv_port_indev_init();