Mobile native

Naviframe: Creating Navigation Using Naviframes

Naviframes are containers useful for implementing interfaces with several screens having a previous/next relationship.

This feature is supported in mobile applications only.

Warm-up

Become familiar with the Eina, Elementary, and Evas API basics by learning about:

Creating Naviframes

Naviframes are containers useful for implementing interfaces with several screens having a previous/next relationship.

This tutorial shows a UI with three naviframes. Each naviframe is made of 20 screens, each made up of a label with the text "label <n>", a title with the same text, and previous and next buttons, which are used to navigate between the screens.

TODO: screenshots of the staged interface

The naviframe is "one-way": elements are added, and when the user clicks on the "previous" button, they are removed; there is no "next" button by default. To add it, we define a structure that holds the naviframe object along with a stack of the elements that the user has popped by using the "previous" button.

Note that it is possible to create the elements on-the-fly each time the "next" button is pressed. Both approaches are valid.

// NOTE: A zipper is a datastructure for an ordered set of elements and a
// cursor in this set, meaning there are elements before the cursor (which are
// stored inside the naviframe) and after (which are stored in the "popped"
// list.
struct naviframe_zipper 
{
   Evas_Object *naviframe;
   Eina_List *popped;
};

To add several naviframes, create a function that factors their creation and initializes the naviframe_zipper structure defined above.

static struct naviframe_zipper *
_naviframe_add(Evas_Object *parent)
{
   struct naviframe_zipper *z = malloc(sizeof(struct naviframe_zipper));
   z->naviframe = elm_naviframe_add(parent);
   z->popped = NULL;

   evas_object_size_hint_weight_set(z->naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(z->naviframe, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_show(z->naviframe);
   // By default, objects are destroyed when they are popped from the naviframe
   // To save and re-use them, enable "preserve_on_pop"
   elm_naviframe_content_preserve_on_pop_set(z->naviframe, EINA_TRUE);

   return z;
}

Create buttons that are at the top of the naviframe and allow the user to go back and forth between the screens. The naviframe component builds a button for "previous" by default, but allows the programmers to provide their own buttons. It has a specific slot for the "next" button.

// Save the element that is popped from the naviframe
static void
_naviframe_prev(void *data, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
   struct naviframe_zipper *z = data;
   z->popped = eina_list_prepend(z->popped, elm_naviframe_item_pop(z->naviframe));
}

// Set the first element after the current one available and push it to the
// naviframe
static void
_naviframe_next(void *data, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
   struct naviframe_zipper *z = data;
   Evas_Object *label, *prev, *next;
   const char *text;
   Elm_Object_Item *it;

   label = eina_list_data_get(z->popped);
   z->popped = eina_list_remove_list(z->popped, z->popped);
   if (label != NULL) 
   {
      // The UI component is saved inside the naviframe but nothing more; we need
      // to create new buttons and set the title text again (copy the one
      // from the label that is saved).
      text = elm_object_text_get(label);
      // The _button function creates a button which is either "Previous" (-1) or
      // "Next" (1)
      prev = _button(z, -1);
      next = _button(z, 1);
      it = elm_naviframe_item_push(z->naviframe, text, prev, next, label, NULL);
   }
}

When a naviframe and the pages that go inside it are built, populate it.

Remember that three naviframes are created, each populated in a different way. The common bits have been factored out as a function and the specific parts are executed through a callback. The generic function is shown below.

// Generic naviframe-populate function:
// Its third (and last) parameter is a callback for customization, i.e. pushes
// the new items to a specific position; it returns a "context" value that is
// used between its calls and enables behaviors such as "push after the
// previously-pushed item" 
static struct naviframe_zipper*
_naviframe_populate_gen(Evas_Object *parent, const char *id,
      void * (*populate_cb) (Evas_Object *nav, const char *title, Evas_Object
         *prev, Evas_Object *next, Evas_Object *label, Elm_Object_Item *context)
      )
{
   struct naviframe_zipper *z;
   Evas_Object *label, *prev, *next;
   Elm_Object_Item *context = NULL;
   char buf[256];
   int i;

   z = _naviframe_add(parent);

   for (i = 0; i < 20; i++) 
   {
      label = elm_label_add(z->naviframe);
      snprintf(buf, sizeof(buf), "%s [%d]", id, i);
      elm_object_text_set(label, buf);
      evas_object_show(label);
      evas_object_size_hint_weight_set(label, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
      evas_object_size_hint_align_set(label, EVAS_HINT_FILL, EVAS_HINT_FILL);
      // The _button function creates a button which is either "Previous" (-1) or
      // "Next" (1)
      prev = _button(z, -1);
      next = _button(z, 1);
      // Use the populate_cb callback to provide the customization of the way the
      // elements are added inside the naviframe
      context = populate_cb(z->naviframe, buf, prev, next, label, context);
   }

   return z;
}

The prototype of the callbacks is fairly large, but that is because of the syntax for callbacks in C.

// Push items one after the other
static Elm_Object_Item *
_populate_cb__push(Evas_Object *nav, const char *title,
      Evas_Object *prev, Evas_Object *next, Evas_Object *label,
      Elm_Object_Item *context)
{
   return elm_naviframe_item_push(nav, title, prev, next, label, NULL);
}

// Push items one after the other but use insert_after for it
static Elm_Object_Item *
_populate_cb__push_then_insert_after(Evas_Object *nav, const char *title,
      Evas_Object *prev, Evas_Object *next, Evas_Object *label,
      Elm_Object_Item *context)
{
   if (context == NULL) 
   {
      return elm_naviframe_item_push(nav, title, prev, next, label, NULL);
   }
   else 
   {
      return elm_naviframe_item_insert_after(nav, context, title, prev, next, label, NULL);
   }
}

// Push one item and repeatedly insert new items before the last inserted
// item
static Elm_Object_Item *
_populate_cb__push_then_insert_before(Evas_Object *nav, const char *title,
      Evas_Object *prev, Evas_Object *next, Evas_Object *label,
      Elm_Object_Item *context)
{
   if (context == NULL) 
   {
      return elm_naviframe_item_push(nav, title, prev, next, label, NULL);
   }
   else 
   {
      return elm_naviframe_item_insert_before(nav, context, title, prev, next, label, NULL);
   }
}

Create a window with a vertical box, which holds the three naviframes from the Appcore's app_create callback.

static bool
_app_create(void *data)
{
   Evas_Object *w, *box;
   struct naviframe_zipper *z;

   w = elm_win_util_standard_add("Naviframe Test", "Naviframe Test");

   box = elm_box_add(w);
   elm_box_horizontal_set(box, EINA_FALSE);
   elm_box_homogeneous_set(box, EINA_TRUE);
   evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_show(box);
   elm_win_resize_object_add(w, box);

   z = _naviframe_populate_gen(w, "Before", _populate_cb__push_then_insert_before);
   elm_box_pack_end(box, z->naviframe);

   z = _naviframe_populate_gen(w, "After", _populate_cb__push_then_insert_after);
   elm_box_pack_end(box, z->naviframe);

   z = _naviframe_populate_gen(w, "Push", _populate_cb__push);
   elm_box_pack_end(box, z->naviframe);

   evas_object_show(w);
}
Go to top