LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 1 8.7 (Caso di studio facoltativo) Pensare a oggetti: animazioni e suoni nella vista Questo caso di studio si è focalizzato principalmente sul modello MVC della nostra simulazione di ascensore. Ora che abbiamo completato la progettazione del modello, ci occupiamo della vista, che fornisce la presentazione visiva del modello. Nel nostro caso di studio, la vista, incapsulata nella classe ElevatorView, è un oggetto JPanel contenente altri oggetti JPanel “figli”, ognuno rappresentante un oggetto unico del modello MVC (per esempio, un oggetto Person, un oggetto Button, ecc.). La classe ElevatorView è la classe più grande nel caso di studio. In questa sezione, discutiamo le classi per la grafica e il suono usate da ElevatorView. Presenteremo e spiegheremo il resto del codice sul sito Web www.apogeonline.com/libri/ 02097/allegati/. Nella sezione 3.7 del volume Tecniche di base, abbiamo costruito il diagramma di classe del nostro modello individuando sostantivi e frasi dalla specifica del problema. Abbiamo ignorato parecchi di questi sostantivi, perché non erano associati con il modello MVC. Ora, elenchiamo i sostantivi e le frasi che sono relativi alla visualizzazione del modello MVC: • • • visualizzazione audio musica dell’ascensore. Il sostantivo “visualizzazione” corrisponde alla vista, o alla presentazione visuale, del modello MVC. Come descritto nella sezione 2.7, la classe ElevatorView aggrega diverse classi. Il sostantivo “audio” si riferisce agli effetti sonori che la nostra simulazione genera quando accadono varie azioni; creeremo la classe SoundEffects per generare tali effetti sonori. La frase “musica dell’ascensore” si riferisce alla musica che viene riprodotta quando una persona viaggia sull’ascensore; useremo la classe SoundEffects anche per riprodurre questa musica. La vista visualizza gli oggetti del modello MVC. Creeremo la classe ImagePanel per rappresentare gli oggetti stazionari del modello, come ElevatorShaft. Creeremo la classe MovingPanel, che estende ImagePanel, per rappresentare gli oggetti in movimento, come Elevator. Infine, creeremo la classe AnimatedPanel, che estende MovingPanel, per rappresentare gli oggetti in movimento la cui immagine corrispondente cambia continuamente, come Person (useremo diverse immagini per mostrare la persona che cammina e preme un pulsante). Usando queste classi, presentiamo il diagramma di classe della vista per la nostra simulazione nella figura 8.5. Le note indicano i ruoli che hanno le classi nel sistema. Secondo il diagramma di classe, la classe ElevatorView rappresenta la vista, le classi ImagePanel, MovingPanel e AnimatedPanel si riferiscono alla grafica e la classe SoundEffects si riferisce ai suoni. La classe ElevatorView contiene diverse istanze delle classi ImagePanel, MovingPanel e AnimatedPanel e un’istanza della classe SoundEffects. Nella versione finale del caso di studio, assoceremo ogni oggetto del modello con una classe corrispondente nella vista. In questa sezione, discutiamo le classi ImagePanel, MovingPanel e AnimatedPanel per spiegare la grafica e le animazioni. Poi, discuteremo la classe SoundEffects per spiegare le funzionalità audio. 2 CAPITOLO 8 javax.swing.JPanel ImagePanel 1..* 1 ElevatorView MovingPanel 1..* 1 1 AnimatedPanel Grafica Figura 8.5 Audio 1 1..* 1 SoundEffects Vista Diagramma di classe della vista del simulatore di ascensore ImagePanel La classe ElevatorView usa oggetti di sottoclassi di JPanel per rappresentare e visualizzare ogni oggetto nel modello (come Elevator, Person, ecc.). La classe ImagePanel (figura 8.6) è una sottoclasse di JPanel in grado di visualizzare un’immagine in una data posizione dello schermo. La classe ElevatorView usa oggetti ImagePanel per rappresentare oggetti immobili nel modello, come ElevatorShaft e i due Floor. La classe ImagePanel contiene un attributo intero, ID (riga 16), che dichiara un identificatore unico per tenere traccia dell’ImagePanel nella vista. Ciò è utile quando diversi oggetti della stessa classe sono presenti nel modello, come diversi oggetti Person. La classe ImagePanel contiene l’oggetto position della classe Point2D.Double (riga 19) per rappresentare la sua posizione sullo schermo. Vedremo più avanti che MovingPanel, che estende ImagePanel, dichiara la velocità con valori double. Convertiremo le coordinate position in valori int per posizionare l’oggetto ImagePanel sullo schermo (Java rappresenta le coordinate dello schermo come interi) nel metodo setPosition (righe 90-94). La classe ImagePanel contiene anche un oggetto ImageIcon chiamato imageIcon (riga 22), il cui metodo paintComponent (righe 54-60) visualizza l’icona sullo schermo. Le righe 41-42 inizializzano ImageIcon usando un parametro stringa che contiene il nome dell’immagine. Infine, la classe ImagePanel contiene l’oggetto panelChildren della classe Set (riga 25) che memorizza tutti gli oggetti JPanel figli (o oggetti di una sottoclasse di JPanel). Gli oggetti figli vengono visualizzati sopra il loro ImagePanel genitore: per esempio, un oggetto Person dentro un Elevator. Il primo metodo add (righe 63-67) aggiunge un oggetto a panelChildren. Il secondo metodo add (righe 70-74) inserisce un oggetto in panelChildren all’indice specificato. Il metodo setIcon (righe 84-87) imposta imageIcon con una nuova immagine. Gli oggetti della classe AnimatedPanel usano ripetutamente il metodo setIcon per cambiare l’immagine visualizzata, in modo da eseguire l’animazione. LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Figura 8.6 3 // ImagePanel.java // sottoclasse di JPanel per posizionare e visualizzare ImageIcon package com.deitel.jhtp5.elevator.view; // package Java di base Java import java.awt.*; import java.awt.geom.*; import java.util.*; // package Java di estensione import javax.swing.*; public class ImagePanel extends JPanel { // identificatore private int ID; // posizione sullo schermo private Point2D.Double position; // imageIcon da disegnare sullo schermo private ImageIcon imageIcon; // memorizza tutti i figli di ImagePanel private Set panelChildren; // costruttore che inizializza posizione e immagine public ImagePanel( int identifier, String imageName ) { super( null ); // specifica layout null setOpaque( false ); // rende trasparente // imposta identificatore unico ID = identifier; // imposta posizione position = new Point2D.Double( 0, 0 ); setLocation( 0, 0 ); // crea ImageIcon con imageName dato imageIcon = new ImageIcon( getClass().getResource( imageName ) ); Image image = imageIcon.getImage(); setSize( image.getWidth( this ), image.getHeight( this ) ); // crea Set per memorizzare i figli di Panel La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello (continua) 4 CAPITOLO 8 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 Figura 8.6 panelChildren = new HashSet(); } // fine costruttore di ImagePanel // disegna Panel sullo schermo public void paintComponent( Graphics g ) { super.paintComponent( g ); // se l’immagine è pronta, disegnala sullo schermo imageIcon.paintIcon( this, g, 0, 0 ); } // aggiunge figlio di ImagePanel a ImagePanel public void add( ImagePanel panel ) { panelChildren.add( panel ); super.add( panel ); } // aggiunge figlio di ImagePanel a ImagePanel all’indice dato public void add( ImagePanel panel, int index ) { panelChildren.add( panel ); super.add( panel, index ); } // rimuove figlio di ImagePanel da ImagePanel public void remove( ImagePanel panel ) { panelChildren.remove( panel ); super.remove( panel ); } // imposta ImageIcon corrente da visualizzare public void setIcon( ImageIcon icon ) { imageIcon = icon; } // imposta posizione sullo schermo public void setPosition( double x, double y ) { position.setLocation( x, y ); setLocation( ( int ) x, ( int ) y ); } // ritorna identificatore di ImagePanel La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello (continua) LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 Figura 8.6 5 public int getID() { return ID; } // ottiene posizione di ImagePanel public Point2D.Double getPosition() { return position; } // ottiene imageIcon public ImageIcon getImageIcon() { return imageIcon; } // ottiene Set di figli di ImagePanel public Set getChildren() { return panelChildren; } } La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello MovingPanel La classe MovingPanel (figura 8.7) è una sottoclasse di ImagePanel in grado di cambiare la sua posizione sullo schermo in base ai valori xVelocity e yVelocity (righe 20-21). La classe ElevatorView usa oggetti MovingPanel per rappresentare oggetti mobili del modello, come Elevator. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Figura 8.7 // MovingPanel.java // Sottoclasse di JPanel con funzionalità di movimento sullo schermo package com.deitel.jhtp5.elevator.view; // package Java di base import java.awt.*; import java.awt.geom.*; import java.util.*; // package Java di estensione import javax.swing.*; public class MovingPanel extends ImagePanel { La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello (continua) 6 CAPITOLO 8 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 Figura 8.7 // MovingPanel dovrebbe cambiare posizione? private boolean moving; // numero di pixel di cui MovingPanel si muove nelle direzioni x e y // per animationDelay millisecondi private double xVelocity; private double yVelocity; // costruttore che inizializza posizione, velocità e immagine public MovingPanel( int identifier, String imageName ) { super( identifier, imageName ); // imposta velocità di MovingPanel xVelocity = 0; yVelocity = 0; } // fine costruttore di MovingPanel // aggiorna posizione e animazione di MovingPanel public void animate() { // aggiorna posizione a seconda della velocità if ( isMoving() ) { double oldXPosition = getPosition().getX(); double oldYPosition = getPosition().getY(); setPosition( oldXPosition + xVelocity, oldYPosition + yVelocity ); } // aggiorna tutti i figli di MovingPanel Iterator iterator = getChildren().iterator(); while ( iterator.hasNext() ) { MovingPanel panel = ( MovingPanel ) iterator.next(); panel.animate(); } } // fine metodo animate // MovingPanel si sta muovendo sullo schermo? public boolean isMoving() { return moving; } // imposta MovingPanel per muoversi sullo schermo public void setMoving( boolean move ) La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello (continua) LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 Figura 8.7 7 { moving = move; } // imposta le velocità x e y di MovingPanel public void setVelocity( double x, double y ) { xVelocity = x; yVelocity = y; } // ritorna velocità x di MovingPanel public double getXVelocity() { return xVelocity; } // ritorna velocità y di MovingPanel public double getYVelocity() { return yVelocity; } } La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello Il metodo animate (righe 35-53) muove MovingPanel secondo i valori attuali dei campi e yVelocity. Se la variabile boolean moving (riga 16) è true, le righe 38-44 usano i campi xVelocity e yVelocity per determinare la prossima posizione di movingPanel. Le righe 47-52 ripetono il procedimento per tutti i figli. Nella nostra simulazione, ElevatorView invoca il metodo animate e il metodo paintComponent della classe ImagePanel ogni 50 millisecondi. Queste chiamate in rapida successione spostano l’oggetto MovingPanel. xVelocity AnimatedPanel La classe AnimatedPanel (figura 8.8), che estende la classe MovingPanel, rappresenta un oggetto animato del modello (cioè, oggetti in movimento la cui immagine corrispondente cambia continuamente), come Person. La classe ElevatorView anima un AnimatedPanel cambiando l’immagine associata con imageIcon. 1 2 3 4 5 6 7 Figura 8.8 // AnimatedPanel.java // Sottoclasse di Panel con funzionalità di animazione package com.deitel.jhtp5.elevator.view; // package Java di base import java.awt.*; import java.util.*; La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 8 CAPITOLO 8 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Figura 8.8 // package Java di estensione import javax.swing.*; public class AnimatedPanel extends MovingPanel { // ImageIcon deve essere animato? private boolean animating; // frequenza di cambio immagine private int animationRate; private int animationRateCounter; private boolean cycleForward = true; // ImageIcon individuali usati per l’animazione private ImageIcon imageIcons[]; // memorizza tutte le sequenze di frame private java.util.List frameSequences; private int currentAnimation; // deve continuare l’animazione alla fine del ciclo? private boolean loop; // deve visualizzare l’ultima immagine alla fine dell’animazione? private boolean displayLastFrame; // determina la prossima immagine da visualizzare private int currentFrameCounter; // costruttore che prende un array di nomi di file public AnimatedPanel( int identifier, String imageName[] ) { super( identifier, imageName[0] ); // crea oggetti ImageIcon dall’array di stringhe imageName imageIcons = new ImageIcon[ imageName.length ]; for ( int i = 0; i < imageIcons.length; i++ ) { imageIcons[i] = new ImageIcon( getClass().getResource( imageName[i] ) ); } frameSequences = new ArrayList(); } // fine costruttore di AnimatedPanel // aggiorna posizione dell’icona e immagine dell’animazione public void animate() La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 Figura 8.8 { super.animate(); // visualizza la prossima immagine se counter > frequenza di animazione if ( frameSequences != null && isAnimating() ) { if ( animationRateCounter > animationRate ) { animationRateCounter = 0; determineNextFrame(); } else animationRateCounter++; } } // fine metodo animate // determina prossima immagine dell’animazione private void determineNextFrame() { int frameSequence[] = ( int[] ) frameSequences.get( currentAnimation ); // se non ci sono più immagini, determina immagine finale // a meno che non si debba ripetere l’animazione if ( currentFrameCounter >= frameSequence.length ) { currentFrameCounter = 0; // se loop è false, termina animazione if ( !isLoop() ) { setAnimating( false ); if ( isDisplayLastFrame() ) // visualizza ultima immagine della sequenza currentFrameCounter = frameSequence.length - 1; } } // imposta immagine corrente dell’animazione setCurrentFrame( frameSequence[ currentFrameCounter ] ); currentFrameCounter++; } // fine metodo determineNextFrame // aggiunge animazione all’ArrayList frameSequences public void addFrameSequence( int frameSequence[] ) { frameSequences.add( frameSequence ); La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 9 10 CAPITOLO 8 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 Figura 8.8 } // chiede se AnimatedPanel sta eseguendo l’animazione public boolean isAnimating() { return animating; } // imposta AnimatedPanel per eseguire l’animazione public void setAnimating( boolean animate ) { animating = animate; } // imposta ImageIcon corrente public void setCurrentFrame( int frame ) { setIcon( imageIcons[ frame ] ); } // imposta frequenza di animazione public void setAnimationRate( int rate ) { animationRate = rate; } // ottiene frequenza di animazione public int getAnimationRate() { return animationRate; } // imposta se l’animazione deve ripetersi public void setLoop( boolean loopAnimation { loop = loopAnimation; } ) // ottiene se l’animazione deve ripetersi public boolean isLoop() { return loop; } // ottiene se visualizzare l’ultima immagine alla fine dell’animazione private boolean isDisplayLastFrame() La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 151 152 153 154 155 11 { return displayLastFrame; } // imposta se visualizzare l’ultima immagine alla fine dell’animazione public void setDisplayLastFrame( boolean displayFrame ) { displayLastFrame = displayFrame; } 156 157 158 159 160 161 162 163 164 165 166 167 168 // inizia ad eseguire la sequenza di animazione all’indice dato public void playAnimation( int frameSequence ) { currentAnimation = frameSequence; currentFrameCounter = 0; setAnimating( true ); } } Figura 8.8 La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello La classe AnimatedPanel sceglie un oggetto ImageIcon da disegnare sullo schermo tra diversi oggetti ImageIcon memorizzati nell’array imageIcons (riga 23). La classe AnimatedPanel determina l’oggetto ImageIcon secondo una sequenza di riferimenti, memorizzata nella lista frameSequences (riga 26), che è un array di interi che memorizza la sequenza appropriata per visualizzare gli oggetti ImageIcon: in particolare, ogni intero rappresenta l’indice di un oggetto ImageIcon nell’array imageIcons. La figura 8.9 mostra la relazione tra imageIcons e frameSequences (questo non è un diagramma UML). Per esempio, la sequenza numero 2 = { 2, 1, 0 } si riferisce a { imageIcon[2], imageIcon[1], imageIcon[0] }, che porta alla sequenza di immagini { C, B, A }. Nella vista, ogni immagine è un file .png unico. Il metodo addFrameSequences (righe 102-105) aggiunge una sequenza di animazione alla lista frameSequences. Il metodo playAnimation (righe 162-167) fa partire l’animazione asso- frameSequences imageIcons A B C D 0 1 2 3 Figura 8.9 0= 0 1 2 1= 0 1 3 2= 2 1 0 3= 3 2 2 1 0 0 image sequences A B C A B D C B A D C C Relazione tra l’array imageIcons e la lista FrameSequences B A A 12 CAPITOLO 8 ciata con il parametro frameSequences. Per esempio, supponiamo di avere un oggetto AnimatedPanel chiamato personAnimatedPanel nella classe ElevatorView. Il frammento di codice animatedPanel.playAnimation( 1 ); genererebbe la sequenza di immagini { A, B, D, B, A } se usiamo la figura 8.9 come riferimento. Il metodo animate (righe 56-70) sovrascrive il metodo animate della superclasse MovingPanel. Le righe 61-69 determinano la successiva immagine per l’animazione a seconda del campo animationRate, che è inversamente proporzionale alla velocità dell’animazione: un valore più alto per animationRate comporta un’animazione più lenta. Per esempio, se animationRate vale 5, animate si sposta alla successiva immagine dell’animazione ogni cinque volte viene invocato. Usando questa logica, la frequenza di animazione viene massimizzata quando animationRate ha un valore 1, perché la successiva immagine viene determinata ogni volta che viene chiamato animate. Il metodo animate chiama determineNextFrame (righe 73-99) per determinare la successiva immagine da visualizzare: in particolare, viene chiamato il metodo setCurrentFrame (righe 120-123) che imposta imageIcon (l’immagine correntemente visualizzata) all’immagine ritornata dalla corrente sequenza di animazione. Le righe 84-92 di determineNextFrame vengono usate per ripetere l’animazione. Se loop vale false, l’animazione termina dopo una iterazione. L’ultima immagine della sequenza viene visualizzata se displayLastFrame vale true, e, se vale false, viene visualizzata la prima immagine della sequenza. Se loop vale true, l’animazione si ripete finché non viene fermata esplicitamente. Effetti sonori Vediamo ora come generare dei suoni nella nostra simulazione. La classe SoundEffects (figura 8.10) trasforma file audio (.au), wave (.wav) e MIDI (.mid) contenenti suoni come il campanello, i passi della persona e la musica dell’ascensore, in oggetti java.applet.AudioClip. L’oggetto ElevatorView riprodurrà gli oggetti AudioClip per generare i suoni. Tutti i file dei suoni sono nella struttura di directory com/deitel/jhtp5/elevator/view/sounds Nella nostra simulazione, useremo suoni e file MIDI disponibili gratuitamente al sito Web di Microsoft: msdn.microsoft.com/downloads/default.asp Per scaricare questi suoni, fate clic su “Graphics and Multimedia”, “Multimedia (General)”, e poi “Sounds”. La classe SoundEffects contiene i metodi getAudioClip (righe 16-27), che usa il metodo statico newAudioClip (della classe java.applet.Applet) per ritornare un oggetto AudioClip usando il parametro soundFile. Il metodo setPathPrefix (righe 30-33) permette di cambiare directory del file audio (utile se vogliamo dividere i nostri file sonori tra directory diverse). LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Figura 8.10 13 // SoundEffects.java // Ritorna oggetti AudioClip package com.deitel.jhtp5.elevator.view; // package Java di base import java.applet.*; public class SoundEffects { // posizione dei file audio private String prefix = “”; public SoundEffects() {} // ottiene AudioClip associato con soundFile public AudioClip getAudioClip( String soundFile ) { try { return Applet.newAudioClip( getClass().getResource( prefix + soundFile ) ); } // ritorna null se il file audio non esiste catch ( NullPointerException nullPointerException ) { return null; } } // imposta prefisso per la posizione di soundFile public void setPathPrefix( String string ) { prefix = string; } } La classe SoundEffects ritorna oggetti AudioClip Conclusione Avete appena completato un procedimento di progettazione orientata agli oggetti che aveva lo scopo di prepararvi per le sfide dei progetti di livello industriale. Speriamo che abbiate trovato le sezioni “Pensare a oggetti” informative e utili come complemento al materiale presentato nei vari capitoli. Inoltre, speriamo vi siate divertiti a progettare il sistema usando UML. Il linguaggio UML è stato adottato come standard dall’industria del software mondiale per la modellazione di software orientato agli oggetti. Anche se avete completato la fase di progettazione, avete solo sfiorato il processo di implementazione. Consultate il sito Web www.apogeonline.com/libri/02097/allegati/ per la completa implementazione del progetto, e le traduzioni dei diagrammi UML in un programma Java completo per la simulazione dell’ascensore. Studiare l’implementazione rafforzerà le abilità nella programmazione che avete sviluppato leggendo il libro e migliorerà la vostra comprensione del processo di progettazione.