В этом уроке мы создадим скроллер контейнер, наполним созданный в предыдущем уроке коробок страницами с изображениями, в качестве страницы будет использоваться стандартный макет. Далее поместим коробок в созданный скроллер. И последним шагом мы используем свойства скроллера для реализации постраничной прокрутки.
Подготовим функцию создания коробка для нужд демо-приложения №3. Для этого удалим создание кнопок из прошлого урока поскольку мы будем использовать макеты как содержимое коробка. У нас получилось следующая функция.
static void
_box_create(UIData *ui)
{
ui->box = elm_box_add(ui->conform);
evas_object_show(ui->box);
}
Теперь добавим скроллер и поместим его в конформант вместо коробка, а сам коробок поместим в скроллер для его дальнейшей прокрутки.
Добавим указатель на скроллер в структуру с главными виджетами.
Evas_Object *scroller;
Получим следующее содержимое нашей структуры с графическими компонентами.
typedef struct _UIData {
Evas_Object *win;
Evas_Object *conform;
Evas_Object *scroller;
Evas_Object *box;
} UIData;
Реализуем следующую функцию для создания скроллера, и помещения в него коробка. Подобное мы уже делали в предыдущих уроках.
static void
_scroller_create(UIData *ui)
{
ui->scroller = elm_scroller_add(ui->conform);
_box_create(ui);
elm_object_content_set(ui->scroller, ui->box);
evas_object_show(ui->scroller);
}
Теперь заменим родительский элемент для коробка. Вместо конформанта теперь передаем скроллер, что бы сохранить правильную иерархию, поскольку необходимо автоматически удалять коробок в случае удаления скроллера. Делаем изменения в следующей функции.
static void
_box_create(UIData *ui)
{
ui->box = elm_box_add(ui->scroller);
...
}
Ну и теперь заменяем содержимое конформанта с коробка на скроллер.
static void
_conformant_create(UIData *ui)
{
...
_scroller_create(ui);
elm_object_content_set(ui->conform, ui->scroller);
...
}
Приступим к добавлению содержимого в коробок. Для начала мы возьмем следующие 5 изображений и будем использовать их в качестве содержимого.
Создадим две функции для загрузки и отображения виджетов-изображений как мы уже делали это в 18-м уроке.
Функция для получения полного пути по заданному имени изображения в папке res/ проекта.
static void
_file_abs_resource_path_get(char *res_file_path, char *abs_path, int buf_size)
{
char *res_dir_path = app_get_resource_path();
if (res_dir_path)
{
snprintf(abs_path, buf_size, "%s%s", res_dir_path, res_file_path);
free(res_dir_path);
}
}
И функция для создания самого виджета изображения.
static Evas_Object *
_image_create(Evas_Object *parent, char *image_name)
{
Evas_Object *image = elm_image_add(parent);
char abs_path_to_image[PATH_MAX] = {0,};
_file_abs_resource_path_get(image_name, abs_path_to_image, PATH_MAX);
elm_image_file_set(image, abs_path_to_image, NULL);
evas_object_size_hint_weight_set(image, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(image, EVAS_HINT_FILL, EVAS_HINT_FILL);
evas_object_show(image);
return image;
}
Теперь создадим наш макет со стандартным стилем.
Воспользуемся стилем elm/layout/body_thumbnail/default мы уже описывали его в 12-м уроке.
Подготовим следующую функцию которая будет создавать объект макета, создавать изображение и помещать его в зону elm.icon.
static Evas_Object *
_page_layout_create(Evas_Object *parent, int image_index)
{
Evas_Object *page_layout = elm_layout_add(parent);
elm_layout_theme_set(page_layout, "layout", "body_thumbnail", "default");
evas_object_size_hint_weight_set(page_layout, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(page_layout, EVAS_HINT_FILL, EVAS_HINT_FILL);
evas_object_show(page_layout);
const int MAX_NAME_LENGTH = 10;
char image_name[MAX_NAME_LENGTH] = {0,};
snprintf(image_name, MAX_NAME_LENGTH, "%d.png", image_index);
Evas_Object *image = _image_create(page_layout, image_name);
elm_object_part_content_set(page_layout, "elm.icon", image);
return page_layout;
}
Мы назвали наши изображения 0.png, 1.png … 4.png, поэтому функция принимает индекс изображения вторым параметром для создания необходимого изображения. Далее мы в цикле будем создавать страничные элементы используя эту функцию и будет удобно передавать счетчик цикла.
Перейдем к самому добавлению элементов в коробок. Воспользуемся функцией
elm_box_pack_end();
Добавим все 5 страниц в наш коробок.
static void
_box_create(UIData *ui)
{
...
for (int i = 0; i <= 4; ++i)
{
Evas_Object *page_layout = _page_layout_create(ui->box, i);
elm_box_pack_end(ui->box, page_layout);
}
...
}
Давайте запустим и посмотрим что же у нас получилось.
Как видно, содержимое коробка расположено слева, это потому что мы не указали весовые параметры коробку. Из-за этого он автоматически занимает минимальный размер, который в нашем случае равен ширине макета, используемого как содержимое для коробка.
Также давайте поменяем ориентацию содержимого с вертикальной на горизонтальную.
Вызовем две следующие функции сразу после создания коробка.
static void
_box_create(UIData *ui)
{
...
elm_box_horizontal_set(ui->box, EINA_TRUE);
evas_object_size_hint_weight_set(ui->box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
...
}
Ниже показано что получилось.
Уже почти идеально. Но тут видно, что первое изображение и последнее не доползают до центра экрана, для того чтобы это исправить, необходимо добавить некоторые отступы в начало и конец коробка. Реализуем отступы с помощью создания двух простых, прозрачных прямоугольников. И установим им размер, который будет рассчитываться динамически во время события изменения размера страничного макета.
Ниже показано схематически, для чего необходимо добавить отступы (зеленые прямоугольники).
Итак, добавим функцию которая будет создавать такой прямоугольник, а затем воспользуемся ей для добавления отстпа в начало и конец коробка.
static Evas_Object *
_padding_item_create(Evas_Object *parent)
{
Evas_Object *padding = evas_object_rectangle_add(evas_object_evas_get(parent));
evas_object_size_hint_weight_set(padding, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(padding, EVAS_HINT_FILL, EVAS_HINT_FILL);
return padding;
}
Для добавления прямоугольника используем следующую функцию:
evas_object_rectangle_add();
Эта функция в отличии от функций elementary создает примитивный объект не имеющий особого поведения как у кнопок, или списков которые поддерживают смарт-колбеки. Хотя примитивные объекты поддерживает простые события наподобие EVAS_CALLBACK_MOUSE_DOWN или EVAS_CALLBACK_SHOW.
Теперь нам необходимо задать размер этим отступам на основе содержимого. Поскольку при создании объектов для отступа мы не можем знать какой размер нужно задать, мы должны дождаться события изменения размера для страницы (макета с изображением), и после этого взять размер.
Используем функцию выше для создания и помещения виджетов для отступа в коробок. Добавляем их после создания всех страниц, воспользовавшись функциями для добавления содержимого в начало и конец коробка.
Добавим указатели на виджеты отступа в структуру с главными виджетами, что бы мы получили доступ к ним в коллбеке на изменение размера.
Evas_Object *padding_start;
Evas_Object *padding_end;
Получим следующее содержимое нашей структуры с графическими компонентами.
typedef struct _UIData {
Evas_Object *win;
Evas_Object *conform;
Evas_Object *scroller;
Evas_Object *box;
Evas_Object *padding_start;
Evas_Object *padding_end;
} UIData;
Теперь создаем и добавляем сами отступы.
static void
_box_create(UIData *ui)
{
...
// Создание страниц с изображениями
ui->padding_start = _padding_item_create(ui->box);
elm_box_pack_start(ui->box, ui->padding_start);
ui->padding_end = _padding_item_create(ui->box);
elm_box_pack_end(ui->box, ui->padding_end);
...
}
Теперь реализуем функцию обратного вызова для события изменения размера последней страницы.
static void
_layout_resize_cb(void *data , Evas *e, Evas_Object *page_layout, void *event_info)
{
int page_width;
int page_height;
int container_width;
int container_height;
UIData *ui = data;
evas_object_geometry_get(page_layout, NULL, NULL, &page_width, &page_height);
evas_object_geometry_get(ui->scroller, NULL, NULL, &container_width, &container_height);
int padding_size = (container_width - page_width) / 2;
evas_object_size_hint_min_set(ui->padding_start, padding_size, container_height);
evas_object_size_hint_min_set(ui->padding_end, padding_size, container_height);
}
Коротко говоря мы получаем разницу между шириной контейнера, и шириной страницы и делим результат пополам. Это и будет размер наших отступов.
Здесь мы воспользовались следующей функцией для получения геометрии объекта (x, y координаты верхнего левого угла, высота и ширина).
evas_object_geometry_get(object, &x, &y, &width, &height);
Ну и наконец зарегестрируем обработчик на событие изменения размера последней страницы. Отредактируем нашу функцию создания коробка и его содержимого.
static void
_box_create(UIData *ui)
{
...
Evas_Object *page_layout;
for (int i = 0; i <= 4; ++i)
{
page_layout = _page_layout_create(ui->box, i);
elm_box_pack_end(ui->box, page_layout);
}
evas_object_event_callback_add(page_layout, EVAS_CALLBACK_RESIZE, _layout_resize_cb, ui);
...
}
Посмотрим на результат.
Контейнер скроллер позволяет использоваться для постраничного просмотра, это означает что при прокрутке он всегда будет останавливать область прокрутке на начале страницы. Мы можем задавать разные размеры страницы, и лимит прокрутки страниц.
Например в данном приложении можно остановить прокрутку так как показано на следующем скриншоте, то есть в любом месте содержимого.
Давайте настроем скроллер для постраничной прокрутки, что бы контент страниц всегда останавливался посредине области.
Для этого воспользуемся функцией.
elm_scroller_page_size_set(Evas_Object *obj, Evas_Coord width, Evas_Coord height);
Функция принимает размер страницы для прокрутки.
В нашем случае мы можем добавить вызов этой функции при событии изменения колбека изменения размера последней страницы, где мы ранее рассчитывали размеры отступов. Добавим вызов в конец функции обработчика, там у нас уже есть размеры страницы.
static void
_layout_resize_cb(void *data , Evas *e, Evas_Object *page_layout, void *event_info)
{
...
elm_scroller_page_size_set(ui->scroller, page_width, page_height);
}
В результате мы получили то, что хотели.
Иногда возникает необходимость получить событие изменения текущей страницы, в реализации скроллера для носимых устройств не предусмотренно этого сигнала, поэтому прийдется реализовавать его своими силами. Для этого мы подпишимся на событие scroll скроллера, а в самом обработчике будем проверять изменилась ли текущая страница.
У нас получилась следующий обработчик события.
static void
_scroll_cb(void *data, Evas_Object *scroller, void *event)
{
static int prev_h_page = 0;
int cur_h_page = 0;
elm_scroller_current_page_get(scroller, &cur_h_page, NULL);
if (cur_h_page != prev_h_page)
{
dlog_print(DLOG_DEBUG, "Lesson21", "Новая активная страница: %d", cur_h_page);
prev_h_page = cur_h_page;
}
}
Здесь мы воспользовались функцией получения порядкового номера текущей страницы скроллера.
elm_scroller_current_page_get(const Evas_Object *obj, int *h_number, int *v_number);
Функция принимает скроллер для которого получить значения, и указатели на целочисленные переменные в которые записать порядковые номера по горизонтали и вертикали для страницы.
В результате мы выводим в лог информацию о изменении текущей страницы, поскольку нам сейчас не к чему прикрепить это событие. Не забудьте добавить включение заголовочного файла для использования логирования.
#include <dlog.h>
Теперь зарегестрируем обработчик для скроллера после создания этого виджета.
static void
_scroller_create(UIData *ui)
{
...
evas_object_smart_callback_add(ui->scroller, "scroll", _scroll_cb, ui);
...
}
Вот что у нас из этого получилось.
В следующем уроке мы привяжем это событие скроллера к изменению индикатора страницы.
Скачать полный исходный код этого урока вы можете здесь WearLesson021.