Scroller index Sample Overview

Mobile native

The Scroller Index sample application demonstrates how to implement an animated current page indicator for the page scroller component. The sample shows how to rotate Evas_Objects using Evas_Maps and how to handle the elm_scroller smart callbacks.

The following figure illustrates the application views.

Figure: Scroller Index screens

Scroller Index screen Scroller Index screen Scroller Index screen

The application provides a user interface divided into 2 sections:

  • Page scroller: an elm_scroller component at the top of the screen
  • Index: a current page indicator at the bottom of the screen

You can switch pages using the page scroller. The current page is indicated by the index under the scroller.

Source Files

You can create and view the sample application project including the source files in the IDE.

Table: Source files
Category File name Description
General (app_name).c Provides an entry point in the application. Initializes the application main data and provides the implementation of the main callbacks.
View view.c Creates the window, background, conformant, and main layout.
page_scroller.c Provides functions to create the elm_scroller component. The file contains the code for creating the page scroller component and filling it with proper content.
index.c Provides functions to create the index component. The file contains code for creating the elm_box component and the current page indices. It connects the scroller logic to the index animations.

Implementation

Page Scroller Implementation

The page scroller is based on the elm_scroller component. It allows the user to drag the viewable region around, moving through a much larger object that is contained in the scroller. Typically, the elm_scroller component contains an elm_box as its content. The box component is larger than the scroller and holds the layouts of multiple pages. This approach allows you to implement interfaces for obtaining the page and its number as well as removing the page. It is a good practice to use an elm_box object as the content of the elm_scroller.

To create the scroller:

Evas_Object *page_scroller_create(Evas_Object *parent, int p_width, int p_height)
{
   // Create the page scroller component and set its parent
   Evas_Object *page_scroller = NULL;
   page_scroller = elm_scroller_add(parent);

   // Set the page size of the scroller. In this case, the page width is equal to the screen width
   elm_scroller_page_size_set(page_scroller, p_width, p_height);

   // Switch off limiting the scroller's minimum size to the minimum size of its content
   elm_scroller_content_min_limit(page_scroller, EINA_FALSE, EINA_FALSE);

   // Set the bouncing behavior. The bounce effect is shown when the scroller reaches the last page
   // In this case, it must be switched off
   elm_scroller_bounce_set(page_scroller, EINA_FALSE, EINA_TRUE);

   // Set the scroller visibility
   elm_scroller_policy_set(page_scroller, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_OFF);

   // Disable the scroll bars
   elm_scroller_page_scroll_limit_set(page_scroller, 1, 1);

   // Enable scroller looping
   elm_scroller_loop_set(page_scroller, EINA_TRUE, EINA_FALSE);

   // Fill the scroller with the content
   scroller_fill(page_scroller, p_width, p_height);

   return page_scroller;
}

To fill the scroller with content using the scroller_fill() function:

static void
scroller_fill(Evas_Object *page_scroller, int p_w, int p_h)
{
   // Create the box object
   Evas_Object *box = NULL;
   box = scroller_box_create(page_scroller);

   // Set the created box as a part of the elm_scroller component
   elm_object_part_content_set(page_scroller, "default", box);

   // Append new pages to the box
   scroller_page_add(box, p_w, p_h, 255, 0, 0);
   scroller_page_add(box, p_w, p_h, 0, 255, 0);
   scroller_page_add(box, p_w, p_h, 0, 0, 255);
   scroller_page_add(box, p_w, p_h, 0, 255, 255);
}

To create the elm_box component:

static Evas_Object *scroller_box_create(Evas_Object *page_scroller)
{
   // Create the box object and set its parent
   Evas_Object *box = NULL;
   box = elm_box_add(page_scroller);

   // Set the horizontal parameter of the box component. It means that the new elements are added horizontally
   elm_box_horizontal_set(box, EINA_TRUE);

   // Set the center alignment of the box parameters
   elm_box_align_set(box, 0.5, 0.5);

   return box;
}

To create the scroller pages:

static void scroller_page_add(Evas_Object *pages_container_box, int p_w, int p_h, int r, int g, int b)
{
   Evas_Object *page = NULL;
   Evas_Object *page_bg = NULL;
   static int page_no = 0;

   char edj_path[PATH_MAX] = {0,};
   char page_name[PATH_MAX] = {0,};

   // Create the Evas_Object rectangle. This is the background of each page
   // It also sets the minimum size of the page (elm_layout object)
   page_bg = evas_object_rectangle_add(evas_object_evas_get(pages_container_box));

   // Set the color of the background
   evas_object_color_set(page_bg, r, g, b, 255);

   // Resize the page
   evas_object_resize(page_bg, p_w, p_h);
   evas_object_size_hint_min_set(page_bg, p_w, p_h);

   // Create the page (elm_layout component) and load the valid edje file
   page = elm_layout_add(pages_container_box);
   app_get_resource(EDJ_FILE, edj_path, (int)PATH_MAX);
   if (!elm_layout_file_set(page, edj_path, GROUP_PAGE)) 
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "failed to set page!");
      evas_object_del(page_bg);
      evas_object_del(page);

      return;
   }

   // Set the page title
   snprintf(page_name, sizeof(page_name), "PAGE %d", page_no++);
   elm_object_part_text_set(page, PART_TEXT_PAGE_NAME, page_name);

   // Set the page background and append it to the box component
   elm_object_part_content_set(page, PART_SWALLOW_PAGE_BG, page_bg);
   elm_box_pack_end(pages_container_box, page);
   evas_object_show(page);
}

Index Implementation

The index component is based on the elm_box object. The box automatically places each index on the screen. The following example shows how the implement the index:

  1. Add the index to the box:
    Evas_Object *box = NULL;
    cb_data_t *init_data = NULL;
    int page_count = -1;
    int i = 0;
    
    // Obtain the number of page scroller pages
    page_count = page_scroller_page_count_get(page_scroller);
    
    // Create the elm_box component
    box = box_create(page_scroller);
    
    // Create and append the indexes to the box component
    for (i = 0; i < page_count; i++) 
    {
       elm_box_pack_end(box, indice_create(box));
    }
    
    // Connect the elm_scroller smart callbacks with valid functions which are used later for index animation
    evas_object_smart_callback_add(page_scroller, "scroll", page_area_changed_cb, init_data);
    evas_object_smart_callback_add(page_scroller, "scroll,drag,start", page_current_changed_start_cb, init_data);
    evas_object_smart_callback_add(page_scroller, "scroll,anim,stop", page_current_changed_stop_cb, init_data);
    
    return box;
    
  2. Create the box component:
    static Evas_Object *box_create(Evas_Object *parent)
    {
       // Create the elm_box component
       Evas_Object *box = NULL;
       box = elm_box_add(parent);
    
       // Set the default box parameters. When the homogeneous parameter is set to EINA_TRUE, 
       // every object in the elm_box has the same size
       elm_box_horizontal_set(box, EINA_TRUE);
       elm_box_homogeneous_set(box, EINA_TRUE);
       elm_box_align_set(box, 0.5, 0.5);
    
       return box;
    }
    
  3. Create the index:
    static Evas_Object *indice_create(Evas_Object *parent)
    {
       // Create index's elm_layout component
       Evas_Object *indice = NULL;
       char edje_path[PATH_MAX] = {0,};
       indice = elm_layout_add(parent);
    
       // Obtain the path to the edje file
       app_get_resource(EDJ_FILE, edje_path, PATH_MAX);
    
       // Set edje file for the layout object
       if (!elm_layout_file_set(indice, edje_path, GROUP_INDICE)) 
       {
          dlog_print(DLOG_ERROR, LOG_TAG, "failed to set edje file");
          evas_object_del(indice);
    
          return NULL;
       }
    }
    
  4. After creating the index elements, set their initial position:
    static Eina_Bool init_position_set_cb(void *data)
    {
       cb_data_t *init_data = (cb_data_t *) data;
       Evas_Object *page_scroller = NULL;
       Evas_Object *index = NULL;
    
       int c_page = -1;
       int i = 0;
    
       // Obtain the pointer to the used objects
       page_scroller = init_data->scroller;
       index = init_data->box;
    
       // Obtain active page in page scroller component
       c_page = page_scroller_current_page_number_get(page_scroller);
    
       // Set valid angles for the indices. The current page angle is equal to 90 degrees
       for (i = 0; i < SCROLLER_PAGES; i++) 
       {
          if (i == c_page)
             indice_rotate(index, i, CUR_ANGLE);
          else
             indice_rotate(index, i, DEF_ANGLE);
       }
    
       return EINA_FALSE;
    }
    

Index Animations

To animate the index position, Evas_Map is used. The Evas library allows different transformations to be applied to all kinds of Evas_Objects. With Evas mapping, you can map the source object's points to the target's 3D positioning. This allows for effects, such as rotating, scaling, and changing the perspective.

To implement index rotation:

static void indice_rotate(Evas_Object *box, int n, double angle)
{
   Evas_Object *indice = NULL;
   Evas_Map *indice_map = NULL;
   int x = -1;
   int y = -1;
   int w = -1;
   int h = -1;

   // Get the index from the index object
   indice = indice_get(box, n);

   // Get the geometry of the index. It is used later for setting the center of the animation
   evas_object_geometry_get(indice, &x, &y, &w, &h);

   // Create a new Evas_Map object and set the number of transformation points
   indice_map = evas_map_new(4);

   // Get the transformation point map from the animated object
   // In this case, each point corresponds to a corner of the index's Evas_Object
   evas_map_util_points_populate_from_object(indice_map, indice);

   // Rotate the object. The angle value is calculated in a callback function connected to "scroll" event
   // cx and cy correspond to the center of the index
   evas_map_util_rotate(indice_map, angle, x + w / 2, y + w / 2);

   // Connect the transformation map to the object
   evas_object_map_set(indice, indice_map);

   // Apply the transformation effect on the index
   evas_object_map_enable_set(indice, EINA_TRUE);
}

After this function is executed, the operation index is rotated.

The following callback handlers are used in the box constructor:

// Invoked when a drag event starts
evas_object_smart_callback_add(page_scroller, "scroll,drag,start", page_current_changed_start_cb, init_data);
// Invoked repeatedly during the drag event
evas_object_smart_callback_add(page_scroller, "scroll", page_area_changed_cb, init_data);
// Invoked when the drag event stops
evas_object_smart_callback_add(page_scroller, "scroll,anim,stop", page_current_changed_stop_cb, init_data);
  • The page_current_changed_start_cb() callback is invoked when the user starts to drag the elm_scroller component:

    static void page_current_changed_start_cb(void *data, Evas_Object *obj, void *event_info)
    {
       current_page = page_scroller_current_page_number_get(obj);
       elm_scroller_region_get(obj, &current_region, NULL, NULL, NULL);
    }
    

    The callback is only used to obtain the current page number and the current region in the horizontal position. The current_region global variable is used to calculate the progress and is depicted in the following figure.

    Figure: Current region in the Scroller Index

    Current region in the Scroller Index

  • After the drag is started, the page_area_changed_cb() callback is invoked repeatedly.

    static void page_area_changed_cb(void *data, Evas_Object *obj, void *event_info)
    {
       int x = 0;
       int w = 0;
       double angle = 0.0;
       int next_page = -1;
       cb_data_t *cb_data = (cb_data_t *) data;
    
       // Get the actual region of the elm_scroller component
       elm_scroller_region_get(obj, &x, NULL, &w, NULL);
    
       // Calculate the angle. The current region is the initial position set in the drag start callback
       // The difference between x and current_region is the progress of the scroll
       // It is multiplied by 90.0 degrees (if progress equals 1.0, the rotation angle is equal to 90.0 degrees)
       angle = (double) (x - current_region)/w * 90.0;
    
       // Calculate the next page number. It is used to get the next index which is also animated
       if (fabs(x - current_region) <= w) 
       {
          next_page = x > current_region ? (current_page + 1) % SCROLLER_PAGES : current_page - 1;
       } 
       else 
       {
          if (x > current_region) 
          {
             next_page = current_page - 1;
          } 
          else 
          {
             next_page = 0;
          }
       }
    
       // Because the loop property of the page scroller is enabled, 
       // the user is able to scroll pages from first to last directly
       // In that case, the next_page calculated before is lower than 0, but can be used to set its valid value
       if (next_page < 0)
          next_page = SCROLLER_MAX_IDX;
    
       // Set the rotation angles of the indexes
       // The next page rotation angle is increased by 360 degrees because the rotate 
       // function does not work if the angle is lower than 0 and the scroller is moved from right to left
       indice_rotate(cb_data->box, current_page, 90 + angle);
       indice_rotate(cb_data->box, next_page, 360 + angle);
    }