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

в 12:56, , рубрики: qt, sailfish os, мобильная разработка, разработка мобильных приложений

Здравствуйте! На прошлой неделе я написал о том как начать разрабатывать под мобильную платформу 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
        }
    }
}

Даже показ диалога осуществляется добавлением его на стек страниц. Если запустить приложение и нажать на кнопку, то мы увидим следующее:
Разработка для SailfishOS: основы - 1
Как видите, внешне данный минимальный диалог ничем не отличается от обычной страницы. Однако, его поведение отличается: при свайпе влево или вправо (или нажатии на белые области в верхнем левом или нижнем углу) диалог закроется и будет показана начальная страница приложения. При этом в зависимости от направления свайпа вызовется соответствующий сигнал диалога (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 = "Отказался"
}

Теперь он выглядит так:
Разработка для SailfishOS: основы - 2

Следует отметить, что при запуске примера выше вы можете увидеть следующие предупреждения:

[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, который выглядит следующим образом:
Разработка для SailfishOS: основы - 3
Установить свой 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://theme/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.

Автор статьи: Денис Лаурэ

Автор: FRUCT

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js