QML

States и Transitions: декларативное управление состояниями интерфейса

Введение

Привязки (статья 481) синхронизируют значения свойств непрерывно — для дискретных, именованных состояний интерфейса (свёрнутая/развёрнутая панель, выбранный/невыбранный элемент, режим редактирования) QML предоставляет специальный механизм State/Transition, декларативно описывающий набор возможных состояний и переходы между ними.

Концепция

State определяет именованное состояние как набор изменений свойств (PropertyChanges) относительно базового, «обычного» состояния элемента (состояние с пустым именем "", представляющее исходные значения, заданные непосредственно в самом элементе) — переключение между состояниями осуществляется присваиванием свойству state имени нужного состояния. Transition определяет, как именно должен происходить переход между двумя конкретными состояниями (через анимацию изменяющихся свойств, статья 487) — без явно объявленного Transition переключение состояния происходит мгновенно, без какой-либо плавности.

Пример кода: States и Transitions

import QtQuick

Rectangle {
    id: panel
    width: 200; height: 100
    color: "lightblue"

    states: [
        State {
            name: "expanded"
            PropertyChanges { panel.height: 300; panel.color: "steelblue" }
        },
        State {
            name: "collapsed"
            PropertyChanges { panel.height: 30 }
        }
        // Состояние с пустым именем (по умолчанию) НЕ объявляется явно —
        // оно соответствует исходным значениям, заданным прямо в Rectangle (height: 100, color: "lightblue")
    ]

    transitions: [
        Transition {
            from: "*"; to: "*" // применяется к ЛЮБОМУ переходу между любыми состояниями
            NumberAnimation { property: "height"; duration: 300; easing.type: Easing.InOutQuad }
            ColorAnimation { property: "color"; duration: 300 }
        }
    ]

    MouseArea {
        anchors.fill: parent
        onClicked: panel.state = panel.state === "expanded" ? "collapsed" : "expanded"
    }
}
// Состояние, зависящее от условия через привязку, а не только от явного присваивания
Rectangle {
    id: statusIndicator
    property bool isOnline: false

    width: 100; height: 30

    state: isOnline ? "online" : "offline" // STATE САМ может быть результатом привязки!

    states: [
        State { name: "online"; PropertyChanges { statusIndicator.color: "green" } },
        State { name: "offline"; PropertyChanges { statusIndicator.color: "gray" } }
    ]
}

Пояснения к коду

Объявление states как массива State-объектов с именами "expanded"/"collapsed" показывает структуру механизма — каждое состояние явно перечисляет, какие свойства должны измениться (через PropertyChanges) относительно исходных, базовых значений, заданных непосредственно в самом Rectangle. transitions с from: "*"; to: "*" показывает универсальный переход, применяемый к любой паре состояний — для более тонкого контроля можно объявить отдельные Transition для конкретных пар from/to, если разным переходам нужна разная анимация. Пример statusIndicator показывает, что само свойство state может быть результатом привязки (isOnline ? "online" : "offline"), а не только явного, императивного присваивания — это даёт мощную комбинацию декларативного управления состояниями с декларативной логикой определения, какое состояние должно быть активным в зависимости от других данных приложения.

Подводные камни

  • Дублирование одних и тех же значений свойств между базовым (неявным) состоянием и явно объявленными State, что усложняет понимание того, какое именно значение свойства актуально в каждый конкретный момент — стоит явно проектировать PropertyChanges каждого состояния как изменения именно ОТНОСИТЕЛЬНО базовых значений, заданных в самом элементе, а не пытаться продублировать полный набор значений в каждом состоянии независимо.
  • Использование сложной, многошаговой императивной логики переключения состояний вместо простого, прямого присваивания state или привязки к условию — механизм состояний QML рассчитан на простое, прямое указание имени целевого состояния, и обходные, излишне сложные способы переключения (через множественные промежуточные шаги или вспомогательные свойства) часто говорят о неоптимальном использовании самого механизма состояний для конкретной задачи.
  • Забытое объявление Transition для конкретной пары состояний, что приводит к «резкому», мгновенному переключению значений свойств без какой-либо анимации именно для этой конкретной пары, тогда как для других пар переходов анимация работает (если для них есть подходящий, более специфичный Transition) — несогласованность в анимированности разных переходов одного и того же элемента может выглядеть как непродуманная, непоследовательная деталь интерфейса для конечного пользователя.
  • Игнорирование того, что PropertyChanges без явного restoreEntryValues: false (значение по умолчанию — true) автоматически восстанавливает исходное значение свойства при выходе из состояния — это поведение по умолчанию обычно ожидаемо и удобно (значения корректно «откатываются» при возврате к базовому состоянию), но для случаев, где это restoring поведение нежелательно (например, значение должно «запомниться» даже после выхода из состояния), нужно явно отключить его через restoreEntryValues: false, что легко упустить, ожидая иного, неожидаемого поведения по умолчанию.