Tab View: circular scrolling

Does this feature exist now?

The issue was closed due to inactivity.

We are reworking a lot of stuff for v8. I keep this in mind, hopefully we will find a solution to support it in a simple way.

I’ve also had the same feature request pop up recently.

What do you want to achieve?

Switching between the tabs can be done by swiping left or right. When on the first tab and swiping backwards (to the right), it would be good if it would change to the last tab. And when on the last tab and swiping forward (to the left) it would be nice if it would change to the first tab.

Is this implemented in v8 or are there plans for it in 8.1?

Do you agree that this feature is more relevant if the tab buttons are hidden?

If so, I think I can create an example for it using v8.

1 Like

Yes I agree. When there are no buttons, for example due to space constraints, it becomes more difficult to navigate multiple tabs by swiping repeatedly. If a wrap around option is available, it can limit the number of swipes needed to navigate from end-to-end. Thus greatly increasing user experience.

The idea is to use bullets as an indicator of the current tab (like on a smartphone) and update it accordingly:

I’ve implemented the core logic of circular scrolling. It’s not a ready-to-use widget just a demonstration. It’d be great if someone would be interested in turning it into a widget. :slight_smile:

static int32_t act_page_idx = 0;

static void scroll_end_event(lv_event_t * e)
    lv_obj_t * cont = lv_event_get_target(e);

    /* If scroll end came from the user we don't case
     * We want to rearrange the grid when snapping moved scrolled.*/
    lv_indev_t * indev = lv_indev_get_act();
    if(indev) return;
    lv_point_t p;
    lv_obj_get_scroll_end(cont, &p);
    uint32_t child_cnt = lv_obj_get_child_cnt(cont);
    lv_coord_t w = lv_obj_get_content_width(cont);

    /*Get the current page, can be -1,0,1 for left center, right*/
    lv_coord_t page = (p.x + w/ 2) / w;
    if(page < 0) page = 0;
    if(page >= 3) page = 2;

    if(page == 0) return; /*Remained on the center page*/

    /*Hide all children to have clean start*/
    lv_obj_t * child;
    uint32_t i;
    for(i = 0; i < child_cnt; i++) {
        child = lv_obj_get_child(cont, i);
        lv_obj_add_flag(child, LV_OBJ_FLAG_HIDDEN);

    /*Find the new pages circularly*/
    act_page_idx += page;
    if(act_page_idx >  (int32_t)child_cnt - 1) act_page_idx = 0;
    else if(act_page_idx <  0) act_page_idx = child_cnt - 1;

    int32_t prev_page_idx = act_page_idx == 0 ? (int32_t)child_cnt - 1 : act_page_idx - 1;
    int32_t next_page_idx = act_page_idx == (int32_t)child_cnt - 1 ? 0 : act_page_idx + 1;

    /*Add the left center and right pages to the grid*/
    child = lv_obj_get_child(cont, prev_page_idx);
    lv_obj_clear_flag(child, LV_OBJ_FLAG_HIDDEN);
    lv_obj_set_grid_cell(child, LV_GRID_ALIGN_CENTER, 0, 1, LV_GRID_ALIGN_CENTER, 0, 1);

    child = lv_obj_get_child(cont, act_page_idx);
    lv_obj_clear_flag(child, LV_OBJ_FLAG_HIDDEN);
    lv_obj_set_grid_cell(child, LV_GRID_ALIGN_CENTER, 1, 1, LV_GRID_ALIGN_CENTER, 0, 1);

    child = lv_obj_get_child(cont, next_page_idx);
    lv_obj_clear_flag(child, LV_OBJ_FLAG_HIDDEN);
    lv_obj_set_grid_cell(child, LV_GRID_ALIGN_CENTER, 2, 1, LV_GRID_ALIGN_CENTER, 0, 1);

    /*Scroll to the new center object*/
    child = lv_obj_get_child(cont, act_page_idx);
    lv_obj_scroll_to_view(child, LV_ANIM_OFF);
  lv_obj_t * cont = lv_obj_create(lv_scr_act());
  lv_obj_set_size(cont, 400, 300);
  lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE);            /*Allow max 1 page swipe*/
  lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER);    /*Snap a page to the center*/
  lv_obj_add_event_cb(cont, scroll_end_event, LV_EVENT_SCROLL_END, NULL);

  /*A grid for the left center and right pages*/
  static lv_coord_t row_dsc[] = {LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
  lv_obj_set_grid_dsc_array(cont, col_dsc, row_dsc);

  /*Create 5 pages*/
  for(int i = 0; i < 5; i++) {
      lv_obj_t * obj = lv_obj_create(cont);
      lv_obj_set_size(obj, lv_pct(100), lv_pct(100));
      lv_obj_t * label = lv_label_create(obj);
      lv_label_set_text_fmt(label, "Page %d", i);

  /*Page 2, 3 hidden, page 4, 0, 1 are placed to the grid */
  lv_obj_add_flag(lv_obj_get_child(cont, 2), LV_OBJ_FLAG_HIDDEN);
  lv_obj_add_flag(lv_obj_get_child(cont, 3), LV_OBJ_FLAG_HIDDEN);
  lv_obj_set_grid_cell(lv_obj_get_child(cont, 4), LV_GRID_ALIGN_CENTER, 0, 1, LV_GRID_ALIGN_CENTER, 0, 1);
  lv_obj_set_grid_cell(lv_obj_get_child(cont, 0), LV_GRID_ALIGN_CENTER, 1, 1, LV_GRID_ALIGN_CENTER, 0, 1);
  lv_obj_set_grid_cell(lv_obj_get_child(cont, 1), LV_GRID_ALIGN_CENTER, 2, 1, LV_GRID_ALIGN_CENTER, 0, 1);

  /*Be sure page 0 is centered*/
  lv_obj_scroll_to_view(lv_obj_get_child(cont, 0), LV_ANIM_OFF);

1 Like

In action:

I’ve intentionally left the scrollbar visible to show what happens.

Note that this method requires at lest three pages to work properly.


I think a similar function is needed. What I need is for the roller to roll horizontally, whereas at the moment it can only roll vertically

From this concept a horizontal roller can be created too. It gets a little bit more complicated if more options are visible at once, but seems managable.

When creating a widget (roller, tabview, list, etc.) or adding a child, you should have a parameter that controls the growth direction of the child (vertical or horizontal). If you think it is feasible, will you consider joining the development route?

By growth direction, you mean the direction of the layout?

If so, I think it’s a little bit more complicated:

  • roller: there is only one, multi-line label on the roller
  • tabview: already works, you can set the direction when creating the tabview.
  • list: on a horizontal list, what should be the width of the items? aligned to the content of the item’s text?

The problem with horizontal scrolling of rollers and lists is mainly to control their widths. As you said, if items have different widths, it will be difficult to manage.In general, the width of items should be the same when selecting horizontal scrolling, but it is not absolute. I think it is better to use multiple lines or fix the width, and use the LV_LABEL_LONG_SCROLL_Circular mode for any part of the label that exceeds the width.

Can tileview do the same?

Probably the above example can be extended to work in 2D.

1 Like

the v8 is not have the titleview and tabview ? is replace by grid obj ?

Both still exist (tabview, tileview).

the demo has bugs if you use more times

this demo is use grid ,how to implement use titleview ?

Do you mean a circular tileview?

yes,Vertical part of the tileview can circular scrolling ?