Diferència entre revisions de la pàgina «Ús d'arxius vectorials SVG al QML (Qt6)»
Línia 67: | Línia 67: | ||
• '''Rotació Inicial i Opacitat de l'Agulla:''' | • '''Rotació Inicial i Opacitat de l'Agulla:''' | ||
− | + | - La <code>rotation</code> inicial de l'agulla (<code>agullaSvg</code>) es canvia a <code>0</code>. | |
− | + | - S'afegeix una propietat <code>opacity: 0.8</code> a l'agulla. | |
• '''Animació de Rotació (Behavior):''' ** S'afegeix un <code>Behavior on rotation</code> a l'agulla. Això significa que cada vegada que la propietat <code>rotation</code> de l'agulla canvia, s'executarà una animació. ** <code>NumberAnimation { duration: 300; easing.type: Easing.InOutBack }</code>: L'animació durarà 300 mil·lisegons i utilitzarà una funció d'acceleració <code>Easing.InOutBack</code> per a un efecte més dinàmic. | • '''Animació de Rotació (Behavior):''' ** S'afegeix un <code>Behavior on rotation</code> a l'agulla. Això significa que cada vegada que la propietat <code>rotation</code> de l'agulla canvia, s'executarà una animació. ** <code>NumberAnimation { duration: 300; easing.type: Easing.InOutBack }</code>: L'animació durarà 300 mil·lisegons i utilitzarà una funció d'acceleració <code>Easing.InOutBack</code> per a un efecte més dinàmic. |
Revisió del 14:43, 9 set 2025
Explicació de main.qml
de ampMeter00_00.pdf
El fitxer main.qml
de ampMeter00_00.pdf
estableix la base d'una aplicació Qt Quick que simula un amperímetre simple [1].
Estructura Bàsica de la Finestra
import QtQuick
: Importa el mòdul Qt Quick necessari per als elements de la interfície d'usuari [1].Window
: Defineix la finestra principal de l'aplicació [1].width: 640
,height: 480
: Estableix les dimensions de la finestra [1].visible: true
: Fa que la finestra sigui visible quan s'inicia l'aplicació [1].title: qsTr("ampMeter00")
: Defineix el títol de la finestra com "ampMeter00" [1].
Element Arrel (Rectangle)
Rectangle { id: root ... }
: Dins la finestra, hi ha unRectangle
anomenatroot
que serveix com a contenidor principal [1].anchors.fill: parent
: Fa que aquest rectangle ompli completament la finestra pare [1].color: "white"
: Estableix el color de fons del rectangle com a blanc [1].
Imatge de Fons de l'Amperímetre
Image { id: fonsSvg ... }
: S'afegeix un elementImage
per al fons de l'amperímetre [1].source: "qrc:/material/AmpMeterFons_c.svg"
: Carrega la imatge de fons des dels recursos de l'aplicació [1].fillMode: Image.PreserveAspectFit
: Aquesta propietat assegura que la imatge mantingui les seves proporcions originals mentre s'ajusta dins de l'espai disponible [1].anchors.centerIn: parent
: Centra la imatge de fons dins del seu element pare (root
rectangle) [1].
Imatge de l'Agulla de l'Amperímetre
Image { id: agullaSvg ... }
: S'afegeix un altre elementImage
per representar l'agulla de l'amperímetre [2].source: "qrc:/material/AmpMeterAgulla_c.svg"
: Carrega la imatge de l'agulla [2].fillMode: Image.PreserveAspectFit
: Igual que amb el fons, manté les proporcions de l'agulla [2].anchors.centerIn: parent
: Centra l'agulla dins del seu pare (root
rectangle) [2].rotation: 45
: Estableix una rotació inicial de 45 graus per a l'agulla [2]. Aquest és el punt clau de control en aquesta primera versió, ja que la rotació es defineix de manera estàtica [2].
En resum, ampMeter00_00.pdf
presenta una interfície d'usuari bàsica amb un fons i una agulla d'amperímetre, on l'agulla es mostra en una posició fixa de 45 graus [1, 2].
// main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00") Rectangle { id: root anchors.fill: parent color: "white" Image { id: fonsSvg source: "qrc:/material/AmpMeterFons_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: "qrc:/material/AmpMeterAgulla_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent rotation: 45 } } }
Canvis Progressius en el Codi
A continuació, s'expliquen els canvis introduïts en cada iteració del projecte:
De ampMeter00_00.pdf
a ampMeter00_01.pdf
La versió ampMeter00_01.pdf
introdueix la interactivitat i la animació per a l'agulla de l'amperímetre.
• Rotació Inicial i Opacitat de l'Agulla:
- La rotation
inicial de l'agulla (agullaSvg
) es canvia a 0
.
- S'afegeix una propietat opacity: 0.8
a l'agulla.
• Animació de Rotació (Behavior): ** S'afegeix un Behavior on rotation
a l'agulla. Això significa que cada vegada que la propietat rotation
de l'agulla canvia, s'executarà una animació. ** NumberAnimation { duration: 300; easing.type: Easing.InOutBack }
: L'animació durarà 300 mil·lisegons i utilitzarà una funció d'acceleració Easing.InOutBack
per a un efecte més dinàmic.
• Botons de Control: ** S'afegeix una fila de botons (Row
) a la part inferior de la pantalla, anomenada controlButtons
. ** Aquests botons es centren horitzontalment (anchors.horizontalCenter: parent.horizontalCenter
) i es col·loquen a 20 píxels de la part inferior (anchors.bottomMargin: 20
). ** Hi ha un spacing: 20
entre els botons. ** Cada botó és un Rectangle
gris amb un width
i height
de 60 píxels i un radius
de 30 (creant cercles). ** Cada Rectangle
conté un Text
blanc amb la seva etiqueta (per exemple, "-60", "0", "60"). ** Cada botó té una MouseArea
que detecta clics. Quan es clica, la rotation
de l'agulla (agullaSvg.rotation
) es defineix a un valor específic (-45, -20, 0, 20, 45). ** El cursorShape
de la MouseArea
es defineix com Qt.PointingHandCursor
per indicar que és un element clicable.
Aquesta versió permet a l'usuari controlar la rotació de l'agulla mitjançant botons, amb una animació suau cada vegada que l'agulla canvia de posició.
// main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00_01") Rectangle { id: root anchors.fill: parent color: "white" Image { id: fonsSvg source: "qrc:/material/AmpMeterFons_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: "qrc:/material/AmpMeterAgulla_c.svg" fillMode: Image.PreserveAspectFit opacity: 0.8 anchors.centerIn: parent rotation: 0 Behavior on rotation { NumberAnimation { duration: 300 easing.type: Easing.InOutBack } } } Row { id: controlButtons anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 spacing: 20 Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "-60" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = -45 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "-30" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = -20 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "0" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 0 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "30" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 20 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "60" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 45 cursorShape: Qt.PointingHandCursor } } } } }
De ampMeter00_01.pdf
a ampMeter00_02.pdf
La versió ampMeter00_02.pdf
afegeix una animació automàtica i contínua per a l'agulla.
• Canvi de Funció d'Acceleració: ** La funció d'acceleració (easing.type
) del NumberAnimation
de l'agulla es canvia de Easing.InOutBack
a Easing.InOutQuad
.
• Animació Seqüencial: ** S'afegeix una SequentialAnimation on rotation
a l'agulla (agullaSvg
). ** loops: Animation.Infinite
: Aquesta animació es repetirà indefinidament. ** Consisteix en dues RotationAnimation
s: *** Una que fa girar l'agulla fins a 45
graus durant 1000
mil·lisegons. *** Una segona que la fa girar fins a -45
graus durant 500
mil·lisegons. ** Aquesta animació fa que l'agulla es mogui contínuament entre 45
i -45
graus quan no es controla manualment.
• Els botons de control manual romanen igual, permetent a l'usuari sobrescriure l'animació automàtica.
main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00_02") Rectangle { id: root anchors.fill: parent color: "white" Image { id: fonsSvg source: "qrc:/material/AmpMeterFons_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: "qrc:/material/AmpMeterAgulla_c.svg" fillMode: Image.PreserveAspectFit opacity: 0.8 anchors.centerIn: parent rotation: 0 Behavior on rotation { NumberAnimation { duration: 300 easing.type: Easing.InOutQuad } } SequentialAnimation on rotation { loops: Animation.Infinite RotationAnimation { to: 45 duration: 1000 } RotationAnimation { to: -45 duration: 500 } } } Row { id: controlButtons anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 spacing: 20 Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "-60" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = -45 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "-30" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = -20 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "0" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 0 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "30" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 20 cursorShape: Qt.PointingHandCursor } } Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: "60" color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = 45 cursorShape: Qt.PointingHandCursor } } } } }
De ampMeter00_02.pdf
a ampMeter00_03.pdf
La versió ampMeter00_03.pdf
refactoritza el codi dels botons utilitzant un model de dades (ListModel
) i un Repeater
, millorant la modularitat i la facilitat de manteniment.
• Eliminació de l'Animació Seqüencial: ** La SequentialAnimation on rotation
s'elimina.
• Model de Dades per a Botons (ListModel
): ** Es defineix un ListModel
amb id: buttonModel
. ** Conté ListElement
s, cadascun amb una label
(el text del botó) i un value
(el valor de rotació corresponent). Per exemple, { label: "-60"; value: -45 }
.
• Generació de Botons amb Repeater
: ** En lloc de declarar cada botó individualment, s'utilitza un Repeater
amb model: buttonModel
dins del Row
de controlButtons
. ** El Repeater
crea un Rectangle
per a cada element del model. ** El Text
de cada botó utilitza model.label
i l'esdeveniment onClicked
de la MouseArea
utilitza agullaSvg.rotation = model.value
.
Aquest canvi redueix la duplicitat de codi i facilita l'addició o modificació de botons en el futur.
main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00_03") Rectangle { id: root anchors.fill: parent color: "white" Image { id: fonsSvg source: "qrc:/material/AmpMeterFons_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: "qrc:/material/AmpMeterAgulla_c.svg" fillMode: Image.PreserveAspectFit opacity: 0.8 anchors.centerIn: parent rotation: 0 Behavior on rotation { NumberAnimation { duration: 300 easing.type: Easing.InOutBack } } } ListModel { id: buttonModel ListElement { label: "-60"; value: -45 } ListElement { label: "-30"; value: -20 } ListElement { label: "0"; value: 0 } ListElement { label: "30"; value: 20 } ListElement { label: "60"; value: 45 } } Row { id: controlButtons anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 spacing: 20 Repeater { model: buttonModel Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: model.label color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: agullaSvg.rotation = model.value cursorShape: Qt.PointingHandCursor } } } } } }
De ampMeter00_03.pdf
a ampMeter00_04.pdf
La versió ampMeter00_04.pdf
millora la modularitat extraient la lògica dels botons en un component QML reutilitzable anomenat Botonera.qml
.
• Creació del Component Botonera.qml
: ** Es crea un nou fitxer Botonera.qml
que conté la implementació del Row
i el Repeater
per als botons. ** Aquest component té una propietat property alias model: repeater.model
que permet al component pare passar-li el ListModel
. ** Defineix un signal buttonClicked(real value)
que s'emet quan es prem un botó, enviant el value
de rotació. ** Dins del MouseArea
de cada botó, onClicked
ara crida botonera.buttonClicked(model.value)
.
• Ús del Component Botonera
a main.qml
: ** A main.qml
, el Row
original de botons es reemplaça per una instància del nou component Botonera
. ** controlButtons
(la instància de Botonera
) rep el buttonModel
mitjançant model: buttonModel
. ** Captura el senyal onButtonClicked
del component Botonera
i actualitza la rotació de l'agulla amb agullaSvg.rotation = value
.
Aquesta refactorització permet reutilitzar el bloc de botons en altres parts de l'aplicació o en altres projectes, fent el codi de main.qml
més net i centrat en la composició.
main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00_04") Rectangle { id: root anchors.fill: parent color: "white" Image { id: fonsSvg source: "qrc:/material/AmpMeterFons_c.svg" fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: "qrc:/material/AmpMeterAgulla_c.svg" fillMode: Image.PreserveAspectFit opacity: 0.8 anchors.centerIn: parent rotation: 0 Behavior on rotation { NumberAnimation { duration: 300 easing.type: Easing.InOutBack } } } ListModel { id: buttonModel ListElement { label: "-60"; value: -45 } ListElement { label: "-30"; value: -20 } ListElement { label: "0"; value: 0 } ListElement { label: "30"; value: 20 } ListElement { label: "60"; value: 45 } } Botonera { id: controlButtons anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 model: buttonModel onButtonClicked: function(value) { agullaSvg.rotation = value } } } }
Botonera.qml import QtQuick Row { id: botonera spacing: 20 property alias model: repeater.model signal buttonClicked(real value) Repeater { id: repeater Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: model.label color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: botonera.buttonClicked(model.value) cursorShape: Qt.PointingHandCursor } } } }
De ampMeter00_04.pdf
a ampMeter00_05.pdf
La versió ampMeter00_05.pdf
continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un component QML propi anomenat AmpMeter.qml
.
• Creació del Component AmpMeter.qml
: ** Es crea un nou fitxer AmpMeter.qml
que conté el fons (fonsSvg
) i l'agulla (agullaSvg
) de l'amperímetre. ** Exposa una propietat property alias rotation: agullaSvg.rotation
per permetre al component pare controlar directament la rotació de l'agulla interna. ** També defineix propietats per personalitzar les imatges de fons i agulla (fonsSource
, agullaSource
) i l'opacitat de l'agulla (agullaOpacity
). ** El Behavior on rotation
per a l'agulla es troba ara dins d'aquest component.
• Ús del Component AmpMeter
a main.qml
: ** A main.qml
, les dues imatges originals (fonsSvg
i agullaSvg
) es reemplacen per una instància del nou component AmpMeter
. ** L'esdeveniment onButtonClicked
del Botonera
ara actualitza la rotació de l'amperímetre amb ampMeter.rotation = value
, utilitzant la propietat exposada pel component AmpMeter
.
Aquesta última refactorització crea un component autònom (AmpMeter
) que conté tota la lògica i la presentació de l'amperímetre, fent que main.qml
sigui extremadament simple i es basi en la composició de components.
main.qml import QtQuick Window { width: 640 height: 480 visible: true title: qsTr("ampMeter00_05") Rectangle { id: root anchors.fill: parent color: "white" AmpMeter { id: ampMeter anchors.centerIn: parent width: parent.width height: parent.height } ListModel { id: buttonModel ListElement { label: "-60"; value: -45 } ListElement { label: "-30"; value: -20 } ListElement { label: "0"; value: 0 } ListElement { label: "30"; value: 20 } ListElement { label: "60"; value: 45 } } Botonera { id: controlButtons anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 model: buttonModel onButtonClicked: function(value) { ampMeter.rotation = value } } } }
Botonera.qml import QtQuick Row { id: botonera spacing: 20 property alias model: repeater.model signal buttonClicked(real value) Repeater { id: repeater Rectangle { width: 60 height: 60 radius: 30 color: "gray" Text { anchors.centerIn: parent text: model.label color: "white" font.pixelSize: 14 font.bold: true } MouseArea { anchors.fill: parent onClicked: botonera.buttonClicked(model.value) cursorShape: Qt.PointingHandCursor } } } }
AmpMeter.qml import QtQuick Item { id: ampMeter property alias rotation: agullaSvg.rotation property string fonsSource: "qrc:/material/AmpMeterFons_c.svg" property string agullaSource: "qrc:/material/AmpMeterAgulla_c.svg" property real agullaOpacity: 0.8 Image { id: fonsSvg source: ampMeter.fonsSource fillMode: Image.PreserveAspectFit anchors.centerIn: parent } Image { id: agullaSvg source: ampMeter.agullaSource fillMode: Image.PreserveAspectFit opacity: ampMeter.agullaOpacity anchors.centerIn: parent rotation: 0 Behavior on rotation { NumberAnimation { duration: 300 easing.type: Easing.InOutBack } } } }