より簡単なのは、何らかのバッキング バッファーを使用し、何かが変更されたときにのみそこにペイントすることです。
これは、paintComponent への呼び出しがバッキング バッファーのみを描画することを意味し、高速化され、リストが大きくなった場合に 2 番目のスレッドでバッキング バッファーを描画できるようになります。
例で更新
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
. 追加で本当に必要なのは、ワーカーがバッファを更新していることを知ることができるフラグのようなものだけです (ワーカーは再入不可であるため (同じインスタンスを 2 回実行することはできません))。
ワーカーは、現在画面にペイントするために使用されているものに干渉したくないため(または汚れたペイントになってしまうため)、機能する新しい一時バッファを作成し、完了したら、次のことができますメソッド内のバッファを切り替え、コンポーネントを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);
}
}
}
}
}
}
}