Компоновки, списки и карточки
Размещение содержимого
Теперь, когда работа страниц понятна, пора наполнить их содержимым. Далее будут рассмотрены несколько важных компонентов и элементов компоновки, которые пригодятся при разработке приложения.
К концу этого раздела получится приложение с аккуратным внешним видом.
ListViews
Если доводилось пользоваться Discover, NeoChat или «Параметрами системы» Plasma, то наверняка встречался ListView. Проще говоря, он позволяет выводить данные в виде списка.
pageStack.initialPage: Kirigami.ScrollablePage {
// …
Kirigami.CardsListView {
id: cardsView
model: kountdownModel
delegate: kountdownDelegate
}
}Звучит загадочно, но не стоит беспокоиться. Начнём с самого начала.
Этот компонент добавляется в Kirigami.ScrollablePage из предыдущего руководства.
Здесь используется Kirigami.CardsListView — это ListView, позволяющий легко показывать карточки в виде списка. Однако ListView созданы для показа данных, взятых из модели, — для автоматического заполнения из набора данных, на который они указывают. Именно для этого служит свойство model: в этом примере оно указывает на kountdownModel.
Модель
Kirigami.ApplicationWindow {
// …
ListModel {
id: kountdownModel
// Каждый ListElement — это элемент списка, содержащий информацию.
ListElement {
name: "Dog birthday!!"
description: "Big doggo birthday blowout."
date: 100
}
}
// …
}Наш kountdownModel добавляется в Kirigami.ApplicationWindow из предыдущего руководства.
Модель определяет способ структурирования записи данных. Наша kountdownModel пока будет состоять только из одного элемента. Взглянув на ListElement выше, можно увидеть, как структурированы данные kountdownModel: она содержит имя, описание и дату. Это не догма, и в модели могут быть данные других типов. Первые два значения — просто строки, а третье — число, используемое в качестве заполнителя.
Примечание
Поскольку QML построен на основе JavaScript, многие возможности этого языка доступны для использования в файлах QML. Однако перед переменными QML нужно указыватьproperty, если только они не находятся внутри блока кода JS. Подробнее об этом можно прочитать на этой странице.Модели также полезны тем, что их можно изменять с помощью нескольких методов. Некоторые важные из них:
- ListModel.append(yourobject: jsobject) добавляет объект JavaScript (JSObject)
yourobjectв ListModel и помещает его после последнего элемента модели. Чтобы это произошло правильно, необходимо указать объект JSObject с правильными свойствами и типами данных. - ListModel.get(index: int) возвращает JSObject по указанному индексу.
- ListModel.remove(index: int, count: int) удаляет JSObject в позиции
indexи далее столько объектов, сколько указано вcount(значение 1 означает удаление только JSObject в указанной позиции) - ListModel.set(index: int, yourobject: jsobject) изменяет элемент в позиции
indexзначениями, заданными вyourobject. Действуют те же правила, что и дляappend().
Делегаты
В то время как kountdownModel содержит данные, которые будут показаны, kountdownDelegate управляет тем, как эти данные будут показаны в ListView. Для этого используется Kirigami.CardsListView, предназначенный для показа делегатов карточного типа, а сами делегаты визуально представляются с помощью Kirigami.AbstractCard.
Делегаты автоматически получают свойства экземпляров ListElement, указанных в модели. Поэтому можно просто обращаться к их свойствам name, description и date, как к обычным переменным внутри делегата.
Создание карточки делегата
Component, который будет представлять делегат, можно добавить внутрь Kirigami.ApplicationWindow. Затем будет рассмотрено, что делает каждая часть компонента-делегата.
Kirigami.ApplicationWindow {
// …
Component {
id: kountdownDelegate
Kirigami.AbstractCard {
contentItem: Item {
// implicitWidth/Height задают естественную ширину/высоту элемента, если ширина или
// высота не указаны. Параметр ниже определяет предпочтительный размер компонента
// исходя из его содержимого
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
GridLayout {
id: delegateLayout
anchors {
left: parent.left
top: parent.top
right: parent.right
}
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
columns: root.wideScreen ? 4 : 2
Kirigami.Heading {
level: 1
text: date
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
level: 2
text: name
}
Kirigami.Separator {
Layout.fillWidth: true
visible: description.length > 0
}
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: description
visible: description.length > 0
}
}
Controls.Button {
Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
text: i18nc("@action:button","Edit")
// onClicked: будет сделано… скоро!
}
}
}
}
}
// …
}implicitWidth и implicitHeight
В первой части рассматривается управление шириной и высотой компонента:
Kirigami.AbstractCard {
contentItem: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
GridLayout {
id: delegateLayout
// …
}
}
}Рассматривая наш Kirigami.AbstractCard, можно заметить, что первыми задаются свойства implicitWidth и implicitHeight. Им присвоены значения delegateLayout.implicitWidth и delegateLayout.implicitHeight, то есть implicitWidth и implicitHeight элемента GridLayout.
Неявные ширина и высота — это свойства, доступные в любом Item, которые служат подсказками и задаются по умолчанию либо как запасной вариант, если для этих компонентов не заданы явные ширина и высота. По умолчанию эти значения равны 0x0, поэтому очень важно задавать их в обычных компонентах Item, как показано выше.
Здесь implicitWidth и implicitHeight нашей Kirigami.AbstractCard заданы равными соответствующим свойствам расположенного ниже GridLayout, чтобы содержимое не выходило за пределы карточки. Так карточка занимает ровно столько места, сколько необходимо для её содержимого.
Компоновки
GridLayout находится внутри компонента Item, предоставленного для свойства contentItem. Именно этот Item содержит то, что будет показано в карточке.
Также необходимо выбрать компоновку для компонентов, чтобы они не нагромождались друг на друга. Можно выбрать один из трёх основных типов:
- ColumnLayout размещает компоненты вертикально, в один столбец
- RowLayout размещает компоненты горизонтально в один ряд
- GridLayout размещает компоненты в сетке с выбранной компоновкой
С помощью ColumnLayout и RowLayout достаточно лишь поместить свои компоненты внутрь компонента Layout. Как видно, здесь используется GridLayout, что требует немного больше ручной работы.
GridLayout {
id: delegateLayout
anchors {
left: parent.left
top: parent.top
right: parent.right
}
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
columns: root.wideScreen ? 4 : 2
// …
}Первое, что видно, — это наши anchors. Система якорей QtQuick предоставляет удобный способ размещения компонентов в определённых частях родительского компонента. Наш GridLayout закреплён за левым, верхним и правым краями родительской карточки, благодаря чему содержимое растягивается на всю карточку.
Далее задаётся расстояние между строками и столбцами внутри сетки, чтобы компоненты не сбивались в кучу. Kirigami предоставляет ряд удобных предопределённых единиц, которые можно использовать для этой цели:
| Единица Kirigami | Пиксели |
|---|---|
| smallSpacing | 4px |
| largeSpacing | 8px |
| gridUnit | 18px |
Примечание
Группа визуального дизайна KDE (VDG) приводит гораздо больше сведений о различных единицах, определённых в Plasma и Kirigami, в Руководстве по проектированию пользовательского интерфейса.Напомним, что root — идентификатор нашего Kirigami.ApplicationWindow. Он предоставляет свойство wideScreen, используемое для определения того, является ли экран текущего устройства широкоэкранным (то есть монитором компьютера или телефоном в альбомной ориентации). Здесь используется тернарный условный оператор, чтобы изменять число столбцов в сетке в зависимости от используемого экрана: если экран широкоэкранный, сетка будет содержать 4 столбца, иначе — 2.
Внутренние компоненты
Можно было бы просто создать три надписи в компоненте-делегате и на этом остановиться, но это выглядело бы не очень красиво. Будут задействованы несколько более удобных компонентов:
GridLayout {
// …
Kirigami.Heading {
level: 1
text: date
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
level: 2
text: name
}
Kirigami.Separator {
Layout.fillWidth: true
visible: description.length > 0
}
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: description
visible: description.length > 0
}
}
Controls.Button {
Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
text: i18nc("@action:button","Edit")
}
}
Внешний вид пользовательской карточки
- Слева — Kirigami.Heading: использует
dateизListElementв качестве заголовка первого уровня. - В центре — ColumnLayout: содержит Kirigami.Heading, показывающий название задачи; Kirigami.Separator, обеспечивающий горизонтальную линию; и Controls.Label, показывающий необязательное описание задачи. Последние два компонента имеют свойство visible, которое проверяет, пусто ли описание, и показывает компоненты в зависимости от результата
description.length > 0. - Справа — Controls.Button: кнопка, которая что-то сделает… скоро!
Наше приложение на данный момент
Main.qml:
| |

Вот и базовая карточка!
Эти шаги заложили основу для добавления всей функциональности в приложение.