Основы использования Sailfish.Silica

Модуль Sailfish.Silica предоставляет основные компоненты для построения пользовательского интерфейса приложений ОС Аврора.

Сюда входят UI-компоненты, основанные на QtQuick, а также средства для стилизации приложений ОС Аврора и управления их поведением. Модуль Silica не только облегчает построение интерфейсов приложений, но и обеспечивает единство стиля оформления с другими приложениями ОС Аврора.

Создание приложения для ОС Аврора

В основе приложений для ОС Аврора лежит сочетание языков QML и C++: пользовательский интерфейс создается с помощью компонентов QML из модулей Silica и QtQuick (и других сторонних модулей), а запуск осуществляется приложением C++ путем вызова основного QML файла в классе QQuickView.

Приложения для ОС Аврора создаются с помощью Аврора SDK. Пример создания проекта приложения для ОС Аврора приведен в документации к SDK. В этом проекте автоматически создается QML-файл с пользовательским интерфейсом и приложение C++ для его запуска.

В QML-файле описывается компоновка пользовательского интерфейса приложения. Рассмотрим возможности модуля Sailfish.Silica путем последовательной доработки сгенерированного пользовательского интерфейса.

Интерфейс пользователя приложения ОС Аврора

Ниже приведен пример простого приложения ОС Аврора, которое отображает строку текста в центре экрана:

 import QtQuick 2.2
 import Sailfish.Silica 1.0

 ApplicationWindow {
     initialPage: Component {
         Page {
             Label {
                 text: "Hello world!"
                 anchors.centerIn: parent
             }
         }
     }
 }

Отметим три основные момента касаемо этого пользовательского интерфейса:

  • Сначала выполняется импорт модулей QtQuick и Sailfish.Silica для последующего использования соответствующих типов QML;
  • Для создания главного компонента используется тип ApplicationWindow из модуля Silica;
  • Для отображения содержимого в качестве начального экрана задается элемент Page (тип модуля Silica), в центре которого расположена текстовая метка Label (тип модуля Silica).

С помощью типа ApplicationWindow реализуется компонент верхнего уровня приложения ОС Аврора. По сути, это окно, заключающее в себе пользовательский интерфейс приложения. Каждое окно содержит стек страниц, который отображает текущий экран или, другими словами, страницуприложения. С помощью свойства initialPage в контейнере ApplicationWindow задается первоначальная страница приложения. В примере выше при запуске приложения будет показана одиночная страница Page текстовой меткой Label в центре экрана. В качестве значения свойства initialPage можно задать URL файла QML, экземпляр страницы Page или компонент Component с верхнеуровневой страницей Page (как в примере выше).

Теперь, имея простое приложение, мы будем последовательно дорабатывать пользовательский интерфейс с помощью некоторых QML-типов модуля Silica.

Стилизация интерфейса пользователя приложения ОС Аврора

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

 import QtQuick 2.2
 import Sailfish.Silica 1.0

 ApplicationWindow {
     initialPage: Component {
         Page {
             Column {
                 width: parent.width

                 // Интерактивный ползунок с шагом в диапазоне значений от 0 до 100
                 Slider {
                     id: slider
                     label: "Простой ползунок"
                     width: parent.width
                     minimumValue: 0; maximumValue: 100; stepSize: 1
                     valueText: value
                 }

                 Button {
                     text: "Сохранить"
                     anchors.horizontalCenter: parent.horizontalCenter
                     onClicked: {
                         listModel.append({"sliderValue": "Значение: " + slider.value})
                     }
                 }

                 Repeater {
                     model: ListModel { id: listModel }

                     Label { text: model.sliderValue }
                 }
             }
         }
     }
 }

Так же как и в предыдущем примере, приложение и его первоначальная страница с содержимым создаются с помощью QML-типов ApplicationWindow и Page. Коме того, в приложении используются:

  • Тип Slider (из модуля Silica) для отображения горизонтального ползунка;
  • Тип Button (из модуля Silica) для отображения интерактивной кнопки;
  • Тип Label (из модуля Silica) для отображения текста;
  • Типы Column, ListModel и Repeater (из стандартного модуля QtQuick) для компоновки элементов на экране и наполнения списка значениями, заданными с помощью ползунка. Тип Repeater создает экземпляр своего дочернего элемента (в данном примере — текстовой метки Label) для каждого элемента своей модели.

Наше приложение для ОС Аврора полностью работоспособно, однако в нем отсутствуют некоторые возможности, которые сделают его более презентабельным и удобным для использования. Например:

  • Значения ползунка можно было бы удалять из списка;
  • Элементы списка расположены непосредственно у левой границы экрана без отступа;
  • Страница не содержит заголовка;
  • Страницу нельзя прокручивать после наполнения списка достаточно большим количеством записей.

Все это можно исправить с помощью других типов модуля Silica, а также с помощью объекта Theme.

Добавление элемента ListItem в список

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

В примере ниже, использование типа ListItem превращает каждый элемент списка в интерактивный элемент с набором соответствующих действий. Достигается это путем замены дочернего элемента типа Repeater на ListItem и переносом туда текстовой метки Label. Затем с помощью свойства menu в элементе ListItem создается контекстное меню ContextMenu, состоящее из одной команды, описанной в контейнере MenuItem. Вызов этой команды удаляет соответствующий элемент из модели:

 Repeater {
     model: ListModel { id: listModel }

     ListItem {
         width: parent.width

         Label {
             text: model.sliderValue
             anchors.verticalCenter: parent.verticalCenter
         }

         menu: ContextMenu {
             MenuItem {
                 text: "Удалить"
                 onClicked: {
                     listModel.remove(model.index)
                 }
             }
         }
     }
 }

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

Согласитесь, было бы гораздо удобнее, если бы при удалении элемента из списка пользователь подтверждал это действие и, при необходимости, мог бы отменить его. В ОС Аврора для этого существует специальная концепция отмены разрушающего действия, которая реализуется с помощью типов RemorseItem и RemorsePopup: вместо незамедлительного выполнения действия пользователю предоставляется возможность в течение некоторого времени отменить данное действие.

Для отмены разрушающего действия в объектах ListItem нет необходимости использовать типы RemorseItem или RemorsePopup напрямую. Вместо этого тип ListItem предоставляет соответствующий метод remorseAction(), который сначала покажет таймер обратного отсчета, а затем по истечении времени выполнит заданное действие. Изменим обработчик onClicked контекстного меню так, чтобы в нем вызывался метод remorseAction(). В данный метод, в свою очередь, передается функция, вызывающая метод ListModel::remove(), когда таймер обратного отсчета достигнет нуля:

 Repeater {
     model: ListModel { id: listModel }

     ListItem {
         id: listEntry    // Добавим идентификатор для обращения к элементу в обработчике onClicked()
         width: parent.width

         Label {
             text: model.sliderValue
             anchors.verticalCenter: parent.verticalCenter
         }

         menu: ContextMenu {
             MenuItem {
                 text: "Удалить"
                 onClicked: {
                     listEntry.remorseAction("Удаление", function() { listModel.remove(model.index) })
                 }
             }
         }
     }
 }

Остались небольшие проблемы с текстовой меткой: в соответствии с соглашением, принятым для приложений ОС Аврора, все интерактивные элементы (т.е. элементы, реагирующие на касания, такие как кнопки или элементы списка, как в нашем примере) должны менять соответствующим образом свой цвет при нажатии. Кроме того, необходимо добавить интервал между элементами списка, отступ от края страницы, а также отступы сверху и низу кнопки "Сохранить".

Обе эти проблемы можно решить стандартным для приложений ОС Аврора способом с помощью объекта Theme.

Стилизация интерфейса пользователя с помощью объекта Theme

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

Например, свойство Theme.horizontalPageMargin определяет стандартное расстояние между краями страницы и ее содержимым. В нашем примере мы можем использовать его для задания отступа элементам списка. Кроме того, свойства Theme.primaryColor и Theme.highlightColor определяют цвета элементов в обычном состоянии и при их подсветке в соответствии с текущей атмосферой ОС Аврора. Поэтому, с помощью свойства highlighted элемента списка (возвращает true, когда элемент списка подсвечивается при нажатии) можно задать условие для раскрашивания текстовой метки — свойство color будет принимать соответствующий цвет:

 ListItem {
     id: listEntry
     width: parent.width

     Label {
         color: listEntry.highlighted ? Theme.highlightColor : Theme.primaryColor    // Раскрасим текст соответствующим образом
         text: model.sliderValue
         x: Theme.horizontalPageMargin
         anchors.verticalCenter: parent.verticalCenter
     }

     menu: ContextMenu {
         MenuItem {
             text: "Удалить"
             onClicked: {
                 listEntry.remorseAction("Удаление", function() { listModel.remove(model.index) })
             }
         }
     }
 }

Для добавления отступа между ползунком, кнопкой и списком, установите параметр spacing столбца в значение Theme.paddingLarge — стандартное расстояние между элементами на странице. Поскольку этот отступ не должен применяться к элементам списка, разместим элемент Repeater и его дочерние элементы в новом столбце Column, в котором по умолчанию используется нулевой отступ:

 Page {
     // Основной столбец
     Column {
         width: parent.width
         spacing: Theme.paddingLarge // Добавим интервал между элементами в столбце

         // Slider { ... }
         // Button { ... }

         // Вложенный столбец, содержащий элемент Repeater и элементы списка
         Column {
             width: parent.width

             // Repeater { ... }
         }
     }
 }

Дальнейшие улучшения

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

С помощью типа PageHeader добавим заголовок над основным столбцом, а также включим прокрутку для списка путем помещения столбца Column в контейнер SilicaFlickable. В дополнение к этому добавим вертикальный индикатор прокрутки VerticalScrollDecorator как дочерний элемент контейнера SilicaFlickable. Этот индикатор представляет собой вертикальную полоску, которая отображается справа при прокручивании содержимого и обозначает его текущее положение и общую площадь.

 Page {
     PageHeader {
         id: header
         title: "Пример Sailfish"
     }

     SilicaFlickable {
         anchors {
             top: header.bottom
             bottom: parent.bottom
             left: parent.left
             right: parent.right
         }
         contentHeight: mainColumn.height

         // main column
         Column {
             id: mainColumn
             // ... rest of main column contents
         }

         VerticalScrollDecorator {}
     }
 }

Также было бы полезно иметь возможность полной очистки списка. Сделать это можно с помощью вытягиваемых меню: такие меню по умолчанию скрыты и показываются при прокручивании родительского элемента. Добавим элемент PullDownMenu (вытягиваемое меню, отображаемое в верхней части страницы) в контейнер SilicaFlickable:

 SilicaFlickable {
     PullDownMenu {
         MenuItem {
             text: "Очистить"
             onClicked: listModel.clear()
         }
     }
     // ... Содержимое элемента с прокруткой
 }

Было бы удобнее, если бы список очищался не сразу, а с кратковременной возможностью отмены действия, как это мы сделали ранее с помощью метода remorseAction(). В данном случае, такую функциональность можно реализовать с помощью объекта RemorsePopup путем вызова его метода execute(). Попробуйте выполнить это самостоятельно.

Замена списка в столбце на SilicaListView

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

В последнем случае будет предпочтительнее использовать тип SilicaListView (основан на типе ListView из модуля QtQuick), который создает и отрисовывает делегаты только в видимой области экрана (а также делегаты за пределами экрана в количестве, указанном в параметре cacheBuffer). Для этого заменим тип SilicaFlickable на SilicaListView, а также переместим свойства model и delegate из контейнера Repeater в контейнер вида:

 Page {
     SilicaListView {
         anchors.fill: parent

         model: ListModel { id: listModel }

         delegate: ListItem {
             // ... Содержимое элемента
         }

         // Вложенный столбец, содержащий элемент Repeater и элементы списка, больше не требуется
         // и может быть удален

         VerticalScrollDecorator {}

         PullDownMenu {
             MenuItem {
                 text: "Очистить"
                 onClicked: listModel.clear()
             }
         }
     }
 }

Для большего удобства заголовок страницы PageHeader и основной столбец Column (с ползунком Slider и кнопкой Button) можно вынести в область заголовка вида с прокруткой (унаследованное свойство header), чтобы эти элементы прокручивались вместе с отрисованными делегатами. Сделать это можно путем группировки этих элементов в новый контейнер типа Column:

 SilicaListView {
     header: Column {
         width: parent.width
         height: header.height + mainColumn.height + Theme.paddingLarge    // Добавим интервал между кнопкой и первым элементом списка

         PageHeader {
             id: header
             title: "Пример Sailfish"
         }

         // Основной столбец
         Column {
             id: mainColumn
             // ... Содержимое основного столбца
         }
     }

     // ... rest of SilicaListView code
 }

Готовый результат

Подведем итоги. Для создания простого пользовательского интерфейса приложения ОС Аврора с помощью модуля Silica мы выполнили следующие этапы:

  • Создание корневого компонента ApplicationWindow назначение ему первоначальной страницы initialPage;
  • Добавление компонентов Label, Button, Slider и ListItem из модуля Silica для начала отображения данных и получения ввода пользователя;
  • Использование объекта Theme для стилизации пользовательского интерфейса и применения стандартных цветов, размеров и отступов;
  • Доработка пользовательского интерфейса путем добавления вытягиваемых меню (PullDownMenu и PushUpMenu) и отсроченных разрушающих действий (либо с помощью встроенных методов типа ListItem, либо с помощью отдельных специализированных объектов RemorseItem и RemorsePopup).

Ниже приведен пример завершенного кода:

 import QtQuick 2.2
 import Sailfish.Silica 1.0

 ApplicationWindow {
     initialPage: Component {
         Page {
             SilicaListView {
                 anchors.fill: parent

                 header: Column {
                     width: parent.width
                     height: header.height + mainColumn.height + Theme.paddingLarge

                     PageHeader {
                         id: header
                         title: "Пример Sailfish"
                     }

                     Column {
                         id: mainColumn
                         width: parent.width
                         spacing: Theme.paddingLarge

                         Slider {
                             id: slider
                             label: "Простой ползунок"
                             width: parent.width
                             minimumValue: 0; maximumValue: 100; stepSize: 1
                             valueText: value
                         }

                         Button {
                             text: "Сохранить"
                             anchors.horizontalCenter: parent.horizontalCenter
                             onClicked: {
                                 listModel.append({"sliderValue": "Значение: " + slider.value})
                             }
                         }
                     }
                 }

                 model: ListModel { id: listModel }

                 delegate: ListItem {
                     id: listEntry
                     width: parent.width

                     Label {
                         color: listEntry.highlighted ? Theme.highlightColor : Theme.primaryColor
                         text: model.sliderValue
                         x: Theme.horizontalPageMargin
                         anchors.verticalCenter: parent.verticalCenter
                     }

                     menu: ContextMenu {
                         MenuItem {
                             text: "Удалить"
                             onClicked: {
                                 listEntry.remorseAction("Удаление", function() {
                                     listModel.remove(model.index)
                                 })
                             }
                         }
                     }
                 }

                 PullDownMenu {
                     MenuItem {
                         text: "Очистить"
                         onClicked: listModel.clear()
                     }
                 }

                 VerticalScrollDecorator {}
             }
         }
     }
 }

Другие полезные типы Silica

В дополнение к уже упомянутым типам Silica приведем еще несколько полезных типов:

  • TextField и TextArea — элементы управления для ввода текста;
  • TextSwitch, ProgressBar и ComboBox — типовые элементы управления;
  • SilicaGridView — представление в виде сетки, основанное на типе QtQuick GridView;
  • SectionHeader и Separator — визуальное разделение компонентов на экране;
  • Dialog - a specialized page with "Accept" and "Cancel" header buttons for user confirmation and cancellation actions

Полный список типов приведен в разделе Справочная документация по Sailfish.Silica.