The Layout Transitions sample application demonstrates how you can implement an animated transition between different application views. The application implements 3 different screens: A, B, and C. The animation implementation is based on 3 EFL mechanisms: elm_transitions, Ecore_Animator, and Edje.
The following figure illustrates the application views.
Figure: Layout Transitions screens
The application is divided into 3 modules:
- Main module is generated by the Tizen SDK and contains all callbacks from the app_control library necessary to run the application.
- View module is responsible for user interactions and UI creation.
- Animator module is responsible for animations between different views.
Implementation
Main Module
The main module contains the code automatically generated by the Tizen SDK when you create a new native project with EDJE files. The module initializes an application instance and handles app control event callbacks.
The following updates have been introduced to the main module:
-
The main data structure is modified. All pointers to elementary components are placed in an internal s_view_data structure. The module does not access the UI components directly.
typedef struct appdata { s_view_data view; } appdata_s;
All functions connected to the UI element creation are placed in the view_init() function from the view module:
static bool app_create(void *data) { appdata_s *ad = data; if (!view_init(&ad->view)) { // Do something } }
View Module
The view module implements all UI functionality. The following figure illustrates the application layout structure and components tree.
Figure: Layout structure and component tree
To create the view module:
-
The view is initialized in the view_init() function. Each component is created in a separate function:
bool view_init(s_view_data *view) { // Create the main window of the application and set its properties // This is the standard implementation from the UI sample, so it is // not described in detail if (!_create_win(view)) { // Do something } // Create the conformant component // It is also standard code generated from the UI sample if (!_create_conformant(view)) { // Do something } // Create the elm_layout component. This layout loads an EDJE file, // which contains proper placeholders for the toolbar component and // the A, B, and C screens if (!_create_layout(view)) { // Do something } // Create the toolbar component that is used for switching between the // different application views if (!_create_toolbar(view)) { // Do something } }
-
To create the toolbar component and its content:
static bool _create_toolbar(s_view_data *view) { // Create a new elm_toolbar component and set it in the view structure view->toolbar = elm_toolbar_add(view->layout); // Set the toolbar items' display behavior as always visible and expanded elm_toolbar_shrink_mode_set(view->toolbar, ELM_TOOLBAR_SHRINK_EXPAND); // Set the style of the toolbar chosen from the themes available in Tizen elm_object_style_set(view->toolbar, "tabbar"); // Append new items to the toolbar and connect the "choose" callbacks view->screen_a_indice = elm_toolbar_item_append(view->toolbar, NULL, "Screen A", _screen_a_activate, view); view->screen_b_indice = elm_toolbar_item_append(view->toolbar, NULL, "Screen B", _screen_b_activate, view); view->screen_c_indice = elm_toolbar_item_append(view->toolbar, NULL, "Screen C", _screen_c_activate, view); // Set selection properties elm_toolbar_select_mode_set(view->toolbar, ELM_OBJECT_SELECT_MODE_ALWAYS); elm_toolbar_item_selected_set(view->screen_b_indice, EINA_TRUE); // Place the toolbar component in the layout and set its Evas properties elm_object_part_content_set(view->layout, PART_TOOLBAR, view->toolbar); evas_object_size_hint_weight_set(view->toolbar, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_show(view->toolbar); }
-
Each view page is created in the _create_view_page() function:
static bool _create_view_page(s_view_data *view, page_type_t p_type) { // Create a new elm_layout component page = elm_layout_add(view->layout); // Check the page type (A, B, or C) // Each is different but uses the same EDJE file to load the layout switch (p_type) { case PAGE_TYPE_A: // Load the custom page group for the created EDJE file if (!elm_layout_file_set(page, edje_path, GROUP_PAGE_CUSTOM)) { } // Set the properties of the created page elm_object_part_text_set(page, PART_PAGE_TITLE, "Screen-A"); elm_object_signal_emit(page, SIGNAL_SCREEN_A_BG, SIGNAL_SOURCE); view->a_page = page; break; case PAGE_TYPE_B: // Load the default page group if (!elm_layout_file_set(page, edje_path, GROUP_PAGE_DEFAULT)) { } // Fill the default page with animation type selectors if (!_create_animation_selectors(view)) { } break; case PAGE_TYPE_C: // Similar content break; default: } }
-
The default page view consists of labels and 2 sets of elm_radio buttons. The elm_radio selectors are responsible for setting the animation type for the screens A and C. They are created in the _create_animation_selectors(), _fill_animation_selectors(), and _create_radio_button() functions:
static bool _create_animation_selectors(s_view_data *view) { // Create a container for the first group of the selectors view->group_1_box = elm_box_add(view->b_page); // Insert the box in the proper swallow parts and set its parameters elm_object_part_content_set(view->b_page, PART_PAGE_DEFAULT_ANIM_TYPE_A, view->group_1_box); evas_object_size_hint_weight_set(view->group_1_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_show(view->group_1_box); elm_box_align_set(view->group_1_box, 0.5, 0.1); // Create a container for the second group of the selectors view->group_2_box = elm_box_add(view->b_page); // Add items to the created containers if (!_fill_animation_selectors(view)) { } static bool _fill_animation_selectors(s_view_data *view) { // Declare helper variables for handling 2 different groups of radio buttons int r_id_1 = 0; int r_id_2 = 0; int i = 0; // Create the elm_radio component and define the first group of the buttons view->radio_gr_1 = __create_radio_button(view, view->group_1_box, radio_names[0], r_id_1++); if (!view->radio_gr_1) return false; // Create the elm_radio component and define the second group of the buttons view->radio_gr_2 = __create_radio_button(view, view->group_2_box, radio_names[0], r_id_2++); if (!view->radio_gr_2) return false; // Create radio buttons for each type of animation and attach the created components to the group for (i = 1; i < ANIM_TYPES_CNT; i++) { radio = __create_radio_button(view, view->group_1_box, radio_names[i], r_id_1++); if (!radio) return false; elm_radio_group_add(radio, view->radio_gr_1); radio = __create_radio_button(view, view->group_2_box, radio_names[i], r_id_2++); if (!radio) return false; elm_radio_group_add(radio, view->radio_gr_2); } // Variable indicating which radio button is checked elm_radio_value_pointer_set(view->radio_gr_1, &(view->actual_indice_scr_a)); elm_radio_value_pointer_set(view->radio_gr_2, &(view->actual_indice_scr_c)); }
-
The view animations are invoked in callbacks attached to the elm_toolbar component. Each "activate" function calls an external function from the animator module with proper parameters.
The functions for switching the current view between B and C screens are quite similar.
static void _screen_a_activate(void *data, Evas_Object *obj, void *event_info) { Evas_Object *current_page = NULL; animator_type_t tmp = ANIMATION_TYPE_NONE; s_view_data *view = (s_view_data*) data; // Check the active view type // If the user clicks the same tab in the toolbar twice, this function must be stopped if (view->active_view == ACTIVE_VIEW_TYPE_SCR_A) { return; } // When a specific view is displayed for the first time, a new page must be created // Implementation is described later if (!view->a_page) { if (!_create_view_page(view, PAGE_TYPE_A)) { } } // Unset the current view of the main layout and set a new one current_page = elm_object_part_content_unset(view->layout, PART_PAGE); elm_object_part_content_set(view->layout, PART_PAGE, view->a_page); evas_object_show(view->a_page); // Check the selected animation type (implementation is described below) __set_animations_type(view); // Invoke the animator function for switching the view if (view->animator_scr_a != ANIMATION_TYPE_NONE) { tmp = view->animator_scr_c; view->animator_scr_c = ANIMATION_TYPE_NONE; animator_page_switch(current_page, view->a_page, view->animator_scr_a, view->animator_scr_c); view->animator_scr_c = tmp; } // If no animation is selected, hide the visible page to show another screen else { evas_object_hide(current_page); } // Set the active view type view->active_view = ACTIVE_VIEW_TYPE_SCR_A; }
-
The __set_animations_type() function checks which of the radio buttons is selected and sets the valid animation type:
static bool __set_animations_type(s_view_data *view) { switch (view->actual_indice_scr_a) { case 0: view->animator_scr_a = ANIMATION_TYPE_NONE; break; case 1: view->animator_scr_a = ANIMATION_TYPE_WIPE_LEFT; break; case 2: view->animator_scr_a = ANIMATION_TYPE_SPLIT; break; case 3: view->animator_scr_a = ANIMATION_TYPE_FADE; break; case 4: view->animator_scr_a = ANIMATION_TYPE_RESIZE; break; default: break; } switch (view->actual_indice_scr_c) { case 0: view->animator_scr_c = ANIMATION_TYPE_NONE; break; case 1: view->animator_scr_c = ANIMATION_TYPE_WIPE_RIGHT; break; case 2: view->animator_scr_c = ANIMATION_TYPE_SPLIT; break; case 3: view->animator_scr_c = ANIMATION_TYPE_FADE; break; case 4: view->animator_scr_c = ANIMATION_TYPE_RESIZE; break; default: break; } return true; }
Animator Module
The animator module is responsible for smooth transitions between the screens. The 3 different methods provided by the EFL and Elementary APIs are used to demonstrate how the view can be changed:
- elm_transitions
- Ecore_Animator
- Animations in the EDJE file
Using the elm_transition
The Elementary transition mechanism is used to prepare a wipe animation. Only the current page pointer is needed to create this animation. The Elm_Transit_Effect_Wipe_Dir parameter is used to select the wipe direction. If the next page is screen A, the wipe direction is set to ELM_TRANSIT_EFFECT_WIPE_DIR_LEFT, otherwise the dir value is equal to ELM_TRANSIT_EFFECT_WIPE_DIR_RIGHT.
static void _start_wipe_animation(Evas_Object *c_page, Elm_Transit_Effect_Wipe_Dir dir) { // Create a new Elm_Transit object Elm_Transit *c_page_transit = NULL; c_page_transit = elm_transit_add(); // Add the Evas_Object to be animated elm_transit_object_add(c_page_transit, c_page); // Set the animation effect elm_transit_effect_wipe_add(c_page_transit, ELM_TRANSIT_EFFECT_WIPE_TYPE_HIDE, dir); // Set the animation duration elm_transit_duration_set(c_page_transit, 0.5); // Add a callback which is invoked when the Elm_Transit object is deleted // (it means the end of the animation) elm_transit_del_cb_set(c_page_transit, _transit_current_del_cb, c_page); // Start the animation elm_transit_go(c_page_transit); }
Using the Ecore_Animator
The Ecore_Animator is used for resize and fade animations. The following example shows the resize animation. The fade animation is similar, and its implementation is omitted. The Ecore_Animator mechanism requires a callback, which is invoked when a new animator object is created.
To create a resize animation:
-
The _start_resize_animation() function creates a new object and sets parameters for the callback which changes the object properties.
static void _start_resize_animation(Evas_Object *c_page, Evas_Object *n_page) { Ecore_Animator *animator = NULL; animation_data_t *anim_data = NULL; // Get the data necessary for the page animation // Animation logic must know the current size and position of the resized page evas_object_geometry_get(c_page, &x, &y, &w, &h); anim_data = (animation_data_t*) malloc(sizeof(*anim_data)); // Set the obtained data in the animation data structure anim_data->c_obj = c_page; anim_data->n_obj = n_page; anim_data->x = x; anim_data->y = y; anim_data->w = w; anim_data->h = h; // Create the animation object and pass a callback function which is invoked in the animation loop animator = ecore_animator_timeline_add(0.5, _resize_animator_timeline_cb, anim_data); }
-
The implementation for the callback which animates the transit between the pages takes 2 parameters: the data pointer which is passed when the animator object is created and the position value. It is a double value range from 0.0 to 1.0, and it is used to acquire the progress of the current animation.
static Eina_Bool _resize_animator_timeline_cb(void *data, double pos) { // Resize the animation: maximize the next page and minimize the current one // Second parameter is needed with the value opposite to the pos value double frame = 1.0 - pos; animation_data_t *anim_data = (animation_data_t*) data; // Set new size and position values for the current page new_c_w = anim_data->w * frame; new_c_h = anim_data->h * frame; new_c_x = (anim_data->x + (double)(anim_data->w - new_c_w)/2); new_c_y = (anim_data->y + (double)(anim_data->h - new_c_h)/2); // Set new size and position values for the next page new_n_w = anim_data->w * pos; new_n_h = anim_data->h * pos; new_n_x = (anim_data->x + (double)(anim_data->w - new_n_w)/2); new_n_y = (anim_data->y + (double)(anim_data->h - new_n_h)/2); // Use new values to change the parameters of the Evas_Object evas_object_resize(anim_data->c_obj, new_c_w, new_c_h); // Animation must resize the object from the center of the screen, so it // must be moved after it is resized evas_object_move(anim_data->c_obj, new_c_x, new_c_y); // Make a couple of additional changes to smoothen the disappearing effect evas_object_color_set(anim_data->c_obj, 255, 255, 255, 255 * frame); // Use the same functions for the next page animation evas_object_resize(anim_data->n_obj, new_n_w, new_n_h); evas_object_move(anim_data->n_obj, new_n_x, new_n_y); evas_object_color_set(anim_data->n_obj, 255, 255, 255, 255 * pos); // If the pos parameter equals 1.0, it is the last animation tick, so the // animation data is freed and the last properties for the next page are set if (pos == 1.0) { evas_object_hide(anim_data->c_obj); evas_object_color_set(anim_data->c_obj, 255, 255, 255, 255); free(anim_data); return ECORE_CALLBACK_CANCEL; } }
Using EDJE for Animations
If the Edje API is used for the animation, all logic responsible for changing the state of the view is implemented in the .edc script file. To start the animation, the elm_object_signal_emit() function is used. If the application must be informed of the end of the animation, an event listener must be set for a proper signal.
static void _start_split_animation(Evas_Object *c_page, Evas_Object *n_page) { // Send the signal to the EDJE layout file elm_object_signal_emit(c_page, SIGNAL_SPLIT_HORIZONTAL, SIGNAL_SOURCE); // Add an event listener for the animation done signal from the EDJE layout elm_object_signal_callback_add(c_page, SIGNAL_SPLIT_ANIM_DONE, SIGNAL_SOURCE, _split_anim_done_cb, c_page); }
The _split_anim_done_cb() callback function is used only to hide the current page when the animation is finished.
The implementation of the animation in the EDJE layout file is very simple. If the EDJE file consists of parts, you only need to define the custom states for them and programs that react for proper events.
The split animation splits the page horizontally and moves the parts from the top of the screen up and parts from the bottom of the screen down. The EDJE file for the screen B consists of:
- Part for the main title at the top of the page
- Labels for the "Screen-A" and "Screen-C" animation types
- Swallow parts for the elm_hoversel components
The following example describes the animation implementation for the main title. The implementation for other parts is similar.
part { name: PART_PAGE_TITLE; type: TEXT; description { state: "default" 0.0; // Default state of the part defines the initial position and size of the part rel1 {relative: 0.0 0.05;} rel2 {relative: 1.0 0.25;} } description { state: "split_animation" 0.0; inherit: "default" 0.0; // In a split animation state, the part moves up the screen, so the // relative parameters are negative rel1 {relative: 0.0 -0.05;} rel2 {relative: 1.0 -0.25;} } }
To change the state of the part and animate its position change, a program is used in the EDJE file:
program { name: "split_anim_start"; // Program is executed when the EDJE file receives the SIGNAL_SPLIT_HORIZONTAL // signal from the SIGNAL_SOURCE signal: SIGNAL_SPLIT_HORIZONTAL; source: SIGNAL_SOURCE; // Define the program action. In this case, STATE_SET means that the state of the // part is changed to "split_animation" 0.0 action: STATE_SET "split_animation" 0.0; // Define the program target target: PART_PAGE_TITLE; // Define the program to be called after the "split_anim_start" program after: "animation_done"; // Set the animation type to DECELERATE and its duration time to 0.5 seconds transition: DECELERATE 0.5; }
Each program defined in the EDJE file can perform only 1 action. If 1 program must be connected to more than 1 action property, the after element must be used to define the next action. In this sample, after invokes the animation_done program:
program { name: "animation_done"; // This action emits a signal to inform that the animation handled in the animator module is finished action: SIGNAL_EMIT SIGNAL_SPLIT_ANIM_DONE SIGNAL_SOURCE; }