Enrera
Mòdul 6
Iniciació a la programació en Java
  Pràctica
1
2
3
4
   
Exercicis
Exercicis
 
 
 
   
  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é.
   
Atenció !
  • 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
Pràctica
  • 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"
   
Atenció ! 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...
   
Pràctica 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í:
 
   
Atenció !
  • 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.
   
   
 
Amunt