Page Example

Layout transitions Sample Overview

Mobile native

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

Screen-A Screen-B (default) Screen-C

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

Layout structure and component tree

To create the view module:

  1. 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
       }
    }
    
  2. 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);
    }
    
  3. 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:
    
       }
    }
    
  4. 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));
    }
    
  5. 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;
    }
    
  6. 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:

  1. 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);
    }
    
  2. 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;

}