可能更容易的是使用某种后备缓冲区,并且仅在发生变化时才对其进行绘制。
这意味着对paintComponent 的调用仅绘制后备缓冲区,使其更快,并且如果列表变得很大,将允许在第二个线程中绘制后备缓冲区。
更新了示例
public class BackingBuffer {
public static void main(String[] args) {
new BackingBuffer();
}
public BackingBuffer() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
File[] imageFiles = new File("D:/hold/ScaledImages").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".gif");
}
});
ImagesPane imagesPane = new ImagesPane();
for (File file : imageFiles) {
try {
BufferedImage image = ImageIO.read(file);
imagesPane.addImage(image);
} catch (Exception e) {
e.printStackTrace();
}
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(imagesPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ImagesPane extends JPanel {
private BufferedImage backingBuffer;
private List<Image> images;
private Timer updateTimer;
public ImagesPane() {
images = new ArrayList<Image>(25);
updateTimer = new Timer(125, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateBuffer();
repaint();
}
});
updateTimer.setRepeats(false);
updateTimer.setCoalesce(true);
}
public void addImage(Image image) {
// You could devise some kind of algorithim to determine if was possible
// to image the image into the existing backing buffer or not.
// It would save having to recreate the backing buffer unless it
// really was required...
images.add(image);
invalidate();
}
public void removeImage(Image image) {
images.remove(image);
invalidate();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void updateBuffer() {
if (backingBuffer == null || backingBuffer.getWidth() != getWidth() || backingBuffer.getHeight() != getHeight()) {
if (getWidth() > 0 && getHeight() > 0) {
backingBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
}
}
if (backingBuffer != null) {
Graphics2D g2d = backingBuffer.createGraphics();
int y = 0;
int x = 0;
int rowHeight = 0;
for (Image image : images) {
rowHeight = Math.max(image.getHeight(this), rowHeight);
if (x + image.getWidth(this) > getWidth() && x != 0) {
x = 0;
y += rowHeight;
}
g2d.drawImage(image, x, y, this);
x += image.getWidth(this);
if (x > getWidth()) {
x = 0;
y += rowHeight;
rowHeight = 0;
}
}
g2d.dispose();
}
}
@Override
public void invalidate() {
// This method can be called repeatly in quick sucession, rather then
// reacting to each call, I want to delay performing the update,
// which might be costly in time and memory until it's all settled down
// a little...
super.invalidate();
updateTimer.restart();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backingBuffer != null) {
g.drawImage(backingBuffer, 0, 0, this);
}
}
}
}
现在,如果您想使用某种后台线程,我可能会使用SwingWorker
. 您真正需要的唯一额外是您可以提出某种标志,以便您知道工作人员正在更新缓冲区(因为工作人员是不可重入的(同一实例不能运行两次))
工作人员将创建一个新的临时缓冲区,它可以工作,因为您不想干扰当前用于在屏幕上绘制的缓冲区(或者您最终会得到脏颜料),一旦完成,您可以切换方法中的缓冲区done
并调用repaint
组件以使其在屏幕上更新...
更新选择突出显示
您可以直接在后备缓冲区中突出显示每个图像,但我个人认为这是一项昂贵的练习,因为您需要在每次单击时更新后备缓冲区。
更好的方法是保持一个Map
图像边界键控回单个图像。当您更新缓冲区时,您将重新创建此地图。
使用此地图,您可以确定是否单击了任何“图像”。然后我会将图像的引用放入一个列表中,然后在绘制组件时使用它......
public class BackingBuffer {
public static void main(String[] args) {
new BackingBuffer();
}
public BackingBuffer() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
File[] imageFiles = new File("C:/hold/ScaledImages").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".gif");
}
});
ImagesPane imagesPane = new ImagesPane();
for (File file : imageFiles) {
try {
BufferedImage image = ImageIO.read(file);
imagesPane.addImage(image);
} catch (Exception e) {
e.printStackTrace();
}
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(imagesPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ImagesPane extends JPanel {
private Map<Image, Rectangle> mapBounds;
private BufferedImage backingBuffer;
private List<Image> images;
private Timer updateTimer;
private List<Image> selected;
public ImagesPane() {
images = new ArrayList<Image>(25);
selected = new ArrayList<Image>(25);
updateTimer = new Timer(125, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateBuffer();
repaint();
}
});
updateTimer.setRepeats(false);
updateTimer.setCoalesce(true);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (mapBounds != null) {
boolean shouldPaint = false;
for (Image image : mapBounds.keySet()) {
Rectangle bounds = mapBounds.get(image);
if (bounds.contains(e.getPoint())) {
if (selected.contains(image)) {
shouldPaint = true;
selected.remove(image);
} else {
shouldPaint = true;
selected.add(image);
}
// In it's current form, there is not overlapping, if you
// have overlapping images, you may want to reconsider this
break;
}
}
if (shouldPaint) {
repaint();
}
}
}
});
}
public void addImage(Image image) {
// You could devise some kind of algorithim to determine if was possible
// to image the image into the existing backing buffer or not.
// It would save having to recreate the backing buffer unless it
// really was required...
images.add(image);
invalidate();
}
public void removeImage(Image image) {
images.remove(image);
if (mapBounds != null) {
mapBounds.remove(image);
}
if (selected.contains(image)) {
selected.remove(image);
}
invalidate();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void updateBuffer() {
if (backingBuffer == null || backingBuffer.getWidth() != getWidth() || backingBuffer.getHeight() != getHeight()) {
if (getWidth() > 0 && getHeight() > 0) {
backingBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
}
}
if (backingBuffer != null) {
mapBounds = new WeakHashMap<Image, Rectangle>(images.size());
Graphics2D g2d = backingBuffer.createGraphics();
int y = 0;
int x = 0;
int rowHeight = 0;
for (Image image : images) {
rowHeight = Math.max(image.getHeight(this), rowHeight);
if (x + image.getWidth(this) > getWidth() && x != 0) {
x = 0;
y += rowHeight;
}
mapBounds.put(image, new Rectangle(x, y, image.getWidth(this), image.getHeight(this)));
g2d.drawImage(image, x, y, this);
x += image.getWidth(this);
if (x > getWidth()) {
x = 0;
y += rowHeight;
rowHeight = 0;
}
}
g2d.dispose();
}
}
@Override
public void invalidate() {
// This method can be called repeatly in quick sucession, rather then
// reacting to each call, I want to delay performing the update,
// which might be costly in time and memory until it's all settled down
// a little...
super.invalidate();
updateTimer.restart();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backingBuffer != null) {
g.drawImage(backingBuffer, 0, 0, this);
if (selected != null) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(UIManager.getColor("List.selectionBackground"));
for (Image image : selected) {
Rectangle bounds = mapBounds.get(image);
if (bounds != null) {
Composite composite = g2d.getComposite();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2d.fill(bounds);
g2d.setComposite(composite);
g2d.draw(bounds);
}
}
}
}
}
}
}