Розробка для SailfishOS: основи

Доброго дня! Минулого тижня я написав про те як почати розробляти під мобільну платформу Sailfish OS. Сьогодні я хотів би розповісти про життєвому циклі програм Sailfish, про створення сторінок, додатки і управління ними, а також про деяких специфічних особливостях мобільних додатків, які слід враховувати при розробці під Sailfish OS, зокрема управління орієнтацією пристрою.

До складу SailfishOS SDK (робота з яким була описана в попередній статті) входить Sailfish Silica — QML модуль, що використовується для створення Sailfish додатків. Даний модуль містить QML компоненти, які виглядають і управляються відповідно до стандартів додатків для Sailfish. Крім іншого, Sailfish Silica так само містить інструменти для створення специфічних елементів Sailfish додатків, таких як, наприклад, Cover Page, які були трохи порушені в минулій статті. Для того, щоб скористатися модулем Sailfish Silica та його інструментами та компонентами, необхідно просто імпортувати цей модуль в QML файли коду програми. Це буде показано в прикладі трохи пізніше.

ApplicationWindow

QML основний будь-якого Sailfish програми є компонент ApplicationWindow, який описує вікно програми і містить інтерфейс програми і взагалі є основною і обов'язковою вхідною точкою завантаження Sailfish програми. Екрани програми реалізуються за допомогою компонента Page. При цьому ApplicationWindow містить властивість initialPage, що дозволяє встановити початковий екран програми. Таким чином мінімальна додаток для платформи Sailfish буде виглядати наступним чином:
import QtQuick 2.2
import Sailfish.Silica 1.0

ApplicationWindow {
initialPage: Component {
Page {
Label {
text: "Привіт, Хабр!"
anchors.centerIn: parent
}
}
}
}

Цей додаток буде відображати одну просту сторінку з написом Привіт, Хабр! посередині. Не обов'язково описувати саму сторінку прямо в описі властивості, можна просто передати туди id сторінки або URL файлу, де описана сторінка.

Page Stack

Крім початкової сторінки, ApplicationWindow так само містить властивість pageStack — містить компонент Page Stack, що дозволяє управляти стеком екранів (сторінок) додатка. У прикладі вище, Page Stack складається всього з однієї сторінки, яка була покладена на цей стек за допомогою властивості initialPage. Додати сторінку на верх стека сторінок (і, відповідно, відобразити її на екрані) можна за допомогою методу push(), передаючи йому в якості аргументу шлях до QML файлу зі сторінкою або id цієї сторінки. Розширимо наш приклад, додавши під напис кнопку, при натисканні на яку буде відбуватися перехід на наступну сторінку (будемо припускати, що код цієї сторінки міститься у файлі SecondPage.qml):
ApplicationWindow {
initialPage: initialPage
Page {
id: initialPage
Label {
id: helloLabel
text: "Привіт, Хабр!"
anchors.centerIn: parent
}
Button {
text: "Наступний"
anchors.top: helloLabel.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml"))
}
}
}

В метод push() так само, замість однієї сторінки, можна передати масив, що містить кілька сторінок. В такому випадку всі ці сторінки будуть додані на стек. У даного методу є ще два опціональних аргументу. Перший — це параметри сторінки, а другий — тип операції, визначає потрібно використовувати анімацію при переході на задану сторінку. Він може бути одним з двох значень: PageStackAction.Animated або PageStackAction.Immediate. Даний аргумент так само присутній і у всіх інших методах Page Stack, які відповідають за зміну поточної сторінки (наприклад, метод pop, який буде розглянуто далі). За замовчуванням усі переходи здійснюються з анімацією, що зручно. Якщо з якоїсь причини анімація при переході не потрібна, можна викликати метод наступним чином:
pageStack.push(Qt.resolvedUrl("SecondPage.qml"), { }, PageStackAction.Immediate)

Для того, щоб повернутися на попередню сторінку потрібно на компоненті Page Stack викликати метод pop(). Цей метод прибере зі стека верхню сторінку і, відповідно, перейде на сторінку назад. Опціонально методом можна вказати деяку сторінку, яка вже є на стеку. У цьому випадку, метод прибере зі стека всі сторінки, розташовані на стеку вище зазначеної. Тут так само варто відзначити, що перехід назад в платформі Sailfish OS реалізований з допомогою горизонтального свайпа від лівого краю екрана, проте в Sailfish Silica даний функціонал вже реалізований і при цьому жесті автоматично викликається метод pop(), що зручно, оскільки розробнику не потрібно докладати зусиль для реалізації стандартного функціоналу.

Крім вищеописаних методів, Page Stack так само надає такі властивості як depth (кількість сторінок у стеку) і currentPage (поточна сторінка), а також такі методи, як:
  • replace() — замінює поточну верхню сторінку на стеку,
  • pushAttached() — додає сторінку на верх стека, але не здійснює перехід до неї (користувач може перейти до сторінки з допомогою горизонтального свайпа від правого краю екрану),
  • navigateForward() navigateBack() — здійснюють перехід на, відповідно, наступну або попередню сторінку до стеку щодо поточної, при цьому не змінюючи сам стек.
Звичайно, вище описані не всі методи і властивості компонента Page Stack. Однак, основні, які можуть стати в нагоді в першу чергу, я спробував описати. Детальніше про компонент можна прочитати в офіційній документації.

Dialog

Діалоги в Sailfish OS являють собою ті ж самі сторінки. Проте, призначені вони для того, щоб показати користувачеві деякі дані, з якими він може погодитися або не погодитися. Причому зробити він може це як натисненням на кнопки, так і свайпом вліво (погодитися) або вправо (відмовитися). Оскільки, діалог являє собою особливу сторінку, то і в Sailfish Silica компонент Dialog «успадковується» від компонента Page. Замінимо в попередньому прикладі перехід на наступну сторінку на показ мінімального діалогу. Для цього опишемо діалог в нашому ApplicationWindow:
ApplicationWindow {
initialPage: initialPage
Page {
id: initialPage
Label {
id: helloLabel
text: "Привіт, Хабр!"
anchors.centerIn: parent
}
Button {
text: "Наступний"
anchors.top: helloLabel.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: pageStack.push(dialog)
}
}
Dialog {
id: dialog
Label {
text: "Я - діалог"
anchors.centerIn: parent
}
}
}

Навіть показ діалогу здійснюється додаванням його на стек сторінок. Якщо запустити програму і натиснути на кнопку, то ми побачимо наступне:

Як бачите, зовні цей мінімальний діалог нічим не відрізняється від звичайної сторінки. Однак, його поведінка відрізняється: при свайпе вліво або вправо (або натисканні на білі області у верхньому лівому або правому куті) діалог закриється і буде показана початкова сторінка програми. При цьому в залежності від напрямку свайпа викличеться відповідний сигнал діалогу (onRejected або onAccepted). Це можна перевірити, додавши в діалог обробники даних сигналів, які будуть змінювати текст на головній сторінці:
onAccepted: helloLabel.text = "Погодився"
onRejected: helloLabel.text = "Відмовився"

Так само на діалозі можна з допомогою компонента DialogHeader додати стандартні кнопки «Cancel» і «Accept» вгорі діалогу. При цьому для відображення даних кнопок досить просто додати порожній компонент. Опціонально можна так само вказати властивість title, яке визначить текст, який буде розташований під кнопками. Даний текст зазвичай використовується для відображення питання до користувача. Додамо DialogHeader в діалог з прикладу вище:
Dialog {
id: dialog
DialogHeader {
title: "Простий діалог"
}
Label {
text: "Я - діалог"
anchors.centerIn: parent
}
onAccepted: helloLabel.text = "Погодився"
onRejected: helloLabel.text = "Відмовився"
}

Тепер він виглядає так:


Слід зазначити, що при запуску прикладу вище ви можете побачити наступні попередження:
[W] unknown:189 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:189: TypeError: Cannot read property 'backIndicatorDown' of null
[W] unknown:194 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:194: TypeError: Cannot read property 'backIndicatorDown' of null
[W] unknown:247 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:247: TypeError: Cannot read property 'forwardIndicatorDown' of null
[W] unknown:242 - file:///usr/lib/qt5/qml/Sailfish/Silica/DialogHeader.qml:242: TypeError: Cannot read property 'forwardIndicatorDown' of null

На функціональності вони ніяк не позначаються, але в інтернеті знайти причину даних попереджень мені не вдалося. Схоже, що це невелика недоробка, оскільки Sailfish Silica все ще знаходиться в розробці. Будемо сподіватися, що в майбутньому ці недоліки будуть виправлені.

Детальніше про Dialog можна почитати в офіційній документації.

Життєвий цикл додатків і Cover

Життєвий цикл додатків для Sailfish OS досить простий. Оскільки в платформі реалізована повноцінна багатозадачність, додаток може знаходиться в одному з трьох станів: або воно не працює зовсім, або воно працює у фоні (background), або працює в активному режимі (foreground). При цьому в активному режимі додаток розгорнуто на весь екран, тоді як у фоновому режимі додаток представлено своєї мініатюрою (званої cover) на головному екрані системи (про це було трохи написано в попередній статті). Визначити в якому саме стані перебуває додаток можна за допомогою властивості Qt.application.state. Якщо додаток є фоні, ця властивість приймає значення Qt.ApplicationInactive. В іншому випадку — Qt.ApplicationАctive. Стан програми необхідно знати і використовувати, наприклад, для зупинки важких обчислювальних завдань або анімацій, коли додаток знаходиться в фоні, щоб не витрачати ресурси системи.

Коли додаток знаходиться в тлі, на головному екрані відображається його мініатюра — cover. Описати цей cover в коді програми можна за допомогою компонента Cover. За промовчанням у програмі вже встановлено cover, який виглядає наступним чином:

Встановити свій cover можна за допомогою властивості cover компонента ApplicationWindow. Переробимо приклад вище так, щоб при натисканні на кнопки у діалозі змінювався текст не на головному екрані програми, а на його cover:
ApplicationWindow {
initialPage: initialPage
cover: cover
Page {
id: initialPage
// Опис головної сторінки додатки...
}
Cover {
id: cover
transparent: true
Label {
id: coverLabel
text: "Привіт, Хабр!"
anchors.centerIn: parent
}
}
Dialog {
id: dialog
DialogHeader {
title: "Простий діалог"
}
Label {
text: "Я - діалог"
anchors.centerIn: parent
}
onAccepted: coverLabel.text = "Погодився"
onRejected: coverLabel.text = "Відмовився"
}
}

Звичайно, даний приклад покликаний лише ознайомити читачів з роботою з компонентом Cover. В реальному додатку cover представляє сам додаток на головному вікні системи. Тому він повинен, по-перше, представляти програму так, щоб користувач при першому погляді міг дізнатися, що це мініатюра даного конкретного додатка. По-друге, cover повинен містити мінімальну кількість найважливішої інформації, оскільки всі деталі користувач може побачити відкривши сам додаток. І, нарешті, по-третє, як і показано в прикладі вище, cover додатки повинен змінюватися, слідом за станом самого додатка і його даними.

Sailfish OS так само дозволяє cover виконувати ресурсномісткі завдання: анімації, обчислення і т. д. Однак, варто зазначити, що оскільки програма може знаходиться в тлі разом з іншими додатками і його мініатюра відображена на ряду з іншими, то ці завдання не повинні навантажувати систему постійно і повинні виконуватися періодично. Наприклад, погодне додаток має оновлювати свій cover тільки коли з сервера приходять нові дані про погоду. Крім того, не варто виконувати такі завдання, коли мініатюра не видно користувачеві (наприклад, коли закритий головний екран). Для цього можна використовувати властивість status, яке приймає одне з наступних значень:
  • Cover.Inactive — мініатюра не видно і користувач не може з нею взаємодіяти,
  • Cover.Active — мініатюра видно і користувач може з нею взаємодіяти,
  • Cover.Activating — мініатюра переходить у статус Cover.Active
  • Cover.Deactivating — мініатюра переходить у статус Cover.Inactive.


Крім цього cover так само може надавати користувачеві можливість керування додатком безпосередньо з самої мініатюри. Для цього з допомогою компонента CoverActionList, усередині якого визначаються компоненти CoverAction, можна додати на мініатюру кнопки. Наприклад, для музичного плеєра це можуть бути кнопки зупинки і відтворення композиції, а також кнопки переходу на наступний або попередній трек. Додамо кнопку управління на мініатюру з нашого прикладу. Ця кнопка буде змінювати напис на нашій мініатюрі:
Cover {
id: cover
transparent: true
Label {
id: coverLabel
text: "Привіт, Хабр!"
anchors.centerIn: parent
}
CoverActionList {
CoverAction {
iconSource: "image://тема/icon-cover-next"
onTriggered: coverLabel.text = "Наступний!"
}
}
}

Детальніше про Cover можна прочитати в офіційній документації.

Орієнтація пристрою

Як і інші мобільні пристрої, пристрої на платформі Sailfish OS підтримують дві можливих орієнтації екрану: портретну і ландшафтну. Для того, щоб дізнатися поточну орієнтацію пристрою можна скористатися властивостями isPortrait isLandscape компонента Page, або властивістю orientation, яке приймає одне з наступних значень: Orientation.Portrait, Orientation.Landscape, Orientation.PortraitInverted або Orientation.LandscapeInverted. Так само, якщо важливо перевірити наприклад, що пристрій знаходиться в портретній орієнтації, а інвертоване воно чи ні — не важливо, то можна порівняти значення властивості orientation з маскою Orientation.PortraitMask. Для ландшафтного режиму існує аналогічна маска Orientation.LandscapeMask.

Якщо необхідно, щоб це додаток працювало лише в певних орієнтаціях, то можна скористатися властивістю allowedOrientations компонента ApplicationWindow, якому можна вказати, в яких орієнтаціях повинно працювати додаток. В якості значень можна вказувати ті ж значення, що повертає властивість orientation, а так само маски Orientation.LandscapeMask, Orientation.PortraitMask або Orientation.All. Значення за замовчуванням властивості allowedOrientations залежить від конкретного пристрою, тому якщо для програми важливо, що воно повинно працювати в певних орієнтаціях (або в будь-яких), то краще вказати це явно. Крім того, ця властивість можна вказати й у компонента Page, тоді правило дозволених орієнтацій буде застосовуватися лише до конкретної сторінці.

На цьому все. Проте, хотілося б відзначити, що не дивлячись на те, що в прикладах даної статті весь код був описаний в одному файлі, при написанні реальних програм найкраще описувати кожну сторінку і cover в окремому QML файлі. Це прискорить запуск програми, оскільки запобіжить компіляцію всіх QML компонентів при старті програми.

У наступній статті я розповім про інші компоненти, що входять до складу Sailfish Silica.

Автор статті: Денис Лаурэ
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.