Grid navigation(网格导航)
显示原文
Grid navigation (gridnav for short) is a feature that changes the currently focused child Widget as arrow keys are pressed.
If the children are arranged into a grid-like layout then the up, down, left and right arrows move focus to the nearest sibling in the respective direction.
It doesn't matter how the children are positioned, as only the current x and y coordinates are considered. This means that gridnav works with manually positioned children, as well as Flex(弹性布局) and Grid(网格布局) layouts.
Gridnav also works if the children are arranged into a single row or column. That makes it useful, for example, to simplify navigation on a List widget.
Gridnav assumes that the Widget to which gridnav is added is part of a group. This way, if the Widget with gridnav has focus, the arrow key presses are automatically forwarded to the Widget so that gridnav can process the arrow keys.
To move the focus to the next widget of the group use
LV_KEY_NEXT
or LV_KEY_PREV
.
Optionally you can also use lv_group_focus_next()
or lv_group_focus_prev()
or the TAB
key on keyboard as usual.
If the container is scrollable and the focused child is out of the view, gridnav will automatically scroll the child into view.
Grid navigation(简称 gridnav)是一项功能,可以在按下方向键时更改当前聚焦的子 Widget。
如果子元素排列成网格状布局,则向上、向下、向左和向右的箭头会将焦点移动到相应方向上最近的兄弟节点。
子元素的实际位置无关紧要,因为只考虑当前的 x 和 y 坐标。这意味着 gridnav 可用于手动定位的子元素,也可用于 Flex(弹性布局) 和 Grid(网格布局) 布局。
即使子元素排列成单行或单列,gridnav 也能正常工作。例如,这使其可以简化在 List widget 上的导航。
Gridnav 假设添加了 gridnav 的 Widget 是 group 的一部分。这样,当具有 gridnav 的 Widget 获得焦点时,方向键的按键事件会自动转发给该 Widget,从而让 gridnav 可以处理方向键。
要将焦点移动到组的下一个 Widget,可以使用 LV_KEY_NEXT
或 LV_KEY_PREV
。也可以选择使用 lv_group_focus_next()
或 lv_group_focus_prev()
,或者像通常一样使用键盘上的 TAB
键。
如果容器是可滚动的,并且聚焦的子元素不在视图范围内,gridnav 会自动将子元素滚动到视图内。
Usage(用法)
显示原文
To add the gridnav feature to a Widget use lv_gridnav_add(cont, flags).
flags
control the behavior of gridnav:
LV_GRIDNAV_CTRL_NONE
: Default settingsLV_GRIDNAV_CTRL_ROLLOVER
: If there is no next/previous Widget in a direction, the focus goes to the Widget in the next/previous row (on left/right keys) or first/last row (on up/down keys)LV_GRIDNAV_CTRL_SCROLL_FIRST
: If an arrow is pressed and the focused Widget can be scrolled in that direction then it will be scrolled instead of going to the next/previous Widget. If there is no more room for scrolling the next/previous Widget will be focused normallyLV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY
: Only use the left/right keys for grid navigation. Up/down key events will be sent to the focused Widget.LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY
: Only use the up/down keys for grid navigation. Left/right key events will be sent to the focused Widget.
LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY
and LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY
should not be used together.
lv_gridnav_remove(cont) Removes gridnav from a Widget.
要向 Widget 添加 gridnav 功能,请使用 lv_gridnav_add(cont, flags)。
flags
用于控制 gridnav 的行为:
LV_GRIDNAV_CTRL_NONE
: 默认设置LV_GRIDNAV_CTRL_ROLLOVER
: 如果在某个方向上没有下一个/上一个 Widget,则焦点会移动到下一行/上一行(对于左右方向键)或第一行/最后一行(对于上下方向键)的 Widget。LV_GRIDNAV_CTRL_SCROLL_FIRST
: 如果按下箭头键并且聚焦的 Widget 可以向该方向滚动,则会滚动而不是移动到下一个/上一个 Widget。如果没有更多的滚动空间,则会正常聚焦到下一个/上一个 Widget。LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY
: 仅使用左右方向键进行网格导航。上下方向键事件将发送到聚焦的 Widget。LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY
: 仅使用上下方向键进行网格导航。左右方向键事件将发送到聚焦的 Widget。
LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY
和 LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY
不应同时使用。
使用 lv_gridnav_remove(cont) 可从 Widget 移除 gridnav。
Focusable objects(可以聚焦的对象)
显示原文
A Widget needs to be clickable or click focusable
(LV_OBJ_FLAG_CLICKABLE
or LV_OBJ_FLAG_CLICK_FOCUSABLE
) and not
hidden (LV_OBJ_FLAG_HIDDEN
) to be focusable by gridnav.
一个 Widget 需要是可点击或可点击聚焦的 (LV_OBJ_FLAG_CLICKABLE
或 LV_OBJ_FLAG_CLICK_FOCUSABLE
),且未被隐藏 (LV_OBJ_FLAG_HIDDEN
),才能被 gridnav 聚焦。
Example
Basic grid navigation
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES
/**
* Demonstrate a a basic grid navigation
*/
void lv_example_gridnav_1(void)
{
/*It's assumed that the default group is set and
*there is a keyboard indev*/
lv_obj_t * cont1 = lv_obj_create(lv_screen_active());
lv_gridnav_add(cont1, LV_GRIDNAV_CTRL_NONE);
/*Use flex here, but works with grid or manually placed objects as well*/
lv_obj_set_flex_flow(cont1, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_style_bg_color(cont1, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED);
lv_obj_set_size(cont1, lv_pct(50), lv_pct(100));
/*Only the container needs to be in a group*/
lv_group_add_obj(lv_group_get_default(), cont1);
lv_obj_t * label = lv_label_create(cont1);
lv_label_set_text_fmt(label, "No rollover");
uint32_t i;
for(i = 0; i < 10; i++) {
lv_obj_t * obj = lv_button_create(cont1);
lv_obj_set_size(obj, 70, LV_SIZE_CONTENT);
lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
lv_group_remove_obj(obj); /*Not needed, we use the gridnav instead*/
label = lv_label_create(obj);
lv_label_set_text_fmt(label, "%"LV_PRIu32"", i);
lv_obj_center(label);
}
/* Create a second container with rollover grid nav mode.*/
lv_obj_t * cont2 = lv_obj_create(lv_screen_active());
lv_gridnav_add(cont2, LV_GRIDNAV_CTRL_ROLLOVER);
lv_obj_set_style_bg_color(cont2, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED);
lv_obj_set_size(cont2, lv_pct(50), lv_pct(100));
lv_obj_align(cont2, LV_ALIGN_RIGHT_MID, 0, 0);
label = lv_label_create(cont2);
lv_obj_set_width(label, lv_pct(100));
lv_label_set_text_fmt(label, "Rollover\nUse tab to focus the other container");
/*Only the container needs to be in a group*/
lv_group_add_obj(lv_group_get_default(), cont2);
/*Add and place some children manually*/
lv_obj_t * ta = lv_textarea_create(cont2);
lv_obj_set_size(ta, lv_pct(100), 80);
lv_obj_set_pos(ta, 0, 80);
lv_group_remove_obj(ta); /*Not needed, we use the gridnav instead*/
lv_obj_t * cb = lv_checkbox_create(cont2);
lv_obj_set_pos(cb, 0, 170);
lv_group_remove_obj(cb); /*Not needed, we use the gridnav instead*/
lv_obj_t * sw1 = lv_switch_create(cont2);
lv_obj_set_pos(sw1, 0, 200);
lv_group_remove_obj(sw1); /*Not needed, we use the gridnav instead*/
lv_obj_t * sw2 = lv_switch_create(cont2);
lv_obj_set_pos(sw2, lv_pct(50), 200);
lv_group_remove_obj(sw2); /*Not needed, we use the gridnav instead*/
}
#endif
Grid navigation on a list
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_GRIDNAV && LV_USE_LIST && LV_BUILD_EXAMPLES
/**
* Grid navigation on a list
*/
void lv_example_gridnav_2(void)
{
/*It's assumed that the default group is set and
*there is a keyboard indev*/
lv_obj_t * list1 = lv_list_create(lv_screen_active());
lv_gridnav_add(list1, LV_GRIDNAV_CTRL_NONE);
lv_obj_set_size(list1, lv_pct(45), lv_pct(80));
lv_obj_align(list1, LV_ALIGN_LEFT_MID, 5, 0);
lv_obj_set_style_bg_color(list1, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED);
lv_group_add_obj(lv_group_get_default(), list1);
char buf[32];
uint32_t i;
for(i = 0; i < 15; i++) {
lv_snprintf(buf, sizeof(buf), "File %d", i + 1);
lv_obj_t * item = lv_list_add_button(list1, LV_SYMBOL_FILE, buf);
lv_obj_set_style_bg_opa(item, 0, 0);
lv_group_remove_obj(item); /*Not needed, we use the gridnav instead*/
}
lv_obj_t * list2 = lv_list_create(lv_screen_active());
lv_gridnav_add(list2, LV_GRIDNAV_CTRL_ROLLOVER);
lv_obj_set_size(list2, lv_pct(45), lv_pct(80));
lv_obj_align(list2, LV_ALIGN_RIGHT_MID, -5, 0);
lv_obj_set_style_bg_color(list2, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED);
lv_group_add_obj(lv_group_get_default(), list2);
for(i = 0; i < 15; i++) {
lv_snprintf(buf, sizeof(buf), "Folder %d", i + 1);
lv_obj_t * item = lv_list_add_button(list2, LV_SYMBOL_DIRECTORY, buf);
lv_obj_set_style_bg_opa(item, 0, 0);
lv_group_remove_obj(item);
}
}
#endif
Nested grid navigations
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES
static void cont_sub_event_cb(lv_event_t * e)
{
uint32_t k = lv_event_get_key(e);
lv_obj_t * obj = lv_event_get_target(e);
if(k == LV_KEY_ENTER) {
lv_group_focus_obj(obj);
}
else if(k == LV_KEY_ESC) {
lv_group_focus_next(lv_obj_get_group(obj));
}
}
/**
* Nested grid navigations
*/
void lv_example_gridnav_3(void)
{
/*It's assumed that the default group is set and
*there is a keyboard indev*/
lv_obj_t * cont_main = lv_obj_create(lv_screen_active());
lv_gridnav_add(cont_main, LV_GRIDNAV_CTRL_ROLLOVER | LV_GRIDNAV_CTRL_SCROLL_FIRST);
/*Only the container needs to be in a group*/
lv_group_add_obj(lv_group_get_default(), cont_main);
/*Use flex here, but works with grid or manually placed objects as well*/
lv_obj_set_flex_flow(cont_main, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_style_bg_color(cont_main, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED);
lv_obj_set_size(cont_main, lv_pct(80), LV_SIZE_CONTENT);
lv_obj_t * btn;
lv_obj_t * label;
btn = lv_button_create(cont_main);
lv_group_remove_obj(btn);
label = lv_label_create(btn);
lv_label_set_text(label, "Button 1");
btn = lv_button_create(cont_main);
lv_group_remove_obj(btn);
label = lv_label_create(btn);
lv_label_set_text(label, "Button 2");
/*Create another container with long text to show how LV_GRIDNAV_CTRL_SCROLL_FIRST works*/
lv_obj_t * cont_sub1 = lv_obj_create(cont_main);
lv_obj_set_size(cont_sub1, lv_pct(100), 100);
label = lv_label_create(cont_sub1);
lv_obj_set_style_bg_color(cont_sub1, lv_palette_lighten(LV_PALETTE_RED, 5), LV_STATE_FOCUSED);
lv_obj_set_width(label, lv_pct(100));
lv_label_set_text(label,
"I'm a very long text which is makes my container scrollable. "
"As LV_GRIDNAV_FLAG_SCROLL_FIRST is enabled arrow will scroll me first "
"and a new objects will be focused only when an edge is reached with the scrolling.\n\n"
"This is only some placeholder text to be sure the parent will be scrollable. \n\n"
"Hello world!\n"
"Hello world!\n"
"Hello world!\n"
"Hello world!\n"
"Hello world!\n"
"Hello world!");
/*Create a third container that can be focused with ENTER and contains another grid nav*/
lv_obj_t * cont_sub2 = lv_obj_create(cont_main);
lv_gridnav_add(cont_sub2, LV_GRIDNAV_CTRL_ROLLOVER);
/*Only the container needs to be in a group*/
lv_group_add_obj(lv_group_get_default(), cont_sub2);
lv_obj_add_event_cb(cont_sub2, cont_sub_event_cb, LV_EVENT_KEY, NULL);
/*Use flex here, but works with grid or manually placed objects as well*/
lv_obj_set_flex_flow(cont_sub2, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_style_bg_color(cont_sub2, lv_palette_lighten(LV_PALETTE_RED, 5), LV_STATE_FOCUSED);
lv_obj_set_size(cont_sub2, lv_pct(100), LV_SIZE_CONTENT);
label = lv_label_create(cont_sub2);
lv_label_set_text(label, "Use ENTER/ESC to focus/defocus this container");
lv_obj_set_width(label, lv_pct(100));
btn = lv_button_create(cont_sub2);
lv_group_remove_obj(btn);
label = lv_label_create(btn);
lv_label_set_text(label, "Button 3");
btn = lv_button_create(cont_sub2);
lv_group_remove_obj(btn);
label = lv_label_create(btn);
lv_label_set_text(label, "Button 4");
}
#endif
Simple navigation on a list widget
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES
static void event_handler(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
lv_obj_t * list = lv_obj_get_parent(obj);
LV_UNUSED(list); /*If logging is disabled*/
LV_LOG_USER("Clicked: %s", lv_list_get_button_text(list, obj));
}
/**
* Simple navigation on a list widget
*/
void lv_example_gridnav_4(void)
{
/*It's assumed that the default group is set and
*there is a keyboard indev*/
lv_obj_t * list = lv_list_create(lv_screen_active());
lv_gridnav_add(list, LV_GRIDNAV_CTRL_ROLLOVER);
lv_obj_align(list, LV_ALIGN_LEFT_MID, 10, 0);
lv_group_add_obj(lv_group_get_default(), list);
uint32_t i;
for(i = 0; i < 20; i++) {
char buf[32];
/*Add some separators too, they are not focusable by gridnav*/
if((i % 5) == 0) {
lv_snprintf(buf, sizeof(buf), "Section %d", i / 5 + 1);
lv_list_add_text(list, buf);
}
lv_snprintf(buf, sizeof(buf), "File %d", i + 1);
lv_obj_t * item = lv_list_add_button(list, LV_SYMBOL_FILE, buf);
lv_obj_add_event_cb(item, event_handler, LV_EVENT_CLICKED, NULL);
lv_group_remove_obj(item); /*The default group adds it automatically*/
}
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_align(btn, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Button");
}
#endif
Grid navigation for only one axis
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES
static const char * opts[] = {"0\n1\n2\n3\n4\n5", "0\n1\n2\n3\n4\n5\n6\n7\n8\n9", "s\nm\nh"};
static const int32_t opts_counts[] = {6, 10, 3};
static lv_obj_t * sliders[3];
static lv_obj_t * rollers[3];
static void slider_key_cb(lv_event_t * e)
{
uint8_t i = (uint32_t)(uintptr_t)lv_event_get_user_data(e);
lv_roller_set_selected(rollers[i], lv_slider_get_value(sliders[i]), LV_ANIM_ON);
}
static void roller_key_cb(lv_event_t * e)
{
uint8_t i = (uint32_t)(uintptr_t)lv_event_get_user_data(e);
lv_slider_set_value(sliders[i], lv_roller_get_selected(rollers[i]), LV_ANIM_ON);
}
/**
* Grid navigation for only one axis
*/
void lv_example_gridnav_5(void)
{
/*It's assumed that the default group is set and
*there is a keyboard indev*/
lv_group_t * group = lv_group_get_default();
lv_obj_t * cont;
cont = lv_obj_create(lv_screen_active());
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_size(cont, lv_pct(100), lv_pct(50));
lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0);
/* only up/down keys will be used for grid navigation in this container. */
/* left/right will be sent to the sliders */
lv_gridnav_add(cont, LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY);
lv_group_add_obj(group, cont);
for(uint32_t i = 0; i < 3; i++) {
lv_obj_t * slider = lv_slider_create(cont);
lv_slider_set_range(slider, 0, opts_counts[i] - 1);
lv_group_remove_obj(slider);
lv_obj_set_width(slider, lv_pct(85));
sliders[i] = slider;
lv_obj_add_event_cb(slider, slider_key_cb, LV_EVENT_KEY, (void *)(uintptr_t)i);
}
cont = lv_obj_create(lv_screen_active());
lv_obj_set_flex_flow