Введение
Qt Quick приложения часто должны работать на экранах самых разных размеров и соотношений сторон — от мобильных телефонов до настольных мониторов. QML предоставляет два взаимодополняющих механизма для построения адаптивного интерфейса: anchors (якоря, привязывающие края элементов друг к другу) и Layouts (Row/Column/GridLayout, автоматически распределяющие пространство между дочерними элементами). В статье разберём, когда использовать каждый из них и как комбинировать для действительно адаптивной верстки.
Концепция
Anchors — низкоуровневый, но очень гибкий механизм: каждый элемент может привязать любой из своих краёв (left, right, top, bottom, centerIn и так далее) к краю родителя или другого элемента, с возможностью отступов (margins). Это хорошо подходит для фиксированной композиции элементов друг относительно друга, но не предназначено для динамического распределения элементов переменного числа.
Layouts (RowLayout, ColumnLayout, GridLayout) автоматически располагают дочерние элементы в ряд, столбец или сетку, с поддержкой растягивающихся пропорций через Layout.fillWidth/Layout.fillHeight и Layout.preferredWidth. Это естественный выбор, когда число элементов может меняться, или когда нужно равномерное распределение доступного пространства, аналогично flexbox в CSS.
Пример кода: Адаптивная верстка в QML
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: window
visible: true
width: 480
height: 320
minimumWidth: 320
// Корневой layout — адаптивно перестраивается под ширину окна
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
// Заголовок, привязанный через anchors внутри собственного Item
Item {
Layout.fillWidth: true
height: 40
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "Панель управления"
font.pixelSize: 20
}
Button {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "Настройки"
}
}
// Адаптивная сетка карточек: число столбцов зависит от ширины
GridLayout {
Layout.fillWidth: true
Layout.fillHeight: true
columns: window.width > 600 ? 3 : (window.width > 400 ? 2 : 1)
rowSpacing: 10
columnSpacing: 10
Repeater {
model: 6
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 80
color: "#e0e0e0"
radius: 8
Text {
anchors.centerIn: parent
text: "Карточка " + (index + 1)
}
}
}
}
// Панель кнопок внизу — равномерно растягивается по горизонтали
RowLayout {
Layout.fillWidth: true
spacing: 8
Button { text: "Отмена"; Layout.fillWidth: true }
Button { text: "Применить"; Layout.fillWidth: true }
}
}
}
Пояснения к коду
Корневой ColumnLayout растягивается на всё окно через anchors.fill: parent, а его дочерние элементы (заголовок, сетка карточек, панель кнопок) располагаются друг под другом в столбик автоматически, без необходимости вручную вычислять координаты Y каждого следующего элемента. Внутри блока заголовка использован anchors, а не Layout — поскольку там всего два элемента с простым относительным расположением (текст слева, кнопка справа), anchors даёт более прямой и компактный код, чем RowLayout с растягивающимся спейсером.
GridLayout.columns вычисляется динамически на основе текущей ширины окна (window.width) — это простой, но эффективный способ адаптивной перестройки сетки карточек: при сужении окна число столбцов уменьшается, и карточки автоматически перераспределяются по новой сетке без какого-либо ручного кода перестроения.
Подводные камни
- Смешивание
anchorsи явныхx/y/width/heightдля одного и того же элемента приводит к неоднозначному, иногда циклическому определению геометрии — Qt выводит предупреждение в рантайме о «binding loop», и поведение становится труднопредсказуемым; для одного элемента стоит выбрать либо anchors, либо явные геометрические свойства, либо участие в Layout, но не смешивать подходы одновременно. anchors.fill: parentвнутри элемента, который сам находится внутри Layout, может конфликтовать с тем, как Layout пытается управлять размерами этого элемента — Layout-менеджеры ожидают, что размеры дочерних элементов определяются черезLayout.*свойства, а не через independent anchors к родителю, отличному от самого Layout.- Захардкоженные пиксельные пороги адаптивности (
window.width > 600) работают для конкретных протестированных размеров, но не масштабируются автоматически под очень большие 4K-мониторы или очень маленькие встраиваемые экраны без дополнительной настройки — для по-настоящему универсальной адаптивности иногда используют относительные единицы или явные breakpoints, синхронизированные с дизайн-системой проекта. - Игнорирование
Layout.minimumWidth/minimumHeightдля контента с фиксированным минимальным размером (например, текст, который не может быть короче своего содержимого) может привести к обрезанию контента при сильном сжатии окна — явное указание минимальных размеров предотвращает это, ценой того, что Layout не сможет сжать элемент ниже этого порога.