Programare Java - 8
FOLOSIREA IMAGINILOR , ANIMATIEI SI SUNETULUI
Animatia in Java se realizeaza prin folosirea AWT - mai precis a unor componente ale sale . Putem crea animatie si folosind instructiunile de desenare invatate in cursurile anterioare dar la acest moment vom trece la folosirea unor imagini deja create - sub forma de fisiere grafice cu diverse extensii .
Avem de facut practic doi pasi conceptuali pentru a realiza o animatie :
desenarea
comandarea sistemului de ferestre pentru a afisa desenul realizat anterior
Daca repetam acesti pasi se reuseste crearea teoretica a unei miscari .
DESENAREA SI REIMPROSPATAREA DESENULUI
In mod normal metoda paint() care deseneaza fereastra unui applet este apelata automat de Java ; pentru un control mai bun al afisarii avem si posibilitatea de a apela chiar noi redesenarea prin metoda repaint() .
Deci pentru a modifica o fereastra applet trebuie sa desenam ceva apoi sa apelam repaint() pentru a vedea rezultatul .
Operatiile acestea de desenare nu vor fi create in metoda paint() deoarece s-ar executa toate direct de la inceputul afisarii ferestrei .
PORNIREA SI OPRIREA EXECUTIEI APPLETULUI
Pentru aceste lucruri exista metodele start() si stop() ale clasei Applet . Aceste metode sunt vide si va trebui sa le suprascriem atunci cand incepem sau finalizam programul . Desi la desenarea unor lucruri simple aceste metode nu erau necesare , pentru animatie situatia se schimba .
CONTROLUL PRIN FIRE DE EXECUTIE
Firele de executie , numite si thread-uri , sunt un lucru foarte important pentru animatie - ele dau posibilitatea tratarii in paralel de Java a mai multor activitati .
Un fir de executie este o parte a unui program care este configurata sa ruleze in timp ce restul programului executa altceva .
Prin separarea ainmatiei pe un fir de executie restul aplicatiei poate rula altceva .
Odata cu utilizarea firelor de executie trebuie sa facem unele modificari in fisierul nostru clasa :
adaugam la declarare "implements Runnable"
se creaza un obiect Thread in care pastram firul
suprascriem metoda start() a applet-ului pentru a crea si lansa firul de executie
suprascriem metoda stop() pentru a seta firul care se executa la null
cream metoda run() care contine instructiunile ce fac appletul sa ruleze continuu animatia
Runnable este o interfata ; ea reprezinta sistemul prin care o clasa poate mosteni metode pe care altfel nu le-ar fi mostenit de la superclasele sale . Aceste metode pot fi astfel disponibile oricarei metode care are nevoie de ele . Runnable contine o metode run() de care avem nevoie pentru a porni un fir de executie si de aceea trebuie implementata aceasta interfata in cazul 444g67e nostru .
Thread este o clasa din pachetul java.lang - asa incat nu avem nevoie de o instructiune import pentru a o utiliza . De obicei obiectul de tip Thread se creaza in metoda start() si va avea o valoare null pana la crearea efectiva a obiectului - creare care se face tot in metoda start() . Pentru a rula un fir de executie creat avem nevoie de metoda sa start() :
obiect_thread.start();
Apelarea acestei metode duce la apelarea metodei run() - mostenita prin interfata Runnable .
Metoda run() este cea mai importanta a appletului devenit fir de executie . Ea este folosita pentru a controla secventele animatiei prin stabilirea tuturor operatiilor legate de desenare si de modificarile intre secventele de animatie .
Dupa definirea in metoda run() a comportamentului necesar firului de executie trebuie sa definim si metoda stop() - pentru oprirea appletului .
Acest lucru - oprirea - il putem face prin atribuirea valorii null obiectului Thread ; de fapt acest lucru nu duce la oprirea automata a executie firului dar problema se rezolva prin metoda run() care va fi creata astfel incat sa permita rularea doar daca obiectul Thread nu este null .
Pentru a clarifica problematica firelor de executie vom prezenta un applet care creaza un ceas - o animatie simpla - cu actualizari constante .
In cadrul appletului vom folosi un ciclu while care ar putea fi periculos in conditii normale : acest ciclu ar monopoliza resursele si practic nu am vedea nimic pe ecran deoarece Java ar astepta la infinit oprirea ciclului .
Appletul va defini un anumit font pentru afisare si un obiect de tip Date care pastreaza data curenta . Metodele start() si stop() au rolul de a porni si respectiv opri firul de executie .
Metoda run() este cea mai importanta - cea care realizeaza de fapt toata animatia . Aici vom avea si buclucasul ciclu while de mai sus ; primul lucru facut in ciclu este apelarea repaint() pentru redesenarea ferestrei . Tot aici vom intalni si o noua metoda : sleep() . Aceasta metoda a clasei Thread determina o pauza in executie . Daca nu s-ar folosi aceasta metoda appletul ar rula la viteza maxima , lucru care poate sa nu fie conform cu dorinta programatorului . Instructiunile try si catch vor apare aici pentru tratarea erorilor si pentru moment le putem ignora .
Metoda paint() a appletului creaza o noua instanta a clasei Date - pentru folosirea acestei clase trebuie sa o importam , ea fiind inclusa in java.util . De asemenea apare si metoda toString() a clasei Date , necesara pentru afisarea ca sir a datei si orei . La fiecare apelare a metodei paint() vom avea astfel un nou obiect Date care va tine ora si data curente .
Sa vedem in continuare codul sursa al appletului descris anterior :
import java.awt.*;
import java.util.*;
public class CeasDigital extends java.applet.Applet implements Runnable
}
public void stop()
}
public void run() catch (InterruptedException e)
}
}
public void paint(Graphics ecran)
}
EFECTUL DE FLICKERING AL ANIMATIE
Acest efect - cu denumirea in limba engleza , traducerea fiind palpaire - este cauzat de modul de reimprospatare a fiecarui caddru de animatie . Dupa cum am mai spus : apelul metodei repaint() duce automat la un apel al metodei repaint() .De fapt mai exista inca o metoda intermadiara pe care Java o foloseste pentru a redesena ecranul aplicatiei ; metoda update() - care sterge ecranul prin umplerea cu culoarea de fundal a ferestrei appletului si abia apoi se apeleaza paint() .
Din cauza umplerii ecranului efectuata de metoda update() apare si acest efect de flickering .
In practica exista doua moduri de a evita acest eveniment suparator :
suprascrierea metodei update() astfel incat aceasta sa nu mai stearga ecranul sau sa nu stearga partile de fereastra care nu se vor modifica .
suprascrierea metodelor paint() si update() impreuna cu folosirea dublei memorari ( double buffering ).
Vom prezenta mai intai prima metoda , suprascrierea lui update() - aceasta fiind cea mai simpla ; in multe cazuri insa ea nu este aplicabila la programe mai complexe si va trebui utilizata cea de a doua tehnica de mai sus .
SUPRASCRIEREA METODEI UPDATE()
Implicit aceasta metoda are urmatorul cod sursa :
public void update(Graphics g)
Putem alege o versiune de rezolvare a problemei palpairii prin anularea totala a stergerii ecranului . Aceasta solutie nu poate fi aplicata pe scara larga dar in cazurile in care se poate aplica este cea mai simpla solutie .
Sa luam ca exemplu codul sursa de mai jos :
import java.awt.*;
public class AlternareCulori extends java.applet.Applet implements Runnable
}
public void stop()
public void run()
int i=0;
Thread firExecutie=Thread.currentThread();
while (executabil==firExecutie) catch (InterruptedException e)
if (i==culori.length) i=0;
}
}
public void paint(Graphics ecran)
}
Pentru a intelege cat mai bine appletul sa incercam un comentariu aproape linie cu linie al codului sursa :
Linia 5 defineste o variabila "culori" care desemneaza un tablou cu 50 de elemente . La pornirea appletului metoda run() umple acest tablou cu obiecte Color . Crearea tabloului de culori poate fi realizata si in metoda paint() dar nu ar avea logica , el creandu-se astfel la fiecare apelare a metodei paint() . In realitate este de ajuns crearea sa o singura data , astfel incat e mai bine sa cream tabloul de culori in metoda init() .
Pentru a crea obiecte de culori diferite s-a folosit metoda getHSBColor() ; aceasta este parte a clasei Color si creaza un obiect Color pe baza combinatiei intre nuanta ( hue ) , saturatie ( saturation ) si luminozitate ( brightness ) . Prin incrementarea valorii nuantei putem astfel crea un obiect cu o culoare noua - avantajul crearii in acest fel a obiectelor Color fiind rapiditatea mai mare a limbajului la implementarea acestei metode .
Pentru a se obtine animatia - adica modificarea continua a culorii textului afisat de applet - se utilizeaza un ciclu care strabate tabloul de culori stabilind pe rand fiecare culoare din tablou drept culoare de desenare si apoi reapeleaza metoda repaint() . Datorita conditiei din ciclul while procesul descris mai sus se va relua la infinit pana la oprirea fortata a appletului .
La rularea appletului - e adevarat ca si in functie de hardware-ul disponibil - apare efectul de flicker ; acesta se datoreaza aici faptului ca la fiecare desenare a sirului cu o noua culoare exista si o operatie de stergere totala a ecranului .
Acum vom opera modificarea metodei update() pentru a reduce acest efect suparator . Vom inlatura din metoda partea responsabila cu stergerea ecranului , obtinand o metoda update() cu urmatorul cod sursa :
public void update(Graphics ecran)
Cea de a doua metoda de evitare a flickerului este dubla memorare . Aceasta consta in procesul de a desena un cadru intreg de animatie intr-o zona invizibila inainte de a-l copia in zona vizibila de pe ecran . Zona invizibila in care lucram se numeste buffer ( sau memorie tampon ) .
Prin aceasta tehnica se creaza practic inca o suprafata de desenare , se fac toate operatiunile de desenare pe ea dupa care se deseneaza dintr-o data intreaga suprafata in fereastra principala a appletului - toate acestea in loc sa se deseneze direct in fereastra appletului , pe rand fiecare element .
Tehnica dublei memorari este destulde performanta , ea reusind sa elimine practic flickerul dar aduce dezavantajul unei folosiri intensive a memoriei sistemului de calcul . Pentru a crea un applet bazat pe aceasta tehnica trebuie sa avem o imagine pe care o desenam in buffer si un context grafic pentru acea imagine . Acestea vor simula efectul desenarii pe suprafata appletului : contextul grafic ( de fapt o instanta a clasei Graphics ) care ofera metodele de desen si obiectul Image , care memoreaza ceea ce se va desena .
Ca sa reusim implementarea corect a tehnicii de mai sus trebuie sa parcurgem patru etape .
Mai intai imaginea invizibila si contextul grafic trebuie stocate in variabile de instanta care vor putea apoi fi trimise metodei paint() . Acest lucru se face sintactic la modul urmator :
Image imagineInvizibila;
Graphics invizibil;
In al doilea rand pe parcursul initializarii appletului vom crea obiecte Image si Graphics pe care le vom atribui acestor variabile . Metoda createImage() returneaza o instanta a clasei Image , pe care apoi o putem transmite metodei getGraphics() pentru a obtine un nou context pentru imaginea respectiva :
imagineInvizibila = createImage(size().width , size().height);
invizibil=imagineInvizibila.getGraphics();
In acest moment , ori de cate ori va trebui sa desenam pe ecran - cu metoda paint() - vom desena in contextul grafic invizibil ; ca exemplu , pentru a desena o imagine numita img la pozitia 100,100 folosim linia de cod :
invizibil.drawImage(img,100,100,this);
In ceea ce priveste cuvantul cheie this folosit aici nu va faceti probleme pentru ceea ce reprezinta - va fi prezentat mai detaliat in paginile urmatoare .
Ultima etapa , dupa ce s-a terminat de desenat totul in contextul invizibil , instructiunea urmatoare copiaza zona tampon invizibila pe ecran :
ecran.drawImage(imagineInvizibila,0,0,this);
Pentru a elimina si umplerea ecranului cu culoarea de fond ( operatia default a metodei update() ) este indicat sa suprascriem si metoda update() la modul urmator :
public void update(Graphics g)
Pentru a realiza in practica implementarea dublei memorari vom prezenta un exemplu de applet care deseneaza si misca un cer intre doua patrate colorate diferit - appletul se numeste "Dame" , dupa asemanarea rezultatului vizual cu o piesa din acest joc :
import java.awt.*;
public class Dame extends java.applet.Applet implements Runnable
public void start()
}
public void stop()
public void run()
repaint();
try catch (InterruptedException e)
}
}
public void update(Graphics ecran)
public void paint(Graphics ecran)
public void destroy()
}
Sa comentam putin programul de mai sus :
variabila pozX este folosita pentru a muta cercul dintr-un loc in altul , ea pastrand coordonatele unde se afla piesa la un moment dat . Valoarea variabilei se modifica continuu in metoda run() .
primul pas pentru dubla memorare consta in crearea unui obiect Image care sa pastreze cadrul invizibil pana cand acesta este complet si a unui obiect Graphics care sa permita desenarea in aceasta zona invizibila . Acest lucru il fac liniile :
Image imgInvizibila;
Graphics invizibil;
celor doua variabile amintite in randurile precedente li se atribuie obiecte create in metoda init() a appletului :
public void init()
metoda paint() este modificata pentru a scrie zona tampon invizibila in locul obiectului principal Graphics ; practic , doar ultima linie a metodei paint() afiseaza in fereastra appletului . Aceasta instructiune afiseaza cadrul de animatie complet la coordonatele 0,0 . Deoarece obiectul imgInvizibil a fost creat de aceeasi dimensiune ca zona de ecran el va acoperi complet fereastra appletului .
DISTRUGEREA CONTEXTELOR GRAPHICS
Ati remarcat in finalul exemplului de mai sus o metoda pe care nu am detaliat-o :
public void destroy()
Pe scurt , aceasta metoda distruge obiectul invizibil .
Sa detaliem totusi putin problema !
Chiar daca recuperatorul de memorie Java functioneaza automat si distruge obiectele atunci cand nu mai este nevoie de ele in program acest lucru nu este valabil si pentru obiectele Java care sunt create pentru a gestiona memoriile tampon din afara ecranului .Obiectele ramase orfane si nefolosite ocupa astfel memorie si inrautatesc performantele Java ; pentru a rezolva acest neajuns ar trebui sa folosim explicit metoda dispose() a clasei Graphics pentru a distruge aceste obiecte atunci cand am terminat treaba cu ele .
Locul cel mai potrivit pentru a face acest lucru este metoda destroy() a appletului - metoda introdusa sporadic pe parcursul unui curs anterior .
Metoda destroy() este apelata fara argumente , dupa modelul sintactic de mai jos :
public void destroy()
INCARCAREA SI FOLOSIREA IMAGINILOR
Lucrul cu imagini in Java se realizeaza in principal prin intermediul clasei Image , inclusa in pachetul java.awt . Cand lucram cu un applet vom folosi pentru incarcare si afisare a imaginilor metode ale claselor Applet si Graphics .
Pentru a afisa o imagine in appletul nostru trebuie intai sa o incarca in program din World Wide Web . Imaginile se vor pastra in fisiere grafice separate de fisierele sursa si compilate Java , asa ca trebuie specificat clar locul in care le putem gasi . Cand folosim clasa Image fisierele grafice pe care le utilizam trebuie sa fie de tip .GIF sau .JPG .
O adresa web este reprezentata printr-un obiect URL . Clasa URL face parte din pachetul java.net care va trebui deci importat pentru a-l pune la dispozitia programului nostru .
Obiectul URL este creat prin transmiterea adresei paginii web ca argument pentru metoda constructor a clasei URL , ca in exemplul de mai jos :
URL u=new URL (https://www.site.com/imagini/imagine1.gif);
Dupa ce am creat obiectul URL il putem folosi pentru a crea un obiect Image care reprezinta propriu-zis fisierul grafic .
Pentru a incarca o imagine noua intr-un obiect Image clasa Applet contine o metoda numita getImage() , care poate fi folosita in doua moduri :
cu un singur argument - obiect URL , localizandu-se imaginea de la adresa exacta
cu doua argumente : adresa URL de baza ca obiect URL si un sir care reprezinta calea relativa sau denumirea fisierului care contine imaginea .
Ultima metoda este putin mai complicata dar ofera o mai mare flexibilitate .
Clasa Applet poseda doua metode care pot fi folosite pentru a crea o adresa URL de baza fara a folosi in program o adresa fixa explicita ( lucru care ar face ca la orice modificare a adresei necesitate de applet sa fie necesara si o recompilare a appletului ) :
metoda getDocumentbase() returneaza obiectul URL care reprezinta directorul ce contine pagina web care prezinta appletul
metoda getCodeBase() care returneaza obiectul URL care reprezinta directorul unde se afla fisierul cu clasa principala a appletului .
Calea relativa catre o resursa se foloseste ca al doilea argument pentru metoda getImage() si se modifica in functie de ce s-a folosit in primul argument .
Sa luam un exemplu cu o pagina web cu adresa : https://www.site.com/index.html , care incarca o imagine din acelasi director , imagine numita imagine1.gif . Pentru a folosi aceasta imagine in appletul nostru ne trebuie o instructiune de genul :
Image imagine=getImage(getDocumentBase(),"imagine1.gif");
Practic , folosirea metodelor getDocumentBase() si getCodeBase() depinde de locul in care avem fisierele grafice : in subdirectoarele appletului Java sau in subdirectoarele paginii web care apeleaza appletul . Datorita folosirii acestor metode putem reloca pagina web cu tot cu applet fara a aparea probleme legate de eventuala necesitate de a recompila clasele Java .
DESENAREA IMAGINILOR
Dupa ce am pus o imagine intr-un obiect Image aceasta poat fi afisata in applet cu metoda drawImage() a clasei Graphics . Pentru a afisa o imagine la dimensiunile reale vom apela metoda cu patru argumente :
obiectul Image pentru afisare
coordonatele x si y ale coltului din stanga sus ale locului unde vrem sa afisam imaginea
cuvantul cheie this
Daca fisierul grafic trebuie afisat la o alta scara decat originalul trebuie sa folosim sase argumente pentru metoda drawImage() :
obiectul Image de afisat
coordonatele x si y ale imaginii
latime imaginii in pixeli
inaltimea imaginii in pixeli
cuvantul cheie this
Scalarea imaginii are efect doar pentru afisarea in applet , obiectul propriu-zis nefiind alterat de aceste apeluri de metoda .
Pentru a afla dimensiunile unei imagini afisate avem la dipsozitie metodele getHeight() si getWidth() care returneaza inaltimea si respectiv latimea imaginii afisate .
Ultiimul argument al metodei drawImage este cuvantul cheie this - element folosit in general intr-un obiect pentru a face o referinta la el insusi .
Folosirea sa in acest context este necesara pentru a identifica un applet care poate urmari incarcarea imaginii de pe web . Incarcarea imaginii este urmarita prin intermediul unei interfete ImageObserver . Clasele care implementeaza aceasta interfata - printre care si Applet - pot observa gradul de incarcare al unei imagini . Acest lucru poate fi folositor de exemplu pentru un program care sa afiseze altceva in timpul incarcarii unor imagini ( procese care uneori pot dura destul de mult ) .
In continuare vom prezenta un exemplu de afisare a unor imagini la scara originala si cu dimensiuni marite :
import java.awt.*;
public class Imagine extends java.applet.Applet
public void paint(Graphics ecran)
}
Appletul de mai sus presupune ca dispunem de un fisier grafic numit poza1.gif , pe care dorim sa-l afisam mai intai la dimensiunile sale reale si apoi cu latime si inaltimea de patru ori mai mari .
Variabila xPoz contine valoarea coordonatei x a locului unde se doreste inceperea afisarii imaginii .
FOLOSIREA SUNETULUI
Clasa Applet are posibilitatea de a reda si sunet . De la
versiunea 2 a limbajului Java putem folosi fisiere audio in mai multe formate :
AIFF , AU ,
Cea mai simpla metoda de incarcare si redare a unui sunet este prin utilizarea metodei play() a clasei Applet . Aceasta metoda are doua forme de apelare :
cu un argument - obiect URL , va incarca si reda fisierul audio de la adresa servita ca argument
cu doua argumente - a adresa URL de baza si o cale directoare . Primul argument este de cele mai multe ori un apel al metodei getDocumentBase() sau getCodeBase() .
Instructiunea de mai jos incarca si reda un fisier audio numit sunet.wav , aflat in acelasi director cu appletul :
play(getCodeBase(),"sunet.wav");
Metoda play() incarca si reda sunetul cat mai repede posibil . In cazul in care fisierul de sunet nu este disponibil la adresa servita metodei play() nu vom obtine nici un mesaj de eroare - pur si simplu nu se va auzi nici un sunet !
Avem posibilitatea de a reda continuu un sunet , de a-l porni si a-l opri la dorinta . Acesta lucru se poate face incarcand fisierul audio intr-un obiect AudioClip , folosind metoda getAudioClip a acestuia . Clasa AudioClip este inclusa in pachetul java.awt .
Metoda getAudioClip() primeste unul sau doua argumente . Primul argument ( care poate fi si unic ) este un obiect URL care identifica fisierul de sunet iar al doilea poate fi o referinta la cale .
In exemplul de mai jos putem vedea cum se incarca un fisier audio - "sunet.wav" , aflat intr subdirectorul "audio" al appletului - intr-un obiect AudioClip :
AudioClip clip=getAudioClip(getCodeBase(),"audio/sunet.wav");
Metoda getAudioClip() poate fi apelata numai in cadrul unui applet . Pentru incarcarea unui fisier audio intr-o aplicatie independenta Java trebuie sa folosim metoda newAudioClip() a clasei Applet :
AudioClip clip=newAudioClip("audio/sunet.wav");
Odata creat obiectul AudioClip putem apela si metodele play() , stop() sau loop() ale acestuia . Play() reda sunetul , stop() opreste redarea sunetului iar loop() provoaca redarea continuu a fisierului audio .
Spre deosebire de apelarea simpla play() pentru un anumit fisier de sunet ( caz in care inexistenta fisierului audio nu provoaca nici o eroare ) folosirea metodelor getAudioClip() sau newAudioClip() poate duce la erori in cazul in care fisierul de sunet indicat de argumente nu exista ; acest lucru se datoreaza faptului ca obiectul AudioClip creat de noi va avea valoarea null , iar redarea unui obiect null produce o eroare .
In cazul in care vrem sa redam mai multe sunete simultan nu exista nici o problema - vom folosi mai multe fire de executie .
Trebuie mentionata si o problema - in cazul in care utilizam o redare continuua a unui sunet in appletul nostru oprirea firului de executie al appletului nu va duce si la oprirea automata a sunetului . In practica daca un utilizator va trece in alta pagina web sunetul va continua sa se auda ! Rezolvarea acestei probleme se face prin utilizarea metodei stop() pentru sunetul redat in acelasi timp cu oprirea firului de executie al appletului .
In continuare avem un exemplu care reda continuu un sunet - sunet1.wav - si o data la fiecare 5 secunde reda si fisierul sunet2.wav :
import java.awt.*;
import java.applet.*;
public class CicluAudio extends java.applet.Applet implements Runnable {
AudioClip sunetFundal;
AudioClip bip;
Thread executabil;
public void start()
}
public void stop()
}
public void init()
public void run() catch (InterruptedException e)
if (bip!=null) bip.play();
}
}
public void paint(Graphics ecran)
}
|