Sono stato davvero sorpreso di vedere che, nonostante si usi il look nativo &, in Windows il selettore di file non ha una vista di anteprima. Ho provato il tuo esempio e stai seguendo le linee giuste, ma vedo quanto fosse lento per le cartelle con molte immagini di grandi dimensioni. Il sovraccarico è, ovviamente, dovuto all'I/O durante la lettura del contenuto del file e quindi all'interpretazione dell'immagine, il che è inevitabile.
Ciò che è peggio, è che ho scoperto che FileView.getIcon(File)
è chiamato un sacco - prima che venga visualizzato l'elenco dei file, quando il mouse su un'icona, e quando cambia la selezione. Se non salviamo in cache le immagini dopo averle caricate, ricaricheremo inutilmente le immagini tutto il tempo.
La soluzione ovvia è di spingere tutte le caricamento immagine via sopra un altro filo o un pool di thread, e una volta che il nostro risultato in scala ridotta, inserirlo in una cache temporanea in modo che possa essere recuperato nuovamente.
Ho suonato in giro con Image
e ImageIcon
molto e ho scoperto che un'immagine ImageIcon
s' possono essere modificati in qualsiasi momento chiamando setImage(Image)
. Ciò che questo significa per noi è, entro getIcon(File)
, è possibile restituire immediatamente un'icona vuota o predefinita, ma mantenere un riferimento ad esso, passarlo a un thread di lavoro che caricherà l'immagine in background e impostare l'immagine dell'icona in un secondo momento quando è fatto (l'unica presa è che dobbiamo chiamare repaint()
per vedere la modifica).
Per questo esempio, sto utilizzando un pool di thread memorizzato nella cache ExecutorService
(questo è il modo più veloce per ottenere tutte le immagini, ma utilizza molti I/O) per elaborare le attività di caricamento dell'immagine. Sto anche usando uno WeakHashMap
come cache, per assicurarci di rimanere sulle icone della cache solo per il tempo necessario. Potresti usare un altro tipo di mappa, ma dovresti gestire il numero di icone su cui tieni premuto, per evitare di esaurire la memoria.
package guitest;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.filechooser.FileView;
public class ThumbnailFileChooser extends JFileChooser {
/** All preview icons will be this width and height */
private static final int ICON_SIZE = 16;
/** This blank icon will be used while previews are loading */
private static final Image LOADING_IMAGE = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB);
/** Edit this to determine what file types will be previewed. */
private final Pattern imageFilePattern = Pattern.compile(".+?\\.(png|jpe?g|gif|tiff?)$", Pattern.CASE_INSENSITIVE);
/** Use a weak hash map to cache images until the next garbage collection (saves memory) */
private final Map imageCache = new WeakHashMap();
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFileChooser chooser = new ThumbnailFileChooser();
chooser.showOpenDialog(null);
System.exit(1);
}
public ThumbnailFileChooser() {
super();
}
// --- Override the other constructors as needed ---
{
// This initializer block is always executed after any constructor call.
setFileView(new ThumbnailView());
}
private class ThumbnailView extends FileView {
/** This thread pool is where the thumnnail icon loaders run */
private final ExecutorService executor = Executors.newCachedThreadPool();
public Icon getIcon(File file) {
if (!imageFilePattern.matcher(file.getName()).matches()) {
return null;
}
// Our cache makes browsing back and forth lightning-fast! :D
synchronized (imageCache) {
ImageIcon icon = imageCache.get(file);
if (icon == null) {
// Create a new icon with the default image
icon = new ImageIcon(LOADING_IMAGE);
// Add to the cache
imageCache.put(file, icon);
// Submit a new task to load the image and update the icon
executor.submit(new ThumbnailIconLoader(icon, file));
}
return icon;
}
}
}
private class ThumbnailIconLoader implements Runnable {
private final ImageIcon icon;
private final File file;
public ThumbnailIconLoader(ImageIcon i, File f) {
icon = i;
file = f;
}
public void run() {
System.out.println("Loading image: " + file);
// Load and scale the image down, then replace the icon's old image with the new one.
ImageIcon newIcon = new ImageIcon(file.getAbsolutePath());
Image img = newIcon.getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH);
icon.setImage(img);
// Repaint the dialog so we see the new icon.
SwingUtilities.invokeLater(new Runnable() {public void run() {repaint();}});
}
}
}
Problemi noti:
1) Noi non mantenere le proporzioni dell'immagine durante il ridimensionamento. Ciò potrebbe causare la comparsa di icone con dimensioni strane che interromperanno l'allineamento della visualizzazione elenco.La soluzione è probabilmente quella di creare un nuovo BufferedImage
che sia 16x16 e rendere l'immagine ridimensionata su di essa, centrata. Puoi implementarlo se lo desideri!
2) Se un file non è un'immagine o è danneggiato, non verrà visualizzata alcuna icona. Sembra che il programma rilevi questo errore solo durante il rendering dell'immagine, non quando lo cariciamo o lo ridimensioniamo, quindi non possiamo rilevarlo in anticipo. Tuttavia, potremmo rilevare se fissiamo problema 1.
Suggerendo AWT in una domanda swing nel 2012? Eww ... –