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
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.
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:
- 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;
- 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; }
- 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; } }
- 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, ¤t_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
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); }