De hecho, me sorprendió ver que, a pesar de utilizar el aspecto nativo & en Windows, el selector de archivos no tiene una vista en miniatura. Probé tu ejemplo y vas por las líneas correctas, pero veo lo lento que fue para las carpetas con muchas imágenes grandes. La sobrecarga es, por supuesto, debido a E/S al leer el contenido del archivo y luego interpretar la imagen, que es inevitable.
Lo que es aún peor, es que descubrí que FileView.getIcon(File)
se llama un montón- antes de que aparezca la lista de archivos, al pasar el ratón sobre un icono, y cuando cambia la selección. Si no almacenamos en caché las imágenes después de cargarlas, recargaremos sin sentido las imágenes todo el tiempo.
La solución obvia es eliminar toda la carga de imágenes en otro subproceso o un grupo de subprocesos, y una vez que tenemos nuestro resultado reducido, colóquelo en un caché temporal para que pueda recuperarse nuevamente.
He jugado un poco con Image
y ImageIcon
mucho y he descubierto que un ImageIcon
's imagen se puede cambiar en cualquier momento llamando setImage(Image)
. Lo que esto significa para nosotros es que, dentro de getIcon(File)
, podemos devolver inmediatamente un ícono en blanco o predeterminado, pero mantener una referencia, pasarlo a un hilo de trabajo que cargará la imagen en el fondo y establecerá la imagen del ícono más adelante cuando esté hecho (El único inconveniente es que debemos llamar al repaint()
para ver el cambio).
Para este ejemplo, estoy usando un grupo de subprocesos en caché ExecutorService
(esta es la forma más rápida de obtener todas las imágenes, pero usa mucha E/S) para procesar las tareas de carga de imágenes. También estoy usando un WeakHashMap
como caché, para asegurarnos de que solo retengamos los íconos en la memoria caché por el tiempo que los necesitemos. Podría usar otro tipo de Mapa, pero tendría que administrar la cantidad de íconos a los que se agarra, para evitar quedarse sin 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();}});
}
}
}
problemas conocidos
1) No mantener la relación de aspecto de la imagen cuando se escala. Hacerlo podría dar como resultado iconos con dimensiones extrañas que romperán la alineación de la vista de lista.La solución probablemente sea crear un nuevo BufferedImage
que sea 16x16 y renderizar la imagen escalada encima, centrada. ¡Puedes implementar eso si lo deseas!
2) Si un archivo no es una imagen, o está dañado, no se mostrará ningún icono en absoluto. Parece que el programa solo detecta este error mientras se procesa la imagen, no cuando la cargamos o escalamos, por lo que no podemos detectar esto por adelantado. Sin embargo, podríamos detectarlo si fijamos tema 1.
Sugiriendo AWT en una pregunta de oscilación en el 2012? Eww ... –