Diferència entre revisions de la pàgina «Ús d'arxius vectorials SVG al QML (Qt6)»

De binefa.com
Salta a la navegació Salta a la cerca
Línia 70: Línia 70:
 
** S'afegeix una propietat <code>opacity: 0.8</code> a l'agulla.
 
** 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.
  
'''Botons de Control:''' ** S'afegeix una '''fila de botons (<code>Row</code>)''' a la part inferior de la pantalla, anomenada <code>controlButtons</code>. ** Aquests botons es centren horitzontalment (<code>anchors.horizontalCenter: parent.horizontalCenter</code>) i es col·loquen a 20 píxels de la part inferior (<code>anchors.bottomMargin: 20</code>). ** Hi ha un <code>spacing: 20</code> entre els botons. ** Cada botó és un <code>Rectangle</code> gris amb un <code>width</code> i <code>height</code> de 60 píxels i un <code>radius</code> de 30 (creant cercles). ** Cada <code>Rectangle</code> conté un <code>Text</code> blanc amb la seva etiqueta (per exemple, "-60", "0", "60"). ** Cada botó té una <code>MouseArea</code> que detecta clics. Quan es clica, la <code>rotation</code> de l'agulla (<code>agullaSvg.rotation</code>) es defineix a un valor específic (-45, -20, 0, 20, 45). ** El <code>cursorShape</code> de la <code>MouseArea</code> es defineix com <code>Qt.PointingHandCursor</code> per indicar que és un element clicable.
+
* '''Botons de Control:'''  
 +
** S'afegeix una '''fila de botons (<code>Row</code>)''' a la part inferior de la pantalla, anomenada <code>controlButtons</code>.  
 +
** Aquests botons es centren horitzontalment (<code>anchors.horizontalCenter: parent.horizontalCenter</code>) i es col·loquen a 20 píxels de la part inferior (<code>anchors.bottomMargin: 20</code>).  
 +
** Hi ha un <code>spacing: 20</code> entre els botons.  
 +
** Cada botó és un <code>Rectangle</code> gris amb un <code>width</code> i <code>height</code> de 60 píxels i un <code>radius</code> de 30 (creant cercles).  
 +
** Cada <code>Rectangle</code> conté un <code>Text</code> blanc amb la seva etiqueta (per exemple, "-60", "0", "60").  
 +
** Cada botó té una <code>MouseArea</code> que detecta clics. Quan es clica, la <code>rotation</code> de l'agulla (<code>agullaSvg.rotation</code>) es defineix a un valor específic (-45, -20, 0, 20, 45). ** El <code>cursorShape</code> de la <code>MouseArea</code> es defineix com <code>Qt.PointingHandCursor</code> 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ó.
 
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ó.
Línia 225: Línia 233:
 
=== De <code>ampMeter00_01.pdf</code> a <code>ampMeter00_02.pdf</code> ===
 
=== De <code>ampMeter00_01.pdf</code> a <code>ampMeter00_02.pdf</code> ===
 
La versió <code>ampMeter00_02.pdf</code> afegeix una '''animació automàtica i contínua''' per a l'agulla.
 
La versió <code>ampMeter00_02.pdf</code> afegeix una '''animació automàtica i contínua''' per a l'agulla.
'''Canvi de Funció d'Acceleració:''' ** La funció d'acceleració (<code>easing.type</code>) del <code>NumberAnimation</code> de l'agulla es canvia de <code>Easing.InOutBack</code> a <code>Easing.InOutQuad</code>.
+
* '''Canvi de Funció d'Acceleració:'''  
'''Animació Seqüencial:''' ** S'afegeix una <code>SequentialAnimation on rotation</code> a l'agulla (<code>agullaSvg</code>). ** <code>loops: Animation.Infinite</code>: Aquesta animació es repetirà indefinidament. ** Consisteix en dues <code>RotationAnimation</code>s: *** Una que fa girar l'agulla fins a <code>45</code> graus durant <code>1000</code> mil·lisegons. *** Una segona que la fa girar fins a <code>-45</code> graus durant <code>500</code> mil·lisegons. ** Aquesta animació fa que l'agulla es mogui contínuament entre <code>45</code> i <code>-45</code> graus quan no es controla manualment.
+
** La funció d'acceleració (<code>easing.type</code>) del <code>NumberAnimation</code> de l'agulla es canvia de <code>Easing.InOutBack</code> a <code>Easing.InOutQuad</code>.
Els botons de control manual romanen igual, permetent a l'usuari sobrescriure l'animació automàtica.
+
* '''Animació Seqüencial:''' ** S'afegeix una <code>SequentialAnimation on rotation</code> a l'agulla (<code>agullaSvg</code>).  
 +
** <code>loops: Animation.Infinite</code>: Aquesta animació es repetirà indefinidament.  
 +
** Consisteix en dues <code>RotationAnimation</code>s:  
 +
*** Una que fa girar l'agulla fins a <code>45</code> graus durant <code>1000</code> mil·lisegons.  
 +
*** Una segona que la fa girar fins a <code>-45</code> graus durant <code>500</code> mil·lisegons.  
 +
** Aquesta animació fa que l'agulla es mogui contínuament entre <code>45</code> i <code>-45</code> graus quan no es controla manualment.
 +
* Els botons de control manual romanen igual, permetent a l'usuari sobrescriure l'animació automàtica.
 
<pre><nowiki>
 
<pre><nowiki>
main.qml
+
// main.qml
 
import QtQuick
 
import QtQuick
 
Window {
 
Window {
Línia 391: Línia 405:
 
=== De <code>ampMeter00_02.pdf</code> a <code>ampMeter00_03.pdf</code> ===
 
=== De <code>ampMeter00_02.pdf</code> a <code>ampMeter00_03.pdf</code> ===
 
La versió <code>ampMeter00_03.pdf</code> refactoritza el codi dels botons utilitzant un '''model de dades (<code>ListModel</code>) i un <code>Repeater</code>''', millorant la modularitat i la facilitat de manteniment.
 
La versió <code>ampMeter00_03.pdf</code> refactoritza el codi dels botons utilitzant un '''model de dades (<code>ListModel</code>) i un <code>Repeater</code>''', millorant la modularitat i la facilitat de manteniment.
'''Eliminació de l'Animació Seqüencial:''' ** La <code>SequentialAnimation on rotation</code> s'elimina.
+
* '''Eliminació de l'Animació Seqüencial:'''  
'''Model de Dades per a Botons (<code>ListModel</code>):''' ** Es defineix un <code>ListModel</code> amb <code>id: buttonModel</code>. ** Conté <code>ListElement</code>s, cadascun amb una <code>label</code> (el text del botó) i un <code>value</code> (el valor de rotació corresponent). Per exemple, <code>{ label: "-60"; value: -45 }</code>.
+
** La <code>SequentialAnimation on rotation</code> s'elimina.
'''Generació de Botons amb <code>Repeater</code>:''' ** En lloc de declarar cada botó individualment, s'utilitza un <code>Repeater</code> amb <code>model: buttonModel</code> dins del <code>Row</code> de <code>controlButtons</code>. ** El <code>Repeater</code> crea un <code>Rectangle</code> per a cada element del model. ** El <code>Text</code> de cada botó utilitza <code>model.label</code> i l'esdeveniment <code>onClicked</code> de la <code>MouseArea</code> utilitza <code>agullaSvg.rotation = model.value</code>.
+
* '''Model de Dades per a Botons (<code>ListModel</code>):'''  
 +
** Es defineix un <code>ListModel</code> amb <code>id: buttonModel</code>. ** Conté <code>ListElement</code>s, cadascun amb una <code>label</code> (el text del botó) i un <code>value</code> (el valor de rotació corresponent). Per exemple, <code>{ label: "-60"; value: -45 }</code>.
 +
* '''Generació de Botons amb <code>Repeater</code>:'''  
 +
** En lloc de declarar cada botó individualment, s'utilitza un <code>Repeater</code> amb <code>model: buttonModel</code> dins del <code>Row</code> de <code>controlButtons</code>.  
 +
** El <code>Repeater</code> crea un <code>Rectangle</code> per a cada element del model.  
 +
** El <code>Text</code> de cada botó utilitza <code>model.label</code> i l'esdeveniment <code>onClicked</code> de la <code>MouseArea</code> utilitza <code>agullaSvg.rotation = model.value</code>.
 
Aquest canvi redueix la duplicitat de codi i facilita l'addició o modificació de botons en el futur.
 
Aquest canvi redueix la duplicitat de codi i facilita l'addició o modificació de botons en el futur.
 
<pre><nowiki>
 
<pre><nowiki>
main.qml
+
// main.qml
 
import QtQuick
 
import QtQuick
 
Window {
 
Window {
Línia 477: Línia 496:
 
=== De <code>ampMeter00_03.pdf</code> a <code>ampMeter00_04.pdf</code> ===
 
=== De <code>ampMeter00_03.pdf</code> a <code>ampMeter00_04.pdf</code> ===
 
La versió <code>ampMeter00_04.pdf</code> millora la modularitat extraient la lògica dels botons en un '''component QML reutilitzable''' anomenat <code>Botonera.qml</code>.
 
La versió <code>ampMeter00_04.pdf</code> millora la modularitat extraient la lògica dels botons en un '''component QML reutilitzable''' anomenat <code>Botonera.qml</code>.
'''Creació del Component <code>Botonera.qml</code>:''' ** Es crea un nou fitxer <code>Botonera.qml</code> que conté la implementació del <code>Row</code> i el <code>Repeater</code> per als botons. ** Aquest component té una propietat <code>property alias model: repeater.model</code> que permet al component pare passar-li el <code>ListModel</code>. ** Defineix un <code>signal buttonClicked(real value)</code> que s'emet quan es prem un botó, enviant el <code>value</code> de rotació. ** Dins del <code>MouseArea</code> de cada botó, <code>onClicked</code> ara crida <code>botonera.buttonClicked(model.value)</code>.
+
* '''Creació del Component <code>Botonera.qml</code>:'''  
'''Ús del Component <code>Botonera</code> a <code>main.qml</code>:''' ** A <code>main.qml</code>, el <code>Row</code> original de botons es reemplaça per una instància del nou component <code>Botonera</code>. ** <code>controlButtons</code> (la instància de <code>Botonera</code>) rep el <code>buttonModel</code> mitjançant <code>model: buttonModel</code>. ** Captura el senyal <code>onButtonClicked</code> del component <code>Botonera</code> i actualitza la rotació de l'agulla amb <code>agullaSvg.rotation = value</code>.
+
** Es crea un nou fitxer <code>Botonera.qml</code> que conté la implementació del <code>Row</code> i el <code>Repeater</code> per als botons.  
 +
** Aquest component té una propietat <code>property alias model: repeater.model</code> que permet al component pare passar-li el <code>ListModel</code>.  
 +
** Defineix un <code>signal buttonClicked(real value)</code> que s'emet quan es prem un botó, enviant el <code>value</code> de rotació.  
 +
** Dins del <code>MouseArea</code> de cada botó, <code>onClicked</code> ara crida <code>botonera.buttonClicked(model.value)</code>.
 +
* '''Ús del Component <code>Botonera</code> a <code>main.qml</code>:'''  
 +
** A <code>main.qml</code>, el <code>Row</code> original de botons es reemplaça per una instància del nou component <code>Botonera</code>.  
 +
** <code>controlButtons</code> (la instància de <code>Botonera</code>) rep el <code>buttonModel</code> mitjançant <code>model: buttonModel</code>.  
 +
** Captura el senyal <code>onButtonClicked</code> del component <code>Botonera</code> i actualitza la rotació de l'agulla amb <code>agullaSvg.rotation = value</code>.
 
Aquesta refactorització permet reutilitzar el bloc de botons en altres parts de l'aplicació o en altres projectes, fent el codi de <code>main.qml</code> més net i centrat en la composició.
 
Aquesta refactorització permet reutilitzar el bloc de botons en altres parts de l'aplicació o en altres projectes, fent el codi de <code>main.qml</code> més net i centrat en la composició.
 
<pre><nowiki>
 
<pre><nowiki>
main.qml
+
// main.qml
 
import QtQuick
 
import QtQuick
 
Window {
 
Window {
Línia 539: Línia 565:
 
</nowiki></pre>
 
</nowiki></pre>
 
<pre><nowiki>
 
<pre><nowiki>
Botonera.qml
+
// Botonera.qml
 
import QtQuick
 
import QtQuick
  
Línia 577: Línia 603:
 
=== De <code>ampMeter00_04.pdf</code> a <code>ampMeter00_05.pdf</code> ===
 
=== De <code>ampMeter00_04.pdf</code> a <code>ampMeter00_05.pdf</code> ===
 
La versió <code>ampMeter00_05.pdf</code> continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un '''component QML propi''' anomenat <code>AmpMeter.qml</code>.
 
La versió <code>ampMeter00_05.pdf</code> continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un '''component QML propi''' anomenat <code>AmpMeter.qml</code>.
'''Creació del Component <code>AmpMeter.qml</code>:''' ** Es crea un nou fitxer <code>AmpMeter.qml</code> que conté el fons (<code>fonsSvg</code>) i l'agulla (<code>agullaSvg</code>) de l'amperímetre. ** Exposa una propietat <code>property alias rotation: agullaSvg.rotation</code> per permetre al component pare controlar directament la rotació de l'agulla interna. ** També defineix propietats per personalitzar les imatges de fons i agulla (<code>fonsSource</code>, <code>agullaSource</code>) i l'opacitat de l'agulla (<code>agullaOpacity</code>). ** El <code>Behavior on rotation</code> per a l'agulla es troba ara dins d'aquest component.
+
* '''Creació del Component <code>AmpMeter.qml</code>:'''  
'''Ús del Component <code>AmpMeter</code> a <code>main.qml</code>:''' ** A <code>main.qml</code>, les dues imatges originals (<code>fonsSvg</code> i <code>agullaSvg</code>) es reemplacen per una instància del nou component <code>AmpMeter</code>. ** L'esdeveniment <code>onButtonClicked</code> del <code>Botonera</code> ara actualitza la rotació de l'amperímetre amb <code>ampMeter.rotation = value</code>, utilitzant la propietat exposada pel component <code>AmpMeter</code>.
+
** Es crea un nou fitxer <code>AmpMeter.qml</code> que conté el fons (<code>fonsSvg</code>) i l'agulla (<code>agullaSvg</code>) de l'amperímetre.  
 +
** Exposa una propietat <code>property alias rotation: agullaSvg.rotation</code> per permetre al component pare controlar directament la rotació de l'agulla interna.  
 +
** També defineix propietats per personalitzar les imatges de fons i agulla (<code>fonsSource</code>, <code>agullaSource</code>) i l'opacitat de l'agulla (<code>agullaOpacity</code>).  
 +
** El <code>Behavior on rotation</code> per a l'agulla es troba ara dins d'aquest component.
 +
* '''Ús del Component <code>AmpMeter</code> a <code>main.qml</code>:'''  
 +
** A <code>main.qml</code>, les dues imatges originals (<code>fonsSvg</code> i <code>agullaSvg</code>) es reemplacen per una instància del nou component <code>AmpMeter</code>.  
 +
** L'esdeveniment <code>onButtonClicked</code> del <code>Botonera</code> ara actualitza la rotació de l'amperímetre amb <code>ampMeter.rotation = value</code>, utilitzant la propietat exposada pel component <code>AmpMeter</code>.
 +
 
 
Aquesta última refactorització crea un component autònom (<code>AmpMeter</code>) que conté tota la lògica i la presentació de l'amperímetre, fent que <code>main.qml</code> sigui extremadament simple i es basi en la composició de components.
 
Aquesta última refactorització crea un component autònom (<code>AmpMeter</code>) que conté tota la lògica i la presentació de l'amperímetre, fent que <code>main.qml</code> sigui extremadament simple i es basi en la composició de components.
 
<pre><nowiki>
 
<pre><nowiki>
main.qml
+
// main.qml
 
import QtQuick
 
import QtQuick
 
Window {
 
Window {
Línia 624: Línia 657:
 
</nowiki></pre>
 
</nowiki></pre>
 
<pre><nowiki>
 
<pre><nowiki>
Botonera.qml
+
// Botonera.qml
 
import QtQuick
 
import QtQuick
  
Línia 661: Línia 694:
 
</nowiki></pre>
 
</nowiki></pre>
 
<pre><nowiki>
 
<pre><nowiki>
AmpMeter.qml
+
// AmpMeter.qml
 
import QtQuick
 
import QtQuick
  

Revisió del 14:48, 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 un Rectangle anomenat root 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 element Image 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 element Image 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 RotationAnimations:
      • 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é ListElements, 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
            }
        }
    }
}