Опубликовано: 25 Октября 2017
(Демо приложение №4)
Круглый селектор (Rotary Selector)

На протяжении следующих 4 уроков мы создадим демо-приложение, в котором будет создан виджет со списком приложений, которые можно будет запускать, а если приложение не установленно на часах, запускать Samsung Galaxy Apps для скачивания и установки приложения.

Мы рассмотрим виджет Rotary Selector из модуля расширения библиотеки EFL, который не является стандартным виджетом библиотеки elementary, модули менеджера пакетов и приложений для получения списка установленных приложений и информации о них, модуль для запуска установленного приложения, и возможность запуска Samsung Galaxy Apps для загрузки и установки приложения.

За основу возьмем шаблон созданный в 11-м уроке. Здесь вы можете загрузить проект WearableUITemplate.

В этом уроке мы рассмотрим виджет Rotary Selector, его основные функции и типы данных, и добавим его в наше демо приложение.

Введение в Rotary Selector

Rotary selector – виджет из расширения библиотеки EFL, он не входит в состав стандартных виджетов библиотеки Elementary и разработан специально для круглых носимых устройств. Этот виджет состоит из нескольких элементов расположеных по кругу носимого устройства на одной или нескольких страницах.

Rotary Selector позволяет удобно выбирать элементы или переходить на следующую или предыдущую страницу с помощью вращения безеля. Этот виджет будет полезен, если к примеру добавить в него элементы настроек приложения, вместо использования стандартного списка, или к примеру для отображения и предоставления выбора некоторых категорий в приложении. Можно в некоторых случаях рассмотривать виджет rotary selector, как замену стандартного списка на часах, так можно увидеть больше элементов на экране и иметь более близкий внешний вид к стандартным приложениям часов, если это необходимо.

Создание Rotary Selector

Давайте добавим виджет Rotary Selector в наше демо приложение, и рассмотрим несколько важных функций.

Для начала расширим нашу структуру UIData добавив в нее указатель на виджет rotary selector

typedef struct _UIData {
   Evas_Object *win;
   Evas_Object *conform;
   Evas_Object *rotary_selector;
} UIData;

Дальше напишем статическую функцию для создания и настройки виджета rotary selector

static void
_rotary_selector_create(UIData *ui)
{
}

Чтобы создать виджет rotary selector необходимо внутри функции вызвать следующую функцию

ui->rotary_selector = eext_rotary_selector_add(ui->conform);

Функция создания принимает единственный параметр - указатель на компонент родителя, который будет отвечать за удаление виджета rotary selector.

Как было сказано выше, основным преимуществом виджета rotary selector есть быстрый выбор элементов и перемещение по страничкам с помощь безеля. Для использования последнего необходимо активировать эту возможность для виджета, вызвав следующую функцию.

void eext_rotary_object_event_activated_set(Evas_Object *obj, Eina_Bool activated);

Первым параметром передается указатель на обьект rotary selector, а вторым параметром передается логический флаг для активации или деактивации навигации между элементами используя безель.

Вызываем эту функцию после создания rotary selector для активации вращения

static void
_rotary_selector_create(UIData *ui)
{
   ...

   eext_rotary_object_event_activated_set(ui->rotary_selector, EINA_TRUE);
}

Далее поместим наш обьект rotary selector в комформант и отобразим его

static void
_rotary_selector_create(UIData *ui)
{
   ...

   elm_object_content_set(ui->conform, ui->rotary_selector);
   evas_object_show(ui->rotary_selector);
}

Вызовем нашу функцию для создания виджета, после создания конформанта.

static void
_conformant_create(UIData *ui)
{
   ...

   _rotary_selector_create(ui);

   ...
}

Если запустить приложение, то на экране мы ничего не увидим, кроме индикатора выбора элемента, расположеного в правом верхнем углу экрана. Если мы начнем нажимать на виджете, то можно увидеть как индикатор меняет свое состояние на нажатое, можно также попробовать повращать безель.

Добавление элементов в Rotary Selector

Теперь давайте добавим элементы в круглый селектор, чтобы увидеть все прелести управления им.

Для добавление элементов в круглый селектор, необходимо вызвать следующую функцию

Eext_Object_Item* eext_rotary_selector_item_append(Evas_Object *obj);

Функция принимает единственный параметр - указатель на обьект rotary selector и возвращает указатель на структуру для созданого элемента. Рассмотрим определения этой структуры

struct _Eext_Object_Item {
   Evas_Object *obj;
   void *data;
};

Первое поле - это собственно указатель на графические данные созданного элемента, а вот второе поле является очень интересным и очень полезным в использовании, это указатель на данные, которые можно будет потом использовать при работе с элементом. Пример использования этого поля мы увидим следующем уроке.

Давайте добавим в наше приложение 15 элементов

static void
_rotary_selector_create(UIData *ui)
{
   ...

   const int items_count = 15;
   for (int i = 0; i < items_count; ++i)
     {
        Eext_Object_Item *item = eext_rotary_selector_item_append(ui->rotary_selector);
     }

   ...
}

Далее получив указатель на созданный элемент можно настроить его, добавив в него текст вызвав следующую функцию

void eext_rotary_selector_item_part_text_set(Eext_Object_Item *item,
                                             const char *part_name,
                                             const char *text);

Первый параметр это указатель на элемент которому необходимо установить текст, второй параметр это имя текстовой области в которую будет устанавливаться текст, а третий параметр - сам текст.

У элемента есть две текстовые области:

"selector,main_text" - для основного текста элемента

"selector,sub_text" - для дополнительного текста элемента

Стоит отметить что дополнительный текс элемента, является опциональным, это как уточняющая информация, и если текст имеет большую длину, то он будет отображаться как бегущая строка.

Давайте добавим немного информации в наше приложение для созданных элементов, вызвав функции добавления текста

 static void
_rotary_selector(UIData *ui)
{
   ...

   for (int i = 0; i < items_count; ++i)
     {
        ...

        const char MAX_TEXT_SIZE = 20;
        char item_text[MAX_TEXT_SIZE] = {'\0',};
        snprintf(item_text, MAX_TEXT_SIZE, "Элемент %d", i + 1);
        eext_rotary_selector_item_part_text_set(item, "selector,main_text", item_text);
        eext_rotary_selector_item_part_text_set(item,
                                                "selector,sub_text",
                                                "Доп. информация");
     }

   ...
}

Можно запустить приложение и посмотреть что получилось.

Как и ожидалось на экране отобразилось 15 элементов с основным и дополнительным текстом, при этом можно увидеть что наш дополнительный текст отображается в виде бегущей строки поскольку имеет большую длину.

Также когда мы вращаем безелем, то видно как наш селектор перемещается по кругу экрана, и если он достигает конца текущей страницы то переходит на следующую.

Одной из основных функций является функция добавления содержимого в элемент, при этом стоит заметить что содержимое может быть разным при разных состояниях элемента.

Для добавления содержимого используется следующая функция.

void eext_rotary_selector_item_part_content_set(Eext_Object_Item *item,
                                                const char *part_name,
                                                Eext_Rotary_Selector_Item_State state,
                                                Evas_Object *content);

Первый параметр это указатель на элемент виджета rotary selector, второй параметр имя области в которую мы будем добавлять содержимое, третий параметр - состояние элемента в котором будет отображатся контент, четвертый параметр - указатель на объект самого содержимого которое мы помещаем в элемент виджета.

Приведем перечень названий областей для вставки содержимого:

"item,icon" – для вставки картинки в элемент

"selector,content" – для изменения картинки селектора для выбора элемента

"item,bg_image" – для изменения фона элемента

"selector,content" – для изменения контента селектора

Стоит заметить, что самой часто используемой областью является "item,bg_image".

Ниже представлены доступные состояния элемента в которых он может находиться.

EEXT_ROTARY_SELECTOR_ITEM_STATE_NORMAL – элемент в нормальном сотоянии

EEXT_ROTARY_SELECTOR_ITEM_STATE_PRESSED – элемент в нажатом сотоянии

EEXT_ROTARY_SELECTOR_ITEM_STATE_DISABLED – элемент не активный

Использование этой функции будет продемонстрировано в следующих уроках.

Наряду с установкой контента, также есть функция получения содержимого элемента, ниже рассмотрим ее описание.

Evas_Object* eext_rotary_selector_item_part_content_get(const Eext_Object_Item *item,
                                                        const char *part_name,
                                                        Eext_Rotary_Selector_Item_State state);

Эта функция аналогична выше описаной функции для установки контента, но с одним отличием вместо установки контента, функция возвращает контент.

Ниже перечислены функции с их описанием, которые могут понадобится при работе с круглым селектором.

const Eina_List* eext_rotary_selector_items_get(const Evas_Object *obj);

Функция принимает указатель на виджет rotary selector, и возвращает константный указатель на список элементов, заметим что он константный, т.е. данные как бы служат только для чтения.

Чтобы получить текущий выделенный элемент, необходимо использовать следующую функцию

Eext_Object_Item* eext_rotary_selector_selected_item_get(const Evas_Object *obj);

Функция принимает указатель на виджет rotary selector, и возращает указатель на текущий выделенный элемент.

Также стоит упомянуть функцию и для установки выделения указанного элемента

void eext_rotary_selector_selected_item_set(Evas_Object *obj, Eext_Object_Item *item);

Первый параметр это указатель на rotary selector, второй параметр это указатель на существующий элемент который необходимо выделить. При вызове этой функции круглый селектор переместит индикатор на указанный элемент, таким образом виджет поддерживает выделение только одного элемента в конкретный момент.

В этой функции есть одно маленькое но, которое мы расмотрим немного позже.

Обработка событий rotary selector

Давайте расмотрим основные сигналы виджета rotary selector, и добавим функции для обработки этих сигналов в наше приложение.

Круглый селектор поддерживает два смарт-сигнала на которые возможно добавить обработчики.

"item,clicked" – сигнал при нажатии на элемент на экране

"item,selected" – сигнал когда элемент был выбран используя безель

Теперь приведем пример как использовать эти сигналы. Добавим их в наше демо-приложение.

Внесем изменения в функцию для создания виджета rotary selector, добавив вызовы функции которые регистрируют обработчики на выше перечисленные сигналы.

static void
_rotary_selector_create(UIData *ui)
{
   ...

   evas_object_smart_callback_add(ui->rotary_selector, "item,clicked", _item_clicked_cb, NULL);
   evas_object_smart_callback_add(ui->rotary_selector, "item,selected", _item_selected_cb, NULL);

   ...
}

Реализуем функции обработчики сигналов, и выведем в лог информацию что вызов этих обработчиков был выполнен.

static void
_item_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
   dlog_print(DLOG_INFO, "lesson24", "Элемент нажат");
}

static void
_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
{
   dlog_print(DLOG_INFO, "lesson24", "Элемент выбран");
}

Не забываем добавить заголовочный файл модуля логгирования.

#include <dlog.h>

Запустим наше приложение и запустим окно логирования, выберем какой тип сообщений нас интересует, в нашем случае информационные, дальше установим фильтр по тегу.

Если мы начнем вращать безель то в окне логирования при изменении активного элемента будут выводится сообщения о выборе элемента, а в случае нажатия на элемент будет выводится информация о нажатии на элемент.

На протяжении следующих уроков мы будем наполнять эти обработчики функциональностью.

В этом уроке приведем пример использования сигнала выделения элемента, но заметим что он будет использоваться только в этом уроке, а в с следующих уроках он будет удален.

Внесем изменения в функцию обработки сигнала выделения элемента, добавив туда код который будет случайным образом менять цвет элемента.

Для изменения цвета элемента круглого селектора есть следующая функция.

void eext_rotary_selector_item_part_color_set(Eext_Object_Item *item,
                                              const char *part_name,
                                              Eext_Rotary_Selector_Item_State state,
                                              int r,
                                              int g,
                                              int b,
                                              int a);

Первый параметер это указатель на элемент, второй параметр это имя области элемента которой мы будем изменять цвет, третий параметр состояние элемента (перечень состояний мы расматривали ранее в этом уроке) с 4 по 7 параметр сответсвенно цвет в формате RGBA (значения от 0 до 255 включительно).

Приведем перечень найболее часто используемых областей:

"item,bg_image" – для фона элемента

"item,icon" – для картинки элемента

"selector,icon" – для картинки индикатора выбора

Для нашего примера мы рассмотрим область "item,bg_image" которая обозначает фон элемента.

Добавим заголовочные файлы необходимый получения времени и использования генератора случайных чисел

#include <time.h>
#include <stdlib.h>

Инициализируем зерно генератора случайных чисел текущим временем.

static bool
_app_create_cb(void *data)
{
   ...

   srand(time(NULL));

   ...
}

Внесем изменение в функцию регистрации обработчика события выделения элемента, передав в наш обработчик указатель на rotary selector который мы затем используем внутри обработчика как пользовательсие данные.

static void
_rotary_selector_create(UIData *ui)
{
   ...

   evas_object_smart_callback_add(ui->rotary_selector,
                                  "item,clicked",
                                  _item_clicked_cb,
                                  ui->rotary_selector);

   evas_object_smart_callback_add(ui->rotary_selector,
                                  "item,selected",
                                  _item_selected_cb,
                                  ui->rotary_selector);

   ...
}

И в обработчиках вызовем функцию для получение текущего выделеного элемента круглого селектора для его дальнейшей перекраски. Потом используя функцию получения случайных чисел, сгенерируем числа от 0 до 255 для RGB цвета, и установим полученый цвет фону элемента в обработчике выделения элемента. А в обработчике нажатия будем всегда красить элемент в белый цвет.

static void
_item_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
   dlog_print(DLOG_INFO, "lesson24", "Элемент нажат");

   if (!data) return;

   Evas_Object *rotary_selector = data;
   Eext_Object_Item *selected_item = eext_rotary_selector_selected_item_get(rotary_selector);

   eext_rotary_selector_item_part_color_set(selected_item,
                                            "item,bg_image",
                                            EEXT_ROTARY_SELECTOR_ITEM_STATE_NORMAL,
                                            255, 255, 255, 255);
}

static void
_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
{
   dlog_print(DLOG_INFO, "lesson24", "Элемент выбран");

   if (!data) return;

   Evas_Object *rotary_selector = data;
   Eext_Object_Item *selected_item = eext_rotary_selector_selected_item_get(rotary_selector);

   int r = rand() % 256;
   int g = rand() % 256;
   int b = rand() % 256;

   eext_rotary_selector_item_part_color_set(selected_item,
                                            "item,bg_image",
                                            EEXT_ROTARY_SELECTOR_ITEM_STATE_NORMAL,
                                            r, g, b, 255);
}

Запустим приложение.

Теперь если мы начнем вращать безель, мы увидем как случайным образом меняется цвет фона элемента.

Полный код урока вы можете взять здесь WearLesson024.

Подводные камни rotary selector

Как было написано ранее расcмотрим особое поведение функции установки текущего элемента.

Предположем мы решили устанавливать последний выбраный элемент при открытии приложения (запомнив его соответственно после закрытия). После создания элемента и его настройки, если он соответсвует нашему условию (тоесть его данные равны данным сохраненного элемента), мы вызывем функцию eext_rotary_selector_selected_item_set установив созданый элемент выделеным.

Запустив приложение мы увидем что индикатор выбора элемента будет находится всегда на первом элементе, а не на том котый мы хотим. Проблема связана с тем что на момент вызова нашей функции виджет еще не успел полностью прорисоватся, поэтому при вызове функции активации элемента, этого элемента графически еще не существует.

Решение этой проблемы это отложенный запуск функции выбора элемента.

Для начала необходимо вызвать функцию для регистрации обработчика на событие отображение виджета rotary selector.

evas_object_event_callback_add(rotary_selector, EVAS_CALLBACK_SHOW, _on_show_rotary, id_item);

Далее в этом обработчике необходимо создать таймер с интервалом, который будет зависеть от количества элементов. К примеру при 25 элементах вполне достаточно 0.2 секунды

static void
_on_show_rotary(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   show_selected_item_rotary = ecore_timer_add(0.2, _timer_select_item_rotary_cb, data);
}

В обработчике таймера вызываем функцию установки текущего элемента

static Eina_Bool
_timer_select_item_rotary_cb(void *data)
{
   ...

   eext_rotary_selector_selected_item_set(rotary_selector, selected_item);

   return ECORE_CALLBACK_DONE;
}

Вот и все, теперь при запуске приложения, индикатор выбора будет отображаться на установленом нами элементе.

Использование таймеров для запуска отложенных процедур мы подробнее раскажем в более поздних уроках.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *