For about a week now I've been disemboweling java swing to make a prettier and performant image list. I think i have succeed finally.
The idea is to publish a interface that gives everything that a user needs to add images that horizontally wrap to a centered jlist, asynchronously or not, without having the danger of memory leaks (by itself).
This can be achieved by:
- Creating a custom JViewportLayoutManager (centered)
- Creating a custom JList or JList configuration class.
- Creating a custom ListCellRenderer (that calls the interface functions)
- Instead of making the interface image function return immediately (forcing your interface to be synchronous) make it return by calling another function (in the JList configuration class). This also serves as a kind of locking so that a loading image work is not repeated, and repaints after loading.
- On the listcellrenderer, dispose of images that are not visible atm.
package util.swing.layouts;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.Point;
import javax.swing.JList;
import javax.swing.JViewport;
import javax.swing.Scrollable;
/**
* A viewport layout that tries to horizontally center a component in
* the viewport. You might have to disable scrollRectToVisible in the
* view class used unfortunatly, since the only way i found of centering
* CellRenderers in a viewport is using a negative start position (upper left)
* since the view always scrolls to the cell from 0 not from the negative start position.
* @author bio-aulas
*/
public class CenteredViewPortLayout implements LayoutManager {
public void addLayoutComponent(String name, Component comp) {/*nop*/}
public void removeLayoutComponent(Component comp) {/*nop*/}
public Dimension maximumLayoutSize(Container target) {
if (target == null) {
return new Dimension(0, 0);
}
return target.getPreferredSize();
}
public Dimension preferredLayoutSize(Container parent) {
Component view = ((JViewport) parent).getView();
if (view == null) {
return new Dimension(0, 0);
} else if (view instanceof Scrollable) {
return ((Scrollable) view).getPreferredScrollableViewportSize();
} else {
return view.getPreferredSize();
}
}
public Dimension minimumLayoutSize(Container parent) {
return new Dimension(4, 4);
}
public void layoutContainer(Container parent) {
JViewport vp = (JViewport) parent;
Component view = vp.getView();
if (view == null) {
return;
}
/**
* All of the dimensions below are in view coordinates.
*/
Dimension newViewSize = view.getPreferredSize();
Dimension maximumViewSize = vp.toViewCoordinates(vp.getSize());
/**
* If a JList the prefered size is the prefered size of the
* sum of cells fitting in a row.
* Also, the limit is the number of cells.
*/
if (view instanceof JList) {
int fixedCellWidth = ((JList) view).getFixedCellWidth();
int expandLimit = ((JList) view).getModel().getSize();
if (fixedCellWidth != -1) {
newViewSize.width = fixedCellWidth;
}
/**
* Multiply the cell width until it matches the
* n * viewPreferredSize.width + z = viewPort.width
* for a z < preferredSize and n <= expandLimit */ int n = 0; while (((n + 1) * newViewSize.width) <= maximumViewSize.width && (n + 1) <= expandLimit) { n++; } newViewSize.width = n * newViewSize.width; } //fill to maximum size if it doesn't fit or nothing there if (newViewSize.width == 0) { newViewSize.width = maximumViewSize.width; } /**_________ __________ * |X|XYX|Y| -> |X|YYYY|X|
* |X|XYX|Y| |X|YYYY|X|
*/
Point viewPosition = vp.getViewPosition();
int centerJustifiedX = (maximumViewSize.width - newViewSize.width) / 2;
viewPosition.x = -Math.max(0, centerJustifiedX);
vp.setViewPosition(viewPosition);
vp.setViewSize(newViewSize);
}
}
The Jlist wrapper:
package ui;
import java.awt.Component;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import util.swing.layouts.CenteredViewPortLayout;
public class ImageList {
private final JScrollPane pane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
private final LazyImageListUpdate update;
private boolean cacheImages = false;
//Only use this cache on the EDT. Even if synchronized it would
//throw concurrent modification exceptions because iterators are used.
private final Map
This is enough, if you implement LazyImageListUpdate correctly to make a pretty and performant JList without memory leaks - if you set cacheImagesInMemory to false in the constructor - thinking about removing that parameter . I tested mine today, but since it needs a lot of libraries, and many other classes, i'm just posting a
My example implementation, prepared to work on a ImageList that receives a File ListModel, using a few utility Chain-of-responsability classes that i made:
private class LazyUpdate implements ImageList.LazyImageListUpdate {
ChainExecutorService ex = newFixedThreadPool(1);
@Override
public void getCellImage(final ImageList list, final Object obj, int imageWidth, int imageHeight) {
URI fileUri = ((File) obj).toURI();
try {
ChainRunnable disjuntive, firstAttempt, secondAttempt, returnLink;
returnLink = new ChainRunnable(){
@Override
protected BufferedImage runLink(BufferedImage arg) throws Throwable {
list.returnImage(obj, arg);
return arg;
}
};
firstAttempt = createChain(new ReadImageFromCacheAsJpg(fileUri),
returnLink);
secondAttempt = createChain(new ReadImageFromArchive(fileUri),
new ScaleImage(imageWidth, imageHeight),
new WriteImageToCacheAsJpg(fileUri),
returnLink);
disjuntive = createDisjuntiveChain(firstAttempt, secondAttempt);
ex.submit(disjuntive);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getCellText(ImageList list, Object obj) {
return ((File) obj).getName();
}
@Override
public String getTooltipText(ImageList list, Object obj) {
return ((File) obj).getName();
}
}
And a image of how the test looks (with a glazedlists textfield filter of course).
Sem comentários:
Enviar um comentário