Aqua è l'interfaccia utente dei computer Macintosh e il codice che andremo a sviluppare nel corso dell'articolo è orientato ad ottenere con Swing una barra degli strumenti il più possibile simile a quella di Apple. Ma, si potrebbe obiettare, Swing non possiede il pluggable look&feel, e di conseguenza, non è già progettato per visualizzare tutti gli elementi di una interfaccia utente con widget in tutto e per tutto uguali a quelli nativi?
In realtà, come molti lettori sapranno, le varie implementazioni dei look&feel sono lontane dall'essere perfette. In ambito Mac OS X, Apple ha fatto un ottimo lavoro, anche se anche in questo caso non è perfetto. In alcuni piccoli particolari, come l'evidenziazione delle check box, penso che la cosa sia voluta. Come se l'utente dovesse capire che il programma è Java, e non nativo Cocoa. Un modo per limitare il potenziale successo della piattaforma di SUN, che è comunque alternativa rispetto a quella nativa di Apple.
Si vuole dunque tentare di realizzare qualcosa di meglio di quello che ha potuto fare l'azienda di Cupertino, raggiungendo la perfezione grafica? No, in realtà in questa serie di articoli si vogliono approfondire le API Swing e mostrare tecniche di programmazione Java. Infatti, si vedrà come manipolare l'aspetto dei widget con funzionalità più avanzate rispetto al normale uso che solitamente se ne fa.
button.setIcon( icon );
Il metodo prosegue poi impostando una serie di opzioni:
button.setBorderPainted(false); button.setContentAreaFilled(false); button.setVerticalTextPosition( AbstractButton.BOTTOM); button.setHorizontalTextPosition( AbstractButton.CENTER); button.setBackground(buttonBackgroundColor); button.setIconTextGap(0);
Queste chiamate effettuano, rispettivamente, le seguenti operazioni:
- viene disabilitata la visualizzazione del bordo: le icone delle toolbar Aqua non hanno bordi
- viene disabilitato il riempimento dello sfondo: questo produce l'effetto di eliminare il riquadro dal pulsante
- viene impostata la posizione del testo rispetto all'icona: questo deve essere in basso
- viene impostato l'allineamento del testo, che in Aqua è centrato rispetto allo spazio disponibile
- il colore dello sfondo viene impostato al colore
new Color(219,219,219), determinato esaminando le finestre create da Mac OS X - l'ultima operazione è l'eliminazione dello spazio di separazione (gap) presente tra icona e testo
Bordi, bordi, bordi
A questo punto è necessario sbizzarrirsi un poco con i bordi, in modo da ricreare esattamente l'aspetto di quelli presenti nelle icone Aqua. L'implementazione di un proprio bordo avviene creando una classe che implementa l'interfaccia Border. Ma Swing permette di personalizzare notevolmente i bordi già presenti nella sua vasta libreria, anche combinandoli tra di loro. Questi sono detti bordi compound. In questo modo è possibile costruire bordi molto complessi. Questa funzionalità verrà ora sfruttata per costruire il bordo del pulsante, combinando tre bordi diversi.
Lo schema è illustrato in Figura 3. Le due barre verticali sono border1, che utilizza il colore buttonLineColor. Le due barre orizzontali sono invece border2, che utilizza il colore di sfondo. Combinando due bordi in questo modo, si ottiene che i due lati si estendono per tutta l'alteza del pulsante, mentre quelli orizzontali non si vedono, essendo del colore di sfondo. Se non fosse stato inserito anche il bordo orizzontale, quello verticale sarebbe stato più corto di un pixel. A questo punto viene concatenato un ulteriore bordo, che permette di spaziare il contenuto del pulsante (icona e testo), rispetto al lato superiore ed inferiore della toolbar. Questo si ottiene con un EmptyBorder con lato alto di 5 pixel e lato basso di 2, che si chiama border3.
A questo punto è possibile combinare i vari bordi, facendo attenzione a unirli nel corretto ordine. Prima infatti è necessario unire border1 con border2; il risultante viene combinato con border3. Il risultato viene impostato come bordo del pulsante.
Il codice è il seguente:
Border border1 =
BorderFactory.createMatteBorder(0,1,0,1,buttonLineColor);
Border border2 =
BorderFactory.createMatteBorder(1,0,1,0,buttonBackgroundColor);
Border border3 =
BorderFactory.createEmptyBorder(5,0,2,0);
Border compoundBorder1 =
BorderFactory.createCompoundBorder( border1, border2 );
Border border =
BorderFactory.createCompoundBorder( compoundBorder1, border3 );
button.setBorder(border);
Giocare con le icone
L'ultima manipolazione in merito ai pulsanti è la configurazione dell'icona da presentare quando viene fatto clic sul pulsante. Come detto, è l'icona originale del pulsante ma leggermente più scura. Per impostare l'icona corrispondente allo stato di "premuto" del pulsante è presente questo codice:
Image img = icon.getImage();
ImageIcon pressedIcon = new ImageIcon(
createSelectedIcon( img ) );
button.setPressedIcon(pressedIcon);
Tutte le operazioni viengono svolte nel metodo createSelectedIcon(), la cui implementazione è la seguente:
private Image createSelectedIcon( Image image ) {
ImageObserver observer = new ImageObserver() {
public boolean imageUpdate(Image image, int infoflags, int x, int y, int width, int height) {
return (infoflags & ImageObserver.ALLBITS) == 0;
}
};
int width = image.getWidth(observer);
int height = image.getHeight(observer);
BufferedImage img1 = new BufferedImage(image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics g = img1.getGraphics();
g.drawImage(image, 0, 0, null);
BufferedImage img2 = new BufferedImage(
width, height,
BufferedImage.TYPE_INT_RGB);
RescaleOp rop = new RescaleOp(0.8f, -1.0f, null);
rop.filter(img1, img2);
return img2;
}
Il metodo non fa altro che utilizzare Java2D per eseguire una trasformazione sui colori, scurendo l'immagine. Procedendo con ordine, questo metodo svolge le seguenti funzionalità:
- crea un ImageObserver che da il via libera alle operazioni sulle immagini solo quando rileva che tutti i dati sono stati caricati. Questo oggetto viene utilizzato da Java per monitorare il caricamento di una immagine
- viene ottenuta la dimensione dell'immagine
- viene creata una BufferedImage, un tipo di immagine che può essere scritta
- su di questa viene disegnata l'immagine, attraverso il relativo oggetto Graphics
- viene creata un'altra BufferedImage, della stessa dimensione della prima
- viene creata un'operazione RescaleOp, che non fa altro che filtrare i colori dell'immagine. Con i parametri indicati si ottiene una versione più scura
- con il metodo
filter()l'immagine di partenza viene copiata sulla seconda, passando però dal filtro di manipolazione del colore - alla fine del metodo viene ritornata l'immagine manipolata
Si noti che con Java2D, oppure con le più potenti JAI, è possibile eseguire diversi tipi di manipolazione sulle immagini. È anche da sottolineare che, se lo si desidera, è possibile creare la versione "scurita" dell'icona con software appositi, come Fireworks o Photoshop, e far caricare al programma le due icone: quella normale e quella scura.
Conclusioni
L'approccio per le icone qui illustrato è più comodo in quanto non richiede di mantenere una coppia di icone per ciascuna funzionalità da aggiungere alla toolbar, ma ha lo svantaggio di utilizzare più risorse di CPU, quelle necessarie ad operare la trasformazione di colore.
La toolbar definitiva è presente in Figura 4. È lontana dall'essere perfetta, ma nel suo sviluppo abbiamo imparato tante cose!
