Skip to main content
Перейти к содержимому

Компоновки, списки и карточки

Изучение различных способов размещения элементов на странице

Размещение содержимого

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

К концу этого раздела получится приложение с аккуратным внешним видом.

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: она содержит имя, описание и дату. Это не догма, и в модели могут быть данные других типов. Первые два значения — просто строки, а третье — число, используемое в качестве заполнителя.

Модели также полезны тем, что их можно изменять с помощью нескольких методов. Некоторые важные из них:

  • 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Пиксели
smallSpacing4px
largeSpacing8px
gridUnit18px

Напомним, что 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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.ApplicationWindow {
    id: root

    width: 400
    height: 300

    title: i18nc("@title:window", "Day Kountdown")

    // ListModel needed for ListView, contains elements to be displayed
    ListModel {
        id: kountdownModel
        // Each ListElement is an element on the list, containing information
        ListElement {
            name: "Dog birthday!!"
            description: "Big doggo birthday blowout."
            date: 100
        }
    }

    Component {
        id: kountdownDelegate
        Kirigami.AbstractCard {
            contentItem: Item {
                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 determines the size of the heading
                        level: 1
                        text: date
                    }

                    // Layout for positioning elements vertically
                    ColumnLayout {
                        Kirigami.Heading {
                            Layout.fillWidth: true
                            level: 2
                            text: name
                        }
                        // Horizontal rule
                        Kirigami.Separator {
                            Layout.fillWidth: true
                            visible: description.length > 0
                        }
                        // Labels contain text
                        Controls.Label {
                            Layout.fillWidth: true
                            // Word wrap makes text stay within box and shift with size
                            wrapMode: Text.WordWrap
                            text: description
                            visible: description.length > 0
                        }
                    }
                    Controls.Button {
                        Layout.alignment: Qt.AlignRight
                        // Column spanning within grid layout (vertically in this case)
                        Layout.columnSpan: 2
                        text: i18nc("@action:button","Edit")
                        //onClicked: to be done...
                    }
                }
            }
        }
    }

    pageStack.initialPage: Kirigami.ScrollablePage {
        title: i18nc("@title", "Kountdown")

        // List view for card elements
        Kirigami.CardsListView {
            id: cardsView
            // Model contains info to be displayed
            model: kountdownModel
            // Delegate is how the information will be presented in the ListView
            delegate: kountdownDelegate
        }
    }
}

Снимок внешнего вида приложения после завершения этого руководства

Вот и базовая карточка!

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