Введение
Реальные приложения редко состоят из единственного экрана — в этой статье разберём навигационные паттерны Qt Quick: StackView для классической навигации «вперёд-назад» (как стек экранов мобильного приложения), SwipeView для горизонтальной навигации свайпом между страницами, и Drawer для выдвижной боковой панели навигации, привычной по многим современным мобильным и десктопным приложениям.
Концепция
StackView поддерживает стек страниц — push() добавляет новую страницу наверх стека (становящуюся видимой, с анимированным переходом, статья 487), а pop() возвращает к предыдущей странице, удаляя текущую — это естественно моделирует типичную навигацию «перейти глубже / вернуться назад», знакомую по большинству мобильных приложений. SwipeView располагает несколько страниц горизонтально и позволяет пользователю переключаться между ними свайпом (или программно через currentIndex), что отличается от StackView отсутствием понятия «глубины» — все страницы SwipeView равноправны и находятся на одном уровне. Drawer — панель, обычно скрытая за пределами видимой области экрана и выезжающая при определённом действии пользователя (свайп от края экрана, нажатие кнопки меню), типично используемая для основной навигации приложения по разным, не связанным напрямую разделам.
Пример кода: Многостраничная навигация в QML
// StackView — классическая навигация с push/pop
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
width: 400; height: 600
StackView {
id: stackView
anchors.fill: parent
initialItem: mainPageComponent
}
Component {
id: mainPageComponent
Page {
title: "Главная"
Button {
anchors.centerIn: parent
text: "Открыть детали"
onClicked: stackView.push(detailsPageComponent, {itemId: 42}) // передача параметра
}
}
}
Component {
id: detailsPageComponent
Page {
property int itemId: -1
title: "Детали элемента " + itemId
Button {
anchors.centerIn: parent
text: "Назад"
onClicked: stackView.pop() // возврат к предыдущей странице стека
}
}
}
}
// SwipeView с индикатором текущей страницы (PageIndicator)
import QtQuick
import QtQuick.Controls
Column {
anchors.fill: parent
SwipeView {
id: swipeView
width: parent.width
height: parent.height - 30
Rectangle { color: "lightcoral"; Text { anchors.centerIn: parent; text: "Страница 1" } }
Rectangle { color: "lightgreen"; Text { anchors.centerIn: parent; text: "Страница 2" } }
Rectangle { color: "lightblue"; Text { anchors.centerIn: parent; text: "Страница 3" } }
}
PageIndicator {
anchors.horizontalCenter: parent.horizontalCenter
count: swipeView.count
currentIndex: swipeView.currentIndex // привязка — индикатор автоматически отражает текущую страницу
}
}
// Drawer — выдвижная боковая панель навигации
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
width: 400; height: 600
Drawer {
id: drawer
width: 250; height: parent.height
Column {
ItemDelegate { text: "Главная"; onClicked: drawer.close() }
ItemDelegate { text: "Настройки"; onClicked: drawer.close() }
ItemDelegate { text: "О приложении"; onClicked: drawer.close() }
}
}
ToolButton {
text: "☰"
onClicked: drawer.open()
}
}
Пояснения к коду
stackView.push(detailsPageComponent, {itemId: 42}) показывает передачу начальных значений свойств в новую страницу непосредственно при добавлении её в стек — detailsPageComponent получает itemId: 42 сразу при создании, что избегает необходимости отдельного, последующего вызова для передачи этого параметра. PageIndicator с привязкой currentIndex: swipeView.currentIndex показывает типичный паттерн синхронизации вспомогательного индикатора с реальным состоянием SwipeView через привязку (статья 481) — индикатор автоматически отражает, на какой странице сейчас находится пользователь, без какого-либо явного, дополнительного кода синхронизации. Drawer с drawer.close() внутри обработчиков ItemDelegate показывает типичный паттерн UX — выбор пункта меню сразу закрывает панель навигации, что ожидаемо для большинства пользователей, привыкших к такому поведению выдвижных навигационных панелей.
Подводные камни
- Накопление избыточно глубокого стека
StackViewбез предусмотренного способа быстро вернуться к началу навигации — для приложений с потенциально глубокой навигацией (несколько последовательныхpush()подряд) стоит предусмотреть либо методpopToRoot()(или его эквивалент черезpop(null), удаляющий весь стек до самой первой страницы), либо явную альтернативную навигацию, не вынуждающую пользователя многократно нажимать «назад» для возврата к началу. - Создание дорогих в инициализации страниц
StackViewзаново при каждомpush()без рассмотрения переиспользования уже созданных, но временно скрытых экземпляров — для страниц с дорогой инициализацией (загрузка данных, сложная разметка) стоит рассмотреть, действительно ли нужно создавать новый экземпляр при каждом переходе, или предпочтительнее переиспользовать существующий, временно скрытый экземпляр там, где логика приложения это допускает. - Игнорирование жестов платформы при использовании
SwipeViewна десктопе, где привычный для мобильных платформ свайп не имеет прямого эквивалента взаимодействия мышью — для десктопных приложений может понадобиться дополнительная, альтернативная навигация (кнопки «вперёд»/»назад», клавиатурные сочетания), не предполагающая обязательного использования жеста свайпа, неестественного для управления мышью. - Открытие
Drawerбез учёта модальности взаимодействия с остальным интерфейсом — по умолчаниюDrawerможет как блокировать взаимодействие с остальной частью приложения, пока он открыт (модальный режим), так и оставлять его доступным (немодальный режим), и выбор подходящего поведения (modalсвойство) должен соответствовать продуманному, осознанному UX-решению, а не оставаться на поведении по умолчанию без явной проверки его соответствия ожиданиям конкретного приложения.