|
|
|
Aquesta pràctica ens ensenyarà
la tècnica del doble buffering,
essencial per a moviments i animacions.
De passada, tindrem un petit primer contacte amb la classe
java.lang.Math. |
|
|
|
Navegant per un canvas a cop de
ratolí... |
|
|
|
La navegació
per un component, per un canvas
en el nostre cas, pot fer-se amb el ratolí,
d'una altra manera que la que proposa la pràctica
anterior. Es tracta d'arrossegar el
canvas, per tal que mostri tot allò
que conté. |
|
|
|
- Això implica que el canvas
ha de ser listener del ratolí
(vegeu la pràctica 2) i, per tant,
ha d'implementar les interfaces
java.awt.MouseListener i java.awt.MouseMotionListener
i, en conseqüència, tots els mètodes
d'aquestes interfaces, encara que alguns
no es facin servir.
- En el codi següent
(projecte Mousing)
en el qual hem marcat en groc clar les diferències amb el projecte
Scrolling, última versió, de
la pràctica anterior,
/*
* @(#)Mousing.java 1.0 02/11/02
*
* You can modify the template of this file in the
* directory ..\JCreator\Templates\Template_1\Project_Name.java
*
* You can also create your own project template by making a new
* folder in the directory ..\JCreator\Template\. Use the other
* templates as examples.
*
*/
//package myprojects.mousing;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
class Mousing extends Frame {
public Mousing() { //constructor
Canvas canvas=new
ElMeuCanvas();
canvas.setBackground(new
Color(255,255,200));
|
add(canvas,BorderLayout.CENTER); |
addWindowListener(new
WindowAdapter() {
public
void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
}
public static void main(String
args[]) {
System.out.println("Starting
Mousing...");
Mousing mainFrame
= new Mousing();
|
mainFrame.setSize(600,
400);
mainFrame.setTitle("Doble
buffering"); |
mainFrame.setVisible(true);
}
}
|
class ElMeuCanvas extends Canvas implements MouseListener,
MouseMotionListener {
int iniX,iniY;
int posX=0,posY=0;
Image image;
|
public ElMeuCanvas
() { //constructor
super(); |
addMouseListener(this);
addMouseMotionListener(this);
Toolkit toolkit=Toolkit.getDefaultToolkit();
URL url=getClass().getResource("imatges/bott_001.jpg");
image=toolkit.getImage(url);
MediaTracker
mediaTracker=new MediaTracker(this);
mediaTracker.addImage(image,0);
try
{
mediaTracker.waitForAll();
while(!mediaTracker.checkAll())
{
}
}
catch (InterruptedException e) {
System.out.println("No
puc!");
}
|
}
public void paint (Graphics g) { |
g.drawImage(image,posX,posY,this);
}
public void mouseClicked
(MouseEvent e) {
}
public void mousePressed
(MouseEvent e) {
iniX=e.getX();
iniY=e.getY();
}
public void mouseReleased
(MouseEvent e) {
}
public void mouseEntered
(MouseEvent e) {
}
public void mouseExited
(MouseEvent e) {
}
public void mouseDragged
(MouseEvent e) {
int fiX=e.getX();
int fiY=e.getY();
posX=posX+fiX-iniX;
posY=posY+fiY-iniY;
repaint();
iniX=fiX;
iniY=fiY;
}
public void mouseMoved (MouseEvent e)
{
}
}
|
- ja posem el canvas
directament a la posició
BorderLayout.CENTER del frame,
sense l'intermedi d'un scrollpane,
ara no necessari,
- a la classe
ElMeuCanvas hi definim les variables
de classe int iniX, int iniY, int posX
i int posY,
que ens han de servir per registrar els moviments
del cursor del mouse,
- hi definim també la
imatge que s'ha de veure com a variable
de classe, per tal que sigui accessible
des de més d'un mètode,
- preparem la imatge
ja al mètode constructor
i finalment,
- dibuixem la
imatge, no en el punt (0,0)
del canvas, sinó en el punt
(posX,posY), calculat a partir dels moviments
del cursor del mouse (vegeu la
pràctica 2 d'aquest mòdul
|
|
- Compileu-lo i
feu-ne la prova. A l'arrossegar la imatge amb el ratolí, observareu
un molest pampallugueig... (Ja heu penjat una còpia de la carpeta
\imatges de la carpeta
\classes d'aquest
projecte?)
|
|
- Aquest pampallugueig es deu a que, en el mètode
public void MouseDragged(MouseEvent e),
cridem al mètode public
void repaint(), el qual crida al mètode
public void update(Graphics g) del canvas.
Com ja expliquem a la pràctica 1 d'aquest
mòdul, aquest mètode,
per defecte, comença per omplir el canvas
amb el color de fons i per tant, fa
desaparèixer tot allò que hi havia pintat i, després
executa el mètode public
void paint(Graphics g). Podem provar de
sobreescriure (override) aquest
mètode. Afegiu això als
mètodes de la classe
ElMeuCanvas:
public
void update (Graphics g) {
paint(g);
} |
Ara, quan compilem i provem, veiem
que la imatge es mou
suaument, però que les restes de les
imatges anteriors no desapareixen pas...
|
|
- Podem provar de dibuixar
un rectangle del color de fons,
tot just abans de dibuixar la imatge:
public
void paint (Graphics g) { |
Dimension
size=getSize();
g.setColor(getBackground());
g.fillRect(0,0,size.width,size.height); |
paint(g);
} |
però tornem a tenir el mateix pampallugueig del principi... Què
hem de fer?
|
|
|
|
La tècnica del "double buffering" |
|
|
 |
La solució és a la tècnica
del doble buffering: consisteix en construir
una imatge, una instància
de la classe java.awt.Image,
que de moment restarà oculta, dibuixar-hi el que calgui (una imatge
té el seu propi context gràfic,
una instància de
java.awt.Gràphics) i, quan tot està a punt, dibuixar-la
al canvas amb el context
gràfic que li és propi. Vegem-ho: |
|
|
|
- Comencem per afegir Image imEncaraNoEsVeu,
la imatge que, de moment restarà
oculta, a la llista de variables de classe
del canvas:
int
iniX,iniY;
int posX=0,posY=0; |
Image
image,imEncaraNoEsVeu; |
Ara cal modificar profundament el mètode
public void paint (Graphics g). Si
la imatge
Image imEncaraNoEsVeu fos null,
la creem amb el mètode
public Image createImage(int ample,int alt).
Això s'ha de fer així per qüestions de gestió
de memòria: si la creem cada vegada, no oblidem que conté
un fitxer *.jpg,
sinó els recursos de la màquina en pateixen de seguida.
Després demanem el context gràfic
d'aquesta imatge (Graphics
gEncaraNoEsVeu) i hi dibuixem a sobre. Finalment, dibuixem la
imatge amb el context gràfic
del canvas (Graphics
g):
public
void paint (Graphics g) {
Dimension size=getSize(); |
if
(imEncaraNoEsVeu==null) {
imEncaraNoEsVeu=createImage(1024,768);
}
Graphics gEncaraNoEsVeu=imEncaraNoEsVeu.getGraphics();
gEncaraNoEsVeu.setColor(getBackground());
gEncaraNoEsVeu.fillRect(0,0,size.width,size.height);
gEncaraNoEsVeu.drawImage(image,posX,posY,this);
g.drawImage(imEncaraNoEsVeu,0,0,this); |
} |
Compilem i assagem. Voilà!
Ara sí, eh?
|
|
|
|
|
|
|
Un altre exemple de doble buffering,
encara... |
|
|
 |
Ara es tracta de dibuixar rectangles
amb el ratolí, de manera que el
vèrtex superior esquerre estigui sempre en el punt on hem començat
a arrossegar i el vèrtex inferior
dret segueixi el moviment del cursor del
ratolí quan arrosseguem.
Així: |
|
|
|
|
 |
- El punt on comença l'arrossegament
és a les variables int
iniX i int iniY, els valors de les
quals es recullen al mètode
public void mousePressed (MouseEvent e). Les
posicions del mouse durant l'arrossegament
seran a les variables int
fiX i int fiY. Per tant cal declarar-les
com a variables de classe:
int iniX,iniY; |
int
fiX=0,fiY=0; |
Image
image,imEncaraNoEsVeu; |
El mètode public
void mouseDragged (MouseEvent e) recull la posició actual
del mouse i força el repaint
del canvas:
public
void mouseDragged (MouseEvent e) { |
fiX=e.getX();
fiY=e.getY(); |
repaint();
} |
Finalment, dibuixem la imatge en el
punt (0,0) del canvas
(ara no s'ha de moure) i, amb els valors recollits, dibuixem el rectangle:
public
void paint (Graphics g) {
Dimension size=getSize();
if
(imEncaraNoEsVeu==null) {
imEncaraNoEsVeu=createImage(1024,768);
}
Graphics gEncaraNoEsVeu=imEncaraNoEsVeu.getGraphics();
gEncaraNoEsVeu.setColor(getBackground());
gEncaraNoEsVeu.fillRect(0,0,size.width,size.height); |
gEncaraNoEsVeu.drawImage(image,0,0,this);
gEncaraNoEsVeu.setColor(Color.WHITE);
int ample=Math.abs(iniX-fiX);
int alt=Math.abs(iniY-fiY);
if
(fiX>iniX) {
if
(fiY>iniY) {
gEncaraNoEsVeu.drawRect(iniX,iniY,ample,alt);
}
else if (fiY<iniY) {
gEncaraNoEsVeu.drawRect(iniX,fiY,ample,alt);
}
}
else if (fiX<iniX) {
if
(fiY>iniY) {
gEncaraNoEsVeu.drawRect(fiX,iniY,ample,alt);
}
else if (fiY<iniY) {
gEncaraNoEsVeu.drawRect(fiX,fiY,ample,alt);
}
} |
g.drawImage(imEncaraNoEsVeu,0,0,this);
} |
Observeu que si volem que el rectangle
es dibuixi encara que sobrepassem el punt inicial per la dreta o per
sobre, el punt de posició del
rectangle ha de canviar. Llavors, l'amplada
i l'alçada del rectangle
es calculem mitjançant el mètode
static de la classe
java.lang.Math, public static int abs(int
n).
La classe java.lang.Math
és una classe declarada
final (no es pot extendre) i
conté, com a mètodes
static, totes les rutines i funcions matemàtiques
d'ús corrent (vegeu-ne la documentació).
El package java.lang
no cal importar-lo: el compilador
el dóna per importat per defecte.
|
|
|
|
|
|
|
 |
|
|
|
|