- PVSM.RU - https://www.pvsm.ru -
Все приложения для часов Pebble делятся на две категории watchapp — просто приложения, и watchface — приложения «часы», которые исходя из названия являются лицом устройства. Отличие «фейсов» — отсутствие реакции на хардварные кнопки, так как «UP» и «DOWN» используются для циклического переключения между установленными watchface.
Но, наверное, в силу низкого разрешения экрана 144x168 px, найти органично вписывающийся в дизайн часов ватчфейс, который при этом выполняет основную свою функцию — отсчет времени, довольно непросто.
Как мне кажется лучше всего на таком экране смотрятся цифры в стиле семисегментных индикаторов.
Ниже, подробнее о том, как добавить в свой watchface элегантного минимализма, индивидуальности и уникальных фишек.
Итак, немного картинок, обрывков кода и в итоге ссылка на готовый проект.
Создание watchface, структура и построение проекта подробно описано в соответствующем разделе документации Build Your Own Watchface [1] [1]. Не буду повторятся, а сразу перейду к
Что будет отличать наше приложение от полутора десятков из Examples и от сотен с mypebblefaces [2]:
Обо всем по порядку:
Для отображения цифр понадобятся два набора, основной большой (цифра 20x38px), для времени:

и дополнительный маленький (цифра 8x16px), для даты и секунд:

Оба набора отрисованы в графическом редакторе, в виде двухцветного png-файла.
Подключаем их как ресурсы в appinfo.json:
"resources": {
"media": [
{
"type": "png",
"name": "DIGITS",
"file": "images/digits.png"
},
{
"type": "png",
"name": "DIGITS_MIDI",
"file": "images/digits_midi.png"
}
]
Графический фреймворк описан в документации [2] [8]. Нас интересует раздел, касающейся работы с растровыми изображениями [3] [9].
Подготовительные действия для работы с растром, создание из ресурса вынесем в отдельную функцию:
/*...*/
static GBitmap *bmp_digits;
static GBitmap *bmp_digits_midi;
/*..*/
static void load_resources() {
bmp_digits = gbitmap_create_with_resource(RESOURCE_ID_DIGITS);
bmp_digits_midi = gbitmap_create_with_resource(RESOURCE_ID_DIGITS_MIDI);
}
/*..*/
static void window_load(Window *window) {
load_resources();
}
не забудем освободить ресурсы:
static void destroy_resources() {
gbitmap_destroy(bmp_digits);
gbitmap_destroy(bmp_digits_midi);
}
/*..*/
static void window_unload(Window *window) {
destroy_resources();
}
Наборы цифр считаны из ресурсов и созданы в виде растра в памяти, теперь необходима функция отрисовки отдельной цифры, при вызове функции указываем графический контекст, на котором будем рисовать, исходный набор из которого мы должны «выдрать» изображение и порядковый номер изображения в наборе:
/*
ctx - графический контекст;
sources - исходное изображение;
bounces - координаты и размер изображения для отрисовки;
number - порядковый номер изображения в наборе.
*/
static void draw_picture(GContext* ctx, GBitmap **sources, GRect bounces,
int number) {
GPoint origin = bounces.origin;
bounces.origin = GPoint(bounces.size.w*number, 0);
GBitmap* temp = gbitmap_create_as_sub_bitmap(*sources, bounces);
bounces.origin = origin;
graphics_draw_bitmap_in_rect(ctx, temp, bounces);
gbitmap_destroy(temp);
}
и для примера, чтобы нарисовать троечку по координатам (10, 0) в контексте:
draw_picture(ctx, &bmp_digits, GRect(10, 0, 20, 38), 3);
к содержанию [10]
Так как у нас будет два независимых экрана, каждый со своим наполнением, реализуем их в виде отдельных слоев, размером с экран часов:
/*..*/
static Layer *standby_layer;
static Layer *info_layer;
/*..*/
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
load_resources();
standby_layer = layer_create(bounds);
layer_add_child(window_layer, standby_layer);
info_layer = layer_create(bounds);
layer_add_child(window_layer, info_layer);
}
Переключение между экранами вынесем в обработчик сервиса «Tick Timer»:
/*..*/
int current_screen = 0;
/*..*/
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
// Каждую минуту помечаем "standby" для отрисовки
if (units_changed & MINUTE_UNIT) {
layer_mark_dirty(standby_layer);
};
switch (current_screen) {
case 0:
// Если слой "standby" скрыт, делаем его видимым и убираем "info"
if (layer_get_hidden(standby_layer)) {
layer_set_hidden(info_layer, true);
layer_set_hidden(standby_layer, false);
};
break;
case 1:
layer_mark_dirty(info_layer);
// Если слой "info" скрыт, делаем его видимым и убираем "standby"
if (layer_get_hidden(info_layer)) {
layer_set_hidden(standby_layer, true);
layer_set_hidden(info_layer, false);
// В зависимости от настройки запускаем таймер возврата на "standby"
if (settings.s_auto) {
standby_timer = app_timer_register(30000, timer_callback, NULL);
};
};
break;
};
}
static void init(void) {
/*..*/
tick_timer_service_subscribe(SECOND_UNIT, tick_handler);
}
Вот и подошли к тому, что нам уже надо вырисовывать контент на экранах. Для примера код для отображения контента на экране ожидания — цифровой циферблат.
Для начала зададим функцию отрисовки для слоя «standby_layer», которая автоматически вызывается, когда это необходимо:
static void window_load(Window *window) {
standby_layer = layer_create(bounds);
layer_add_child(window_layer, standby_layer);
layer_set_update_proc(standby_layer, update_standby);
}
и реализуем отрисовку контента:
static void update_standby(Layer *layer, GContext* ctx) {
GRect bounds = layer_get_bounds(layer);
// Цвет фона - черный
graphics_context_set_fill_color(ctx, GColorBlack);
// Режим композитинга - инвернтный
graphics_context_set_compositing_mode(ctx, GCompOpAssignInverted);
// Заливаем слой
graphics_fill_rect(ctx, bounds, 0, GCornerNone);
time_t temp = time(NULL);
struct tm *tick_time = localtime(&temp);
int hour_dicker = tick_time->tm_hour/10;
int hour_unit = tick_time->tm_hour%10;
int min_dicker = tick_time->tm_min/10;
int min_unit = tick_time->tm_min%10;
// Рисуем цифры
draw_picture(ctx, &bmp_digits, GRect(20, 55, 20, 38), hour_dicker);
draw_picture(ctx, &bmp_digits, GRect(42, 55, 20, 38), hour_unit);
draw_picture(ctx, &bmp_digits, GRect(78, 55, 20, 38), min_dicker);
draw_picture(ctx, &bmp_digits, GRect(100, 55, 20, 38), min_unit);
// Рисуем разделитель
graphics_context_set_fill_color(ctx, GColorWhite);
GRect frame = (GRect) {
.origin = GPoint(bounds.size.w/2-4, 63),
.size = GSize(4, 4)
};
graphics_fill_rect(ctx, frame, 0, GCornerNone);
frame = (GRect) {
.origin = GPoint(bounds.size.w/2-4, 81),
.size = GSize(4, 4)
};
graphics_fill_rect(ctx, frame, 0, GCornerNone);
}
Результат:

аналогично, используя draw_picture рисуем информационный экран, подробнее в исходниках [11].
Результат:

к содержанию [10]
Для переключения экранов задействуем встроенный акселерометр [4] [12]. Для этого подпишемся на «Tap Event Service»:
static void tap_handler(AccelAxisType axis, int32_t direction) {
current_screen = !current_screen;
}
static void init(void) {
/*...*/
tick_timer_service_subscribe(SECOND_UNIT, tick_handler);
accel_tap_service_subscribe(tap_handler);
}
к содержанию [10]
Для автоматического перехода на экран ожидания воспользуемся таймером [4] [12].
/*..*/
AppTimer *standby_timer = NULL;
/*..*/
static void timer_callback() {
current_screen = 0;
}
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
/*..*/
case 1:
layer_mark_dirty(info_layer);
// Если слой "info" скрыт, делаем его видимым и убираем "standby"
if (layer_get_hidden(info_layer)) {
layer_set_hidden(standby_layer, true);
layer_set_hidden(info_layer, false);
// В зависимости от настройки запускаем таймер возврата на "standby"
if (settings.s_auto) {
standby_timer = app_timer_register(30000, timer_callback, NULL);
};
};
break;
/*..*/
}
к содержанию [10]
Для отображения состояния батарейки [6] [13] создадим ресурс с изображениями, соответствующими десяткам процентов (с такой точностью API отдает величину заряда):

"resources": {
"media": [
/*..*/
{
"type": "png",
"name": "BATTERY",
"file": "images/battery.png"
},
/*..*/
]
}
И рисуем на соответствующем экране:
/*..*/
BatteryChargeState charge_state = battery_state_service_peek();
int bat_percent = charge_state.charge_percent/10;
if (charge_state.is_charging) {
bat_percent = 110/10;
};
draw_picture(ctx, &bmp_battery, GRect(0, 0, 8, 15), bat_percent);
/*..*/
Состояние bluetooth [7] [14] отображаем соответствующей иконкой:
"resources": {
"media": [
/*..*/
{
"type": "png",
"name": "BT",
"file": "images/bluetooth.png"
},
/*..*/
]
}
if (bluetooth_connection_service_peek()) {
draw_picture(ctx, &bmp_bt, GRect(0, 0, 8, 15), 0);
};
к содержанию [10]
Итог: watchface, который основное время не раздражает избыточной информацией, читаем и довольно смотрибелен, при желании делится расширенной информацией.
Для заинтересовавшихся:
Код проекта [15] на Bitbucket [16]
Приложение в Pebble App Store [17]
1. Pebble Developers // Build Your Own Watchface [18]
2. Pebble Developers // Graphics [19]
3. Pebble Developers // Graphics Types [20]
4. Pebble Developers // Detecting Acceleration [21]
5. Pebble Developers // Timer [22]
6. Pebble Developers // Measuring Battery Level [23]
7. Pebble Developers // Managing Bluetooth Events [24]
Автор: tmnhy
Источник [25]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/tutorial/76735
Ссылки в тексте:
[1] Build Your Own Watchface [1]: #Anchor1
[2] mypebblefaces: http://www.mypebblefaces.com
[3] шрифт приближенный к 7-segment индикаторам;: #contents1
[4] два экрана: #contents2
[5] переключение между экранами жестом (встряхивание кистью): #contents3
[6] автоматический переход в «standby»: #contents4
[7] отображение состояния батареи и bluetooth-соединения: #contents5
[8] документации [2]: #anchor2
[9] растровыми изображениями [3]: #anchor3
[10] к содержанию: #contents
[11] исходниках: #Sources
[12] акселерометр [4]: #Anchor5
[13] батарейки [6]: #Anchor6
[14] bluetooth [7]: #Anchor7
[15] Код проекта: https://bitbucket.org/tmnhy/7-segment-light/src/042e65e3b8c71afa70cc228f56c99d4b2cc1502d?at=master
[16] Bitbucket: http://bitbucket.org
[17] Pebble App Store: https://apps.getpebble.com/applications/5486da874c38cce14200001f
[18] 1. Pebble Developers // Build Your Own Watchface: http://developer.getpebble.com/getting-started/watchface-tutorial/part1/
[19] 2. Pebble Developers // Graphics: http://developer.getpebble.com/docs/c/group___graphics.html
[20] 3. Pebble Developers // Graphics Types: http://developer.getpebble.com/docs/c/group___graphics_types.html
[21] 4. Pebble Developers // Detecting Acceleration: http://developer.getpebble.com/guides/pebble-apps/sensors/accelerometer/
[22] 5. Pebble Developers // Timer: http://developer.getpebble.com/docs/c/group___timer.html
[23] 6. Pebble Developers // Measuring Battery Level: http://developer.getpebble.com/guides/pebble-apps/app-events/battery-service/
[24] 7. Pebble Developers // Managing Bluetooth Events: http://developer.getpebble.com/guides/pebble-apps/app-events/bluetooth-connection/
[25] Источник: http://habrahabr.ru/post/245297/
Нажмите здесь для печати.