====== Qt Quick: Изображения, Ввод, Анимация ====== ===== Элементы графики ===== ==== Растровые изображения ==== Можно использовать форматы: **JPG**, **PNG** и **SVG**.\\   === Элемент Image === Этот элемент отображает файл изображения, указанный в свойстве **source**, файл может находится как локально так и в сети.\\ Есть интерактивный редактор для изменения свойств.\\ Свойствами можно подвергать изображение трансформациям, типа увеличение/уменьшение (**scale**), поворот (**rotation**), сглаживание (**smooth**).\\
:!: Пример: Изображение с уменьшением, поворотом и сглаживанием import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: img.width; height: img.height; visible: true; Rectangle { anchors.fill: parent Image { id: img source: "qrc:/../../../../Downloads/11.jpg" x: 0; y: 0 smooth: true scale: 0.75 rotation: -30 } } }
Для более тонкой настройки трансформации, ее можно задавать списками (в квадратных скобках).\\
:!: Пример: Аналогично, только трансформацию задаем двумя списками свойству **transform** import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: img.width; height: img.height; visible: true; Rectangle { anchors.fill: parent Image { id: img source: "qrc:/../../../../Downloads/11.jpg" x: 0; y: 0 smooth: true transform: [ Scale { origin.x: width / 2 origin.y: width / 2 xScale: 0.75 yScale: 0.75 }, Rotation { origin.x: width / 2 origin.y: width / 2 angle: -30.0 } ] } } }
Добавим элемент загрузки **BusyIndicator**
:!: Пример: Загрузка и отображение изображения из web import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: 640; height: 480; visible: true; Rectangle { anchors.fill: parent Image { id: img source: "http://qt-book.com/pic.jpg" anchors.fill: parent smooth: true Column { anchors.centerIn: parent visible: img.status == Image.Loading ? true:false Text { text: "Загрузка..." } BusyIndicator {} } } } }
  === BorderImage === Элемент для нормального масштабирования графики, особенно полезно при закругленных углах (кнопок и т.д.). Элемент разбивает изображение на девять частей.\\
:!: Пример: Масштабируемое изображение кнопки, с закругленными углами import QtQuick 2.12 BorderImage { source: "" width: 100; height: 45 border {left: 30; top: 15; right: 30; bottom: 15} }
  ==== Градиент ==== ---- В QML есть только один градиент - линейный, если нужен другой, можно воспользоваться трансформацией.\\ Для создания, нужно свойству **gradient** присвоить элемент **Gradient**, он содержит точки останова (**GradientStop**) с позициями от 0 до 1.\\ Создание градиентов может потребовать много ресурсов, поэтому эффективнее использовать изображение градиента.\\
:!: Пример: Градиент с тремя точками останова, трансформирован в диагональ import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: 640; height: 480; visible: true; Rectangle { anchors.fill: parent gradient: Gradient { GradientStop{position: 0.0; color: "blue"} GradientStop{position: 0.7; color: "gold"} GradientStop{position: 1.0; color: "silver"} } rotation: 30 scale: 1.5 } }
  ==== Рисование Canvas ==== ---- **Canvas** представляет собой элемент холста, на котором можно выполнять растровые операции, по сути аналогичен **QPaintDevice**.\\ Сам QML описательный язык, поэтому алгоритмы рисования реализуются с использованием **JavaScript**, в свойстве обработки **onPaint**.\\
:!: Пример: Прикольный узор, нарисованный на холсте import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: 480; height: 480; visible: true; Rectangle { anchors.fill: parent Canvas { anchors.fill: parent; onPaint: { function drawFantasy() { ctx.beginPath(); ctx.translate(parent.width / 2, parent.height / 2); var fAngle = 91 * 3.14156 / 180 for(var i = 0; i < 300; ++i) { var n = i * 2; ctx.moveTo(0, 0); ctx.lineTo(n, 0); ctx.translate(n, 0); ctx.rotate(fAngle) } ctx.closePath(); } var ctx = getContext("2d"); ctx.clearRect(0, 0, parent.width, parent.height); ctx.save(); ctx.strokeStyle = "black"; ctx.lineWidth = 1; drawFantasy(); ctx.stroke(); ctx.restore(); } } } }
:!: Пример: Градиент на холсте import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: 480; height: 480; visible: true; Rectangle { anchors.fill: parent Canvas { id: canv width: 320; height: 320; onPaint: { var ctx = getContext("2d"); ctx.strokeStyle= "blue"; ctx.lineWidth = 15; var gradient = ctx.createLinearGradient(canv.width, canv.height, 0, 0); gradient.addColorStop(0, "Indigo"); gradient.addColorStop(0.5, "Bisque"); gradient.addColorStop(1, "ForestGreen"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canv.width, canv.height); ctx.strokeRect(0, 0, canv.width, canv.height); } } } }
  ===== Пользовательский ввод ===== ==== Область мыши ==== ---- Для получения событий мыши служат специальные элементы - **MouseArea**, по сути просто прямоугольные области, в которых осуществляется ввод информации от мыши.\\
:!: Пример: Нажатие левой и правой кнопок мыши import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.12 ApplicationWindow { width: 300; height: 300; visible: true; Rectangle { anchors.fill: parent color: "lightgreen" Text { id: txt anchors.centerIn: parent text: "

This is text
Click here

" horizontalAlignment: Text.AlignHCenter } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: { if(mouse.button == Qt.LeftButton) parent.color= "red" else parent.color= "blue" } onReleased: parent.color= "lightgreen" } } }
Так же, можно использовать свойство **containsMouse**, для обнаружения находится ли курсор над областью.\\ Аналогично можно использовать свойства **onEntered** и **onExit**.\\
:!: Пример: Обнаружение находится ли курсор над областью import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 300; height: 300; visible: true; Rectangle { anchors.fill: parent color: mousearea.containsMouse ? "red" : "lightgreen" Text { anchors.centerIn: parent text: "

Hover me

" } MouseArea { id: mousearea anchors.fill: parent hoverEnabled: true } } }
  ==== Сигналы ==== ---- Сигналы в QML это события, которые уже прикреплены к свойствам, с кодом исполнения в них, эти свойства называются с префиксом **on** и являются по сути слотами.\\ Можно определять собственные сигналы:\\ **signal [( , ...)]**\\ К каждому такому сигналу автоматически создастся соответствующий обработчик (слот), с префиксом **on**.\\ Насколько я понимаю, вызвать наш сигнал может только какое то действие т.е. стандартный сигнал (точнее его обработчик **on..**), поэтому любой собственный сигнал будет так или иначе связан с уже существующим, стандартным сигналом.., плюс есть как минимум в том, что наши собственные сигналы будут доступны на уровнях выше т.е. извне, в случае например объявления объекта в отдельном файле.\\
:!: Пример: Сигнал движения курсора мыши import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 300; height: 300; visible: true; Rectangle { anchors.fill: parent color: mousearea.containsMouse ? "red" : "lightgreen" signal myMouseSignal(int x, int y) onMyMouseSignal: { txt.text= "

X:"+x +"; Y:"+y +"

" } Text { id: txt anchors.centerIn: parent } MouseArea { anchors.fill: parent hoverEnabled: true onMouseXChanged: parent.myMouseSignal(mouseX, mouseY) onMouseYChanged: parent.myMouseSignal(mouseX, mouseY) } } }
:!: Пример: Собственный элемент+ передача сигнала наружу // Файл button.qml import QtQuick 2.0 BorderImage { property alias text: txt.text signal clicked; source: "qrc:/button.png" width: txt.width +15 height: txt.height +15 border {left: 15; top: 12; right: 15; bottom: 15} Text { id: txt color: "white" anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: parent.clicked(); onPressed: parent.source= "button-press.png" onReleased: parent.source= "button.png" } } // Основной файл import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 150; height: 100; visible: true; Rectangle { anchors.fill: parent Button { anchors.centerIn: parent text: "Click this" onClicked: text= "Clicked" } } }
  ==== Ввод с клавиатуры ==== ---- В основном ввод с клавиатуры обрабатывается двумя элементами: **TextInput** (однострочный) и **TextEdit** (многострочный).\\ Размер элемента будет соответствовать введенному тексту, что бы при пустом вводе элемент не исчезал, нужно задавать мин.ширину
:!: Пример: Простое поле ввода текста import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: txt.width + 20; height: 100; visible: true; Rectangle { anchors.fill: parent TextInput { id: txt anchors.centerIn: parent color: "red" text: "Text in this" font.pixelSize: 32 focus: true } } }
  ==== Фокус ==== ---- Если на форме один элемент, он получает фокус автоматом, далее, фокус можно изменить ручным выбором мышкой либо табом. Програмно можно воспользоваться свойством **focus**.\\ Нетекстовые элементы так же могут иметь фокус.\\ Для управления порядком фокуса табом, используется т.н. прикрепляемое свойство "**KeyNavigation.tab: **". В этом свойстве так же доступны клавиши типа **KeyNavigation.left**, **KeyNavigation.right**, **KeyNavigation.up**, **KeyNavigation.down** и т.д.\\
:!: Пример: Два поля ввода, выделение их фокусировки import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 200; height: 80; visible: true; Rectangle { anchors.fill: parent TextEdit { anchors { left: parent.left right: parent.horizontalCenter top: parent.top bottom: parent.bottom } text: "Text1\nText1\nText1" font.pixelSize: 20 color: focus ? "red":"black" focus: true } TextEdit { anchors { left: parent.horizontalCenter right: parent.right top: parent.top bottom: parent.bottom } text: "Text2\nText2\nText2" font.pixelSize: 20 color: focus ? "red":"black" } } }
  ==== Сырой ввод ==== ---- С помощью прикрепляемого свойства **Keys** можно получить доступ к событиям клавиатуры, с полной информацией о событии.\\
:!: Пример: Перемещение элемента с помощью клавиш-стрелок import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 200; height: 80; visible: true; Rectangle { anchors.fill: parent Text { x: 20; y: 20; text: "Move this text" horizontalAlignment: Text.AlignHCenter Keys.onLeftPressed: x -= 3 Keys.onRightPressed: x += 3 Keys.onUpPressed: y -= 3 Keys.onDownPressed: y += 3 focus: true } } }
При помощи **Keys.forwardTo** можно пересылать события другим элементам, для дальнейшей обработки.\\
:!: Пример: Так же, можно сделать это в одном обработчике import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 200; height: 80; visible: true; Rectangle { anchors.fill: parent Text { x: 20; y: 20; text: "Move this text" horizontalAlignment: Text.AlignHCenter focus: true Keys.onPressed: { if(event.key === Qt.Key_Left) { x -= 3; } else if(event.key === Qt.Key_Right) { x += 3; } else if(event.key === Qt.Key_Down) { y += 3; } else if(event.key === Qt.Key_Up) {y -= 3; } else if(event.key === Qt.Key_Plus) {font.pixelSize++; } else if(event.key === Qt.Key_Minus) {font.pixelSize--; } } } } }
  ==== Мультитач ==== ---- Область региона- **MultiPointTouchArea**. Он содержит в себе элементы обработки события касания- **TouchPoint**, их должно быть столько, сколько одновременных касаний мы собираемся принимать, его можно сравнить с **MouseArea**.\\
:!: Пример: Обработка пяти одновременных касаний import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 400; height: 400; visible: true Rectangle { anchors.fill: parent color: "black" MultiPointTouchArea { anchors.fill: parent minimumTouchPoints: 1 maximumTouchPoints: 5 touchPoints: [ TouchPoint {}, TouchPoint {}, TouchPoint {}, TouchPoint {}, TouchPoint {} ] Repeater { model: parent.touchPoints Rectangle { color: "white" x: modelData.x; y: modelData.y width: 30; height: 30 visible: modelData.pressed } } } } }
Так же, у **TouchPoint** есть следующие св-ва: | pressed | При касании == true | | pressure | Сила нажатия (не все устр-ва поддерживают) | | previous{X,Y} | Пред координаты касания | | start{X,Y} | Начальные координаты касания | | x,y | Текущие координаты касания |   ===== Анимация ===== ==== Анимация при изменении свойств ==== ---- Для анимации свойств существует элемент **PropertyAnimation**, с ним можно менять сразу **несколько свойств одновременно**.\\
:!: Пример: Одновременное изменение **x** и **y** import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 400; height: 400; visible: true Rectangle { anchors.fill: parent color: "lightgreen" Image { id: img x: 0; y: 0; source: "qrc:/button.png" } PropertyAnimation { target: img properties: "x,y" // Начальное и конечное значение для св-в from: 0; to: 400 - img.height // Длительность мс duration: 1500 running: true loops: Animation.Infinite // Режим. скорость и т.д. есть интерактивный редактор easing.type: Easing.OutExpo } } }
  === Изменение числовых значений === ---- Более эффективная реализация для **real** и **int**.\\
:!: Пример: Изменение размера вложенного прямоугольника x2 import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 300; height: 100; visible: true Rectangle { id: par anchors.fill: parent color: "lightgreen" Rectangle { x: 0; y: 0; height: 100 color: "red" NumberAnimation on width { id: anim1 from: 300; to: 0 duration: 2000 easing.type: Easing.InOutCubic onStopped: { anim2.start() } } NumberAnimation on width { id: anim2 from: 0; to: 300 duration: 2000 easing.type: Easing.InOutCubic running: false onStopped: { anim1.start() } } } } }
  === Изменение цвета === ---- **ColorAnimation** управляет изменением цвета, так же используется **from** и **to**.\\
:!: Пример: Изменение цвета import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 300; height: 100; visible: true Rectangle { anchors.fill: parent ColorAnimation on color { from: "lightgreen" to: "red" duration: 1500 running: true loops: Animation.Infinite } } }
  === Поворот === ---- **RotationAnimation** описывает поворот элемента.\\ Его свойство **direction** задает направление поворота: | RotationAnimation.Clockwise | по часовой (по умолчанию) | | RotationAnimation.Counterclockwise | против часовой | | RotationAnimationShortest | угол поворота в наименьшую сторону исходя из "from-to" |
:!: Пример: Поворот по часовой стрелке import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 150; height: 150; visible: true Rectangle { anchors.fill: parent Image { source: "qrc:/button.png" anchors.centerIn: parent smooth: true RotationAnimation on rotation { from: 0 to: 360 duration: 2000 loops: Animation.Infinite easing.type: Easing.InOutBack } } } }
  ==== Анимация поведения ==== ---- **Behavior** реагирует на изменение свойств элементов, соответственно в эти моменты может вызывать другую анимацию и т.д.\\ В следующем примере, **Behavior** реагирует на изменение **x** и **y** у изображения, внутри другой элемент анимации, изменение же свойств изображения вызывается из событиями мыши в **MouseArea**
:!: Пример: Анимация вызывается поведением import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { id: rect anchors.fill: parent Image { id: img x: 10; y: 10; source: "qrc:/button.png" smooth: true Text { anchors.verticalCenter: img.verticalCenter anchors.top: img.bottom text: "Move cursor" } Behavior on x { NumberAnimation { duration: 500 easing.type: Easing.OutBack } } Behavior on y { NumberAnimation { duration: 500 easing.type: Easing.OutBack } } } MouseArea { anchors.fill: rect hoverEnabled: true onMouseXChanged: img.x= mouseX onMouseYChanged: img.y= mouseY } } }
  ==== Параллельные и последовательные анимации ==== ---- Анимации могут быть объединены в группы, эти группы выполняются **последовательно** (SequentialAnimation) либо **параллельно** (ParallelAnimation). Группы могут быть вложенными друг в друга. Если элемент группы не указан, анимации выполняются параллельно.\\
:!: Пример: Параллельная анимация. Увеличение размера и прозрачность import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { anchors.fill: parent Image { id: img source: "qrc:/button.png" smooth: true anchors.centerIn: parent } ParallelAnimation { NumberAnimation { target: img properties: "scale" from: 0.1 to: 3.0 duration: 2000 easing.type: Easing.InOutCubic } NumberAnimation { target: img properties: "opacity" from: 1.0 to: 0 duration: 2000 } running: true loops: Animation.Infinite } } }
:!: Пример: Тоже самое, но немного другой подход import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { anchors.fill: parent Image { id: img source: "qrc:/button.png" smooth: true anchors.centerIn: parent NumberAnimation on scale { from: 0.1 to: 3.0 duration: 2000 easing.type: Easing.InOutCubic loops: Animation.Infinite } NumberAnimation on opacity { from: 1.0 to: 0 duration: 2000 loops: Animation.Infinite } } } }
Пример последовательной анимации: После нажатия мыши объект упадет вниз, повернется вокруг своей оси, полежит немного, затем поднимется обратно вверх:
:!: Пример: import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 130; height: 450; visible: true Rectangle { anchors.fill: parent Image { id: img source: "qrc:/button.png" smooth: true Text { anchors.horizontalCenter: parent.horizontalAlignment anchors.top: parent.bottom text: "Click for do it" } MouseArea { anchors.fill: img onClicked: anim.running= true } SequentialAnimation { id: anim NumberAnimation { target: img from: 20; to: 300; properties: "y" duration: 1000 easing.type: Easing.OutBounce } RotationAnimation { target: img from: 0; to: 360; properties: "rotation" duration: 1000 } PauseAnimation {duration: 500} NumberAnimation { target: img from: 300; to: 20; properties: "y" duration: 1000 easing.type: Easing.OutBounce } } } } }
  ==== Состояния и переходы ==== === Состояния === ---- Управлять состоянием можно при помощи элемента **State**, поддерживает наборы списков, описания элементов с набором их состояний.\\ Все объекты **обязательно** должны быть **именованными**.\\ Список состояний присваивается свойству **states** а необходимый набор присваивается свойству **state**, так и происходит смена состояний.\\ Изменение свойств внутри состояния перечисляется в в элементе **PropertyChanges**
:!: Пример: Два определенных состояния прямоугольника и смена между ними import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { anchors.fill: parent Rectangle { id: rect state: "State2" Text { id: txt anchors.centerIn: parent } } states: [ State { name: "State1" PropertyChanges { target: rect color: "lightgreen" width: 150; height: 60; } PropertyChanges { target: txt text: "State2: Click" } }, State { name: "State2" PropertyChanges { target: rect color: "yellow" width: 200; height: 120; } PropertyChanges { target: txt text: "State1: Click" } } ] MouseArea { anchors.fill: parent onClicked: parent.state= (parent.state== "State1") ? "State2":"State1" } } }
  ==== Переходы ==== ---- Свойство **transitions** задает список переходов, в списке задаем элементы **Transition**, каждый элемент содержит два момента: * **from / to** - начальное/конечное состояние перехода (**States**, рассмотренные выше) * **PropertyAnimation** - управление анимацией перехода
:!: Пример: Переход между состояниями import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { anchors.fill: parent Rectangle { id: rect width: 100; height: 100 color: "magenta" state: "State1" Text { anchors.centerIn: parent text: "Click this" } MouseArea { anchors.fill: parent onClicked: rect.state = (rect.state == "State1") ? "State2":"State1" } states: [ State { name: "State1" PropertyChanges {target: rect; x: 0; y: 0} }, State { name: "State2" PropertyChanges {target: rect; x: 200; y: 200} } ] transitions: [ Transition { from: "State1"; to: "State2"; PropertyAnimation { target: rect; properties: "x,y"; duration: 1000 easing.type: Easing.OutBack } }, Transition { from: "State2"; to: "State1"; PropertyAnimation { target: rect; properties: "x,y"; duration: 1000 easing.type: Easing.InBounce } } ] } } }
Если свойства перехода будут одинаковыми, тогда можно использовать **шаблонный переход**:
:!: Пример: Тоже самое только шаблонный переход import QtQuick 2.12; import QtQuick.Controls 2.2 ApplicationWindow { width: 300; height: 300; visible: true Rectangle { anchors.fill: parent Rectangle { id: rect width: 100; height: 100 color: "magenta" state: "State1" Text { anchors.centerIn: parent text: "Click this" } MouseArea { anchors.fill: parent onClicked: rect.state = (rect.state == "State1") ? "State2":"State1" } states: [ State { name: "State1" PropertyChanges {target: rect; x: 0; y: 0} }, State { name: "State2" PropertyChanges {target: rect; x: 200; y: 200} } ] transitions: [ Transition { from: "*"; to: "*"; PropertyAnimation { target: rect; properties: "x,y"; duration: 1000 easing.type: Easing.OutBack } } ] } } }
  ==== Модуль частиц ==== ---- Движок для работы с частицами, в роли частиц могут выступать картинки и т.д.
:!: Пример: Кнопкопад import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Particles 2.0 ApplicationWindow { width: 360; height: 360; visible: true Rectangle { anchors.fill: parent color: "MidnightBlue" ParticleSystem { anchors.fill: parent ImageParticle { source: "qrc:/button.png" } Emitter { width: parent.width height: parent.height anchors.bottom: parent.bottom lifeSpan: 10000 sizeVariation: 16 emitRate: 20 velocity: AngleDirection { angle: 90 angleVariation: 10 magnitude: 100 } } } } }