Slide Personalizzati

 

 

Obiettivo: Creare una classe che abbia come argomenti al costruttore il path delle tre immagini ripettivamente del pomello, del trak di fondo e qello di scorrimento, che istanzi un oggetto sliderpersonalizzabbile.

 

Il progetto è così strutturato:

una classe per testare il tutto: MySlider, poi una che assembla i componenti, una che disegna il pomello e ne controlla lo spostamento, una per disegnare il trak di scorrimento, una per quello di fondo.

 

La casse MySlider che richiama MySlid (la classe che crea gli slider personalizzati)

classe MySlider:

 1 package myslider;
 2 
 3 import javax.swing.*;
 4 import java.awt.Color;
 5 
 6 public class MySlider extends JFrame {
 7       
 8     public MySlider() {        
 9         setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
10         setLayout(null);              
11         setSize(1300, 600);
12         setLocationRelativeTo(null);
13         getContentPane().setBackground(Color.LIGHT_GRAY);
14         
15         MySlid myd0 = new MySlid("/image/drag03.png","/image/drag10.png","/image/drag10dx.png");
16         MySlid myd1 = new MySlid("/image/drag03.png","/image/drag10dx.png");
17         MySlid myd2 = new MySlid("/image/stondo.png","/image/drag10dx.png");
18         MySlid myd3 = new MySlid("/image/drag10p.png","/image/drag10.png","/image/drag10dx.png");
19         MySlid myd4 = new MySlid("/image/switch_off.png","/image/drag10.png","/image/drag10dx.png");
20              
21         myd0.setPosition(50, 40);       
22         myd1.setPosition(50, 120);
23         myd2.setPosition(50, 200);
24         myd3.setPosition(50, 350);       
25         myd4.setPosition(50, 470);       
26         
27         getContentPane().add(myd0);        
28         getContentPane().add(myd1);
29         getContentPane().add(myd2);
30         getContentPane().add(myd3);       
31         getContentPane().add(myd4);
32         
33         setVisible(true);
34     }    
35  
36     public static void main(String args[]) {
           //Look end Feel collassato
58         //</editor-fold>
59 
60         /* Create and display the form */
61         java.awt.EventQueue.invokeLater(() -> {
62             new MySlider();
63         });
64     }               
65 }

 

Discussione:

Come si nota la classe MySlid ha due costruttori uno con tre argomenti ed uno con due. In ordine sono il percorso (path) dell'immagine che rappresenterà il pomello di scorrimento, il secondo la trak di scorrimento, la parte a sinistra del pomello man mano che questo scorre sulla destra, cambia di aspetto, il terzo la trak di fondo. Il secondo costruttore accetta solo due argomenti viene praticamente omesso il trak di scorrimento.

 

La classe che disegna il pomello e ne controlla lo spostamento

classe MySlid:

 1 package myslider;
 2 
 3 import javax.swing.JComponent;
 4 //import java.beans.PropertyChangeListener;
 5 import java.beans.PropertyChangeEvent;
 6 
 7 public class MySlid extends JComponent{  
 8     private String po,sx,dx;
 9     private int pH,pW,lun,dW,dH;
10     private MyIcon myc;
11     private TrakSx tsx;
12     private TrakDx tdx;
13     private int xpm;
14    
15     //Costruttore per solo pomello e trak di fondo
16     public MySlid(String a, String c){
17         po=a; dx=c;
18         inizializza(0);
19     }
20     //Costruttore per pomello, trak di fondo e barra espandibile
21     public MySlid(String a, String b, String c){
22         po=a; sx=b; dx=c;        
23          inizializza(1);            
24     } 
25     
26     private void inizializza(int flg){      
27         //Trak nera
28         tdx = new TrakDx(dx);        
29         dW=tdx.getTrkW();
30         dH=tdx.getTrkH();
31         
32         //Trak arancio   
33         if(flg==1){       
34             tsx = new TrakSx(sx);                 
35         }
36 
37         //Pomello
38         myc = new MyIcon(po,dW);
39         pW=myc.getPomW();
40         pH=myc.getPomH();
41         if(flg==1){      
42             myc.addPropertyChangeListener((PropertyChangeEvent evt) -> {
43                 String pro=evt.getPropertyName();
44                 if("foreground".equals(pro)){
45                     xpm=myc.getMpos();
46                     tsx.setTrakW(xpm);
47                 }
48             });
49         }
50         
51         tdx.setLocation(pW/3,(pH-dH)/2 );
52         if(flg==1)tsx.setLocation(pW/3,(pH-dH)/2);
53         
54         this.add(myc);        
55         if(flg==1) this.add(tsx);                     
56         this.add(tdx);
57         
58         lun=dW+(pW*2/3)+5;
59         this.setSize(lun, pH);
60     }
61     
62     public void setPosition(int x, int y){
63         this.setLocation(x-(pW/3), y);
64     }
65 }

 

Discussione:

I due costruttori raccolgono i rispettivi path alle immagini e lasciano al metodo inizializza il lavoro da fare. Se verrà passato il parametro 0 verranno istanziate solo le classi pomello (MyIcon) e la trak di fondo (TrakDx). Se si passa il falg 1 allora inizializza istanzierà tutte e tre le classi quindi anche il trak di scorrimento. Si rammenta che l'ordine in cui verranno visualizzati i tre oggetti è dettato dall'ordine in cui vengono aggiunti al controlPane del componente. This si riferisce alla classe MySlid di cui richiama implicitamente il contentPane righe 56-58. L'ordine in cui vengono istanziate le classi deve essere necesariamente quello riportato, in quanto uno ha bisogno dei dati da prelevare dall'altro. La classe TrakSx (tsx) verrà addizionata solo se si è usato il costruttore a tre argomenti.

Cosa accade dalla riga 38 alla 51? 37 49

Appena creato l'ogetto myc (MyIcon) si recuperano le dimensioni del pomello e poi (sempre se siamo al caso a tre) gli associamo un ascoltatore che sia attento ad intercettare eventuali cambiamenti di proprietà della classe. Perchè abbiamo la necessità di far aumentare la larghezza della traccia di scorrimento in modo che segua il pomello nel suo spostamento verso destra e farla diminuire quando questo si sposta verso sinistra.

Se andiamo a sbirciare nella classe MyIcon vedremo che questa verrà associata ad un ascoltatore che andrà ad intercettare quando il mouse dragherà (mauseDragged). Quando riceve l'evento, tra le altre cose andrà a modificare una proprietà della JLabel, ricordo che la classe MyIcon non estende un JComponent come le altre ma un JLabel proprio per avere a disposizione una proprietà, nel nostro caso ho scelto il cambio di colore del foreground (colore del testo) che alternativamente viene cambiato da nero a bianco e da bianco a nero vedi il metodo setFore().

 

mouseDragged:

 43         addMouseMotionListener(new MouseAdapter(){
 44             @Override
 45             public void mouseDragged(MouseEvent evt){
 46                 nw=evt.getX();
 47                 //Mantieni la posizione del puntatore rispetto ai bordi
 48                 mvx=nw-dx;
 49                 //Blocca agli estremi
 50                 max=(pomWidth/3)+trakdWidth-((pomWidth*2)/3);
 51                 if(mvx<0)mvx=0;
 52                 if(mvx>max)mvx=max;
 53                 movePom(mvx,yMrg);
 54                 setFore();
 55             }
 56         });        
 57     }

 

setFore:

 58     private void setFore(){
 59         if(std==0){
 60             std=1;
 61             this.setForeground(Color.black);
 62         }else{
 63             std=0;
 64             this.setForeground(Color.white);        
 65         }  
 66     }

 

Quindi ogni qualvolta sposteremo il pomello verrà modificata la proprirtà foreground e di conseguenza verrà lanciato un messagio di propertyChange, questo verrà intercettato nella classe MySlid dall'ascoltatore di cui parlavamo che una volta allertato dell'avvenuta modifica di proprietà (alias spostamento del pomello) andrà a modificare la lunghezza del JComponent che disegna la traccia di scorrimento. Tornando a MySlid nelle righe 46-48 leggerà la nuova posizione del mouse (la x) (xpm=myc.getMpos();) e la userà per settare la larghezza della traccia di scorrimento (tsx.setTrakW(xpm);). Nelle righe 45-48 viene lettta la larghezza e l'altezza della traccia di fondo da TrakDx si invia la larghezza a MyIcon che la userà per determinare i finecorsa, poi viene calcolata la posizione della traccia di fondo e se prevista la posizione della traccia di scorrimento. Una particolarità il metodo setPosition, si poteva utilizzare quello a disposizione e cioè setLocation ma quello avrebbe allineato gli slider inizio pomello ma poichè i pomelli potrebbero avere dimensione molto diverse usando setPosition ci troveremo le trak di fondo tutte allineate a prescindere dalla dimensione dei pomelli.

 

Ora passiamo a commentare la classe più complessa la MyIcon

Classe: MyIcon:

  1 package myslider;
  2 
  3 import java.awt.Color;
  4 import java.awt.Graphics;
  5 import java.awt.Graphics2D;
  6 import java.awt.Shape;
  7 import java.awt.event.MouseAdapter;
  8 import java.awt.event.MouseEvent;
  9 import java.awt.geom.Ellipse2D;
 10 import java.net.*;
 11 import javax.swing.*;
 12 
 13 public class MyIcon extends JLabel{
 14     private ImageIcon pom;
 15     private int xMrg=0,yMrg=0,mvx,max,maxlu;
 16     private int xPom=xMrg ,yPom=yMrg;
 17     private int dx,ex,nw;
 18     private int pomWidth,pomHeigh;
 19     private int trakdWidth;
 20     private int std=0,ssx;
 21    
 22     Shape shape = null; 
 23      
 24     public MyIcon(String po, int ww){
 25         trakdWidth=ww;     
 26         //Carica le immagini
 27         pom = createImageIcon(po);        
 28                
 29         //Prendi le dimensioni        
 30         pomWidth=pom.getIconWidth();
 31         pomHeigh=pom.getIconHeight();         
 32         
 33         maxlu=trakdWidth+((pomWidth*2)/3)+5;
 34         this.setBounds(0, 0, maxlu, pomHeigh);
 35       
 36         addMouseListener(new java.awt.event.MouseAdapter() {
 37             @Override
 38             public void mousePressed(MouseEvent evt) {
 39                 ex=evt.getX();
 40                 dx=ex-xPom;
 41             }
 42         });
 43         addMouseMotionListener(new MouseAdapter(){
 44             @Override
 45             public void mouseDragged(MouseEvent evt){
 46                 nw=evt.getX();
 47                 //Mantieni la posizione del puntatore rispetto ai bordi
 48                 mvx=nw-dx;
 49                 //Blocca agli estremi
 50                 max=(pomWidth/3)+trakdWidth-((pomWidth*2)/3);
 51                 if(mvx<0)mvx=0;
 52                 if(mvx>max)mvx=max;
 53                 movePom(mvx,yMrg);
 54                 setFore();
 55             }
 56         });        
 57     }
 58     private void setFore(){
 59         if(std==0){
 60             std=1;
 61             this.setForeground(Color.black);
 62         }else{
 63             std=0;
 64             this.setForeground(Color.white);        
 65         }  
 66     }
 67     private void movePom(int x, int y){
 68         //L stato corrente del pomello e immagazzinato in variabili final (costanti)
 69         //per evitare ripetute chiamate dello stesso metodo
 70         final int CURR_X = xPom;
 71         final int CURR_Y = yPom;
 72         final int CURR_W = pomWidth;
 73         final int CURR_H = pomHeigh;
 74         final int OFFSET = 1;
 75 
 76         if ((CURR_X!=x) || (CURR_Y!=y)) {
 77 
 78             // IL pomello si sta movendo, ridisegna il background
 79             // sopra la vecchia posizione del pomello. 
 80             repaint(CURR_X,CURR_Y,CURR_W+OFFSET,CURR_H+OFFSET);
 81                        
 82             // Aggiorna le coordinate.
 83             xPom=x;
 84             yPom=y;
 85 
 86             // Ridisegna il pomello nella nuova posizione.
 87             repaint(xPom, yPom, 
 88                 pomWidth+OFFSET, 
 89                 pomHeigh+OFFSET);
 90         }
 91     }
 92     @Override 
 93     public void paintComponent(Graphics g) {
 94         super.paintComponent(g);
 95         Graphics2D g2d =(Graphics2D)g;
 96         shape=new Ellipse2D.Float(xPom,yPom,pomWidth,pomHeigh);        
 97         pom.paintIcon(this, g2d, xPom, yPom);
 98     }
 99     
100     @Override
101     public boolean contains(int x, int y) {
102         return shape.contains(x,y);
103     }
104     public int getPomW(){
105         return(pomWidth);
106     } 
107     public int getPomH(){
108         return(pomHeigh);
109     }
110     public int getMpos(){
111         ssx=xPom+(pomWidth/2);
112         return(ssx);
113     }
114     private ImageIcon createImageIcon(String path) {
115          URL imgURL = getClass().getResource(path);
116          if (imgURL != null) {
117              return new ImageIcon(imgURL);
118          } else {
119              System.err.println("Non è possibile trovare il file: " + path);
120            return null;
121         }
122      }
123 }

 

 

Calcolo della massima dimensione che dovrà avere

maxlu=trakdWidtht+((pomWidth*2)/3);

 

da riga 37 a 55 c'è i controllo del movimento del pomello. Si associano due ascoltatori il primo raccoglie la posizione del mouse al primo tocco, la pressione (mousePressed) ci interessa la distanza che il mause ha in questo istante dal bordo sinistro del pomello dx, ci riferiamo alle origini del JComponent (JLabel) di MYIcon, xpon è la x del pomello, ex la x del mouse.

dx=ex-xPom;

 

Questo ci servirà per conservare questa posizione relativa durante lo spostamento.

Il secondo ascoltatore è sensibile al trascinamento (mouseDragged) la nuova posizione che dovrà assumere il pomello sarà datada

mvx=nw-dx;

 

max=(pomWidth/3)+trakdWidth-((pomWidth*2)/3);

 

La y deve rimanere fissa a zero. Si da in pasto al metodo movePom. Segue il metodo setFore() che mi lancerà il cambio della proprietà foreground che come abbiamo visto prima, mi servirà per regolare la lunghezza della traccia a scorrimento. Questo metodo non fa altro chemodificare la proprieta foreground ricorsivamente nero, bianco, nero, bianco ecc

 

setFore:

 58     private void setFore(){
 59         if(std==0){
 60             std=1;
 61             this.setForeground(Color.black);
 62         }else{
 63             std=0;
 64             this.setForeground(Color.white);        
 65         }  
 66     }

 

Ora è la volta del metodo movePom. Per un approfondimento vedi qui. Riceve la nuova posizione x, y

movePom:

 67     private void movePom(int x, int y){
 68         //L stato corrente del pomello e immagazzinato in variabili final (costanti)
 69         //per evitare ripetute chiamate dello stesso metodo
 70         final int CURR_X = xPom;
 71         final int CURR_Y = yPom;
 72         final int CURR_W = pomWidth;
 73         final int CURR_H = pomHeigh;
 74         final int OFFSET = 1;
 75 
 76         if ((CURR_X!=x) || (CURR_Y!=y)) {
 77 
 78             // IL pomello si sta movendo, ridisegna il background
 79             // sopra la vecchia posizione del pomello. 
 80             repaint(CURR_X,CURR_Y,CURR_W+OFFSET,CURR_H+OFFSET);
 81                        
 82             // Aggiorna le coordinate.
 83             xPom=x;
 84             yPom=y;
 85 
 86             // Ridisegna il pomello nella nuova posizione.
 87             repaint(xPom, yPom, 
 88                 pomWidth+OFFSET, 
 89                 pomHeigh+OFFSET);
 90         }
 91     }

 

La prima cosa importante è il definire final ossia non modificabili le variabili delle 4 coordinate in modo che la loro variazione possa provocare ripetute richiamate del metodo. I precedenti prove avevo infatti un tremendo sfarfallio e salti incontrollati dell'oggetto. Cosi il movimento è stabile e fluido.

Ma il cambiamento più importante è l'invocazione del metodo repaint. Questo metodo è definito da java.awt.Component ed è il meccanismo che consente di ridisegnare in modo programmatico la superficie di un determinato componente. Ha una versione no-arg (che ridisegna l'intero componente) e una versione multi-arg (che ripete solo l'area specificata). Questa area è anche nota come clip. Invocare la versione multi-arg di ridisegno richiede un piccolo sforzo in più, ma garantisce che il tuo codice non sprecherà cicli per ridisegnare le aree dello schermo che non sono state modificate.

Poiché stiamo impostando manualmente la clip, il nostro metodo movePom richiama il metodo Repaint non una volta, ma due volte. La prima invocazione dice a Swing di ridisegnare l'area del componente in cui si trovava il quadrato in precedenza (il comportamento ereditato utilizza il Delegato dell'interfaccia utente per riempire quell'area con il colore di sfondo corrente). La seconda invocazione dipinge l'area del componente dove il quadrato è attualmente . Un punto importante che vale la pena notare è che sebbene abbiamo invocato ridipingere due volte di fila nello stesso gestore di eventi, Swing è abbastanza intelligente da prendere quelle informazioni e ridipingere quelle sezioni dello schermo tutte in un'unica operazione di disegno. In altre parole, Swing non ridisegna il componente due volte di seguito, anche se è ciò che il codice sembra fare.

 

Un aspetto fantasrtico è quello di contenere l'area recettiva al mouse all'interno di una forma che possiamo definire noi. Normalmente quest'area è un rettangolo ma nel nostro caso l'abbiamo ridotta ad un cerchio che circoscrive il pomello in modo che potremo trascinarlo solo se entriamo dentro al cerchio. In una condizione normale potevamo trascinarlo anche cliccando agli angoli del quadrato circoscritto. Questo significa che se vessimo disegnato un contorno a forma di stella l'avremmo potuta trascinare solo cliccando all'imterno del suo corpo.

Per fare questo bisogna creare una forma (per noi il cerchio) nel metodo paintComponent naturalmente dovrà sovrapporsi perfettamente al pomello percui stesse coordinate e dimensioni.

 

paintComponent:

 93     public void paintComponent(Graphics g) {
 94         super.paintComponent(g);
 95         Graphics2D g2d =(Graphics2D)g;
 96         shape=new Ellipse2D.Float(xPom,yPom,pomWidth,pomHeigh);        
 97         pom.paintIcon(this, g2d, xPom, yPom);
 98     }

 

Poi bisogna sovrascrivere il metodo contains che il sistema usa per verificare se il mouse è all'interno dell'oggetto . Questo usa il metodo contains della forma shape e il mouse è all'interno di questa forma ritornerà true altrimenti false.

 

contains:

100     @Override
101     public boolean contains(int x, int y) {
102         return shape.contains(x,y);
103     }

 

Per concludere ci sono tre metodi get per avere le relative informazioni.

 

classe TrakSx:

 1 package myslider;
 2 
 3 import java.awt.Graphics;
 4 import java.awt.Graphics2D;
 5 import java.net.URL;
 6 import javax.swing.ImageIcon;
 7 import javax.swing.JComponent;
 8     
 9 public class TrakSx extends JComponent{
10      private final  ImageIcon trak;
11      //private final int trakWidth;
12      private final int trakHeight;
13      
14      
15      public TrakSx(String str){
16          trak = createImageIcon(str);
17          //trakWidth=trak.getIconWidth();
18          trakHeight=trak.getIconHeight(); 
19          this.setSize(40, trakHeight);
20      }
21      @Override
22      public void paintComponent(Graphics g) {
23          super.paintComponent(g);
24          Graphics2D g2d =(Graphics2D)g;       
25          trak.paintIcon(this, g2d, 0, 0);       
26      }
27      
28      public void setTrakW(int ww){
29          this.setSize(ww, trakHeight);
30      }
31      
32      private ImageIcon createImageIcon(String path) {
33         URL imgURL = getClass().getResource(path);
34         if (imgURL != null) {
35             return new ImageIcon(imgURL);
36         } else {
37             System.err.println("Non è possibile trovare il file: " + path);
38           return null;
39        }
40     }           
41 } 

 

classe TrakDx:

 1 package myslider;
 2 
 3 import java.awt.Graphics;
 4 import java.awt.Graphics2D;
 5 import java.net.URL;
 6 import javax.swing.ImageIcon;
 7 import javax.swing.JComponent;
 8 
 9 public class TrakDx extends JComponent{
10      private final  ImageIcon trak;
11      private final int trakWidtht;
12      private final int trakHeight;
13 
14      public TrakDx(String str){
15          trak = createImageIcon(str);
16          trakWidtht=trak.getIconWidth();
17          trakHeight=trak.getIconHeight(); 
18          this.setSize(trakWidtht, trakHeight);
19      }
20      @Override
21      public void paintComponent(Graphics g) {
22          super.paintComponent(g);
23          Graphics2D g2d =(Graphics2D)g;       
24          trak.paintIcon(this, g2d, 0, 0);       
25      }
26      public int getTrkW(){
27         return(trakWidtht);
28      }
29      public int getTrkH(){
30         return(trakHeight);
31      }        
32      private ImageIcon createImageIcon(String path) {
33         URL imgURL = getClass().getResource(path);
34         if (imgURL != null) {
35             return new ImageIcon(imgURL);
36         } else {
37             System.err.println("Non è possibile trovare il file: " + path);
38           return null;
39        }
40     }           
41 } 

Le altre classi dovrebbero essere intuitive.