Tab View: circular scrolling

I’m not sure if this is possible, I would like an option to scroll the tabs but when it is at last tab it will scroll to the first on same direction on a circular fashion way…
I’m using the function lv_tabview_set_tab_act

Now: Tab1 … Tab2 … Tab N … Tab 2 … Tab 1
I would like: Tab1 … Tab2 … Tab N … Tab 1


it was asked several times and I also agree, it would be useful.

An additional bullet type indicator would be great too. Like this.

@embeddedt, do you also find this feature reasonable for the tabview?

1 Like

Yes; it sounds like a useful feature.

1 Like

Thanks! Is there a plan when it could be added?
I would like to get notified, but the github bug report says “Questions and enhancement requests should go to the forum.”
If you create a issue tracker on github please add me so I will get notified.

I’ve opened an issue for it:

1 Like

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);

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.

1 Like

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.