创建 Swing JImageComponent





5.00/5 (4投票s)
使用和/或创建 Swing 类来显示图像
引言
本文详细介绍了如何在 Java™ 中创建一个 Swing 类,用于在 Java™ Applet 和/或应用程序中显示图像。它还包括使图像渲染快速并可用于可滚动容器的步骤。
为了更好地理解,特别是对于初学者,本文使用 `JImageComponent` 实现作为参考,它扩展了 Swing 的 `JComponent`。
说明
1. 创建子类
创建子类意味着扩展你的类。通常,超类将是 Java™ Swing 类中的一个(或多个)。
`JImageComponent` 扩展了 Swing 的 `JComponent`
public class JImageComponent extends javax.swing.JComponent {
/**
* Constructs a new JImageComponent object.
*/
public JImageComponent() {
}
}
2. 创建类变量
你的类将需要各种变量来保存重要数据。随着类功能的扩展,它们可能会发生变化。总的来说,它至少应包含两个变量:一个 `BufferedImage` 对象用于保存要绘制的图像,以及其对应的 `Graphics` 对象。
`JImageComponent` 包含两个 `private` 变量
/** Holds the BufferedImage for the image. */
private BufferedImage bufferedImage = null;
/** Holds the Graphics for the image. */
private Graphics imageGraphics = null;
3. 实现设置/更改图像的功能
你的类将根据其变量绘制图像。你可能需要在构造时设置此图像,和/或在运行时设置/更改图像。
`JImageComponent` 允许在构造时设置图像,并在运行时设置/更改图像。为简洁起见,此处仅列出一个构造函数。
一个以 `BufferedImage` 作为参数的构造函数。
/**
* Constructs a new JImageComponent object.
*
* @param bufferedImage
* Image to load as default image.
*/
public JImageComponent(BufferedImage bufferedImage) {
this.setBufferedImage(bufferedImage);
}
`JImageComponent` 用于在运行时设置/更改图像的方法。该方法还设置了组件的边界,这将在讨论实现可滚动容器的功能时进行解释。
/**
* Sets the buffered image, and updates the components bounds.
*
* @param bufferedImage
* The buffered image to set to.
*/
public void setBufferedImage(BufferedImage bufferedImage) {
this.bufferedImage = bufferedImage;
// Clear the graphics object if null image specified.
// Clear the component bounds if null image specified.
if (this.bufferedImage == null) {
this.imageGraphics = null;
this.setBounds(0, 0, 0, 0);
}
// Set the graphics object.
// Set the component's bounds.
else {
this.imageGraphics = this.bufferedImage.createGraphics();
this.setBounds(0, 0, this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
}
}
4. 实现设置/加载图像的功能
你的类可以实现通过从资源加载图像来设置图像的功能。
`JImageComponent` 允许通过提供一个方法来加载应用程序存档中的图像,该方法通过统一资源定位符 (URL) 参数指定图像。请注意,你可能需要在图像加载后调用 `JImageComponet` 的 `repaint()` 方法来绘制图像。
/**
* Loads image from an URL.
*
* @param imageLocation
* URL to image.
* @throws IOException
* Throws an IOException if file cannot be loaded.
*/
public void loadImage(URL imageLocation) throws IOException {
this.bufferedImage = ImageIO.read(imageLocation);
this.setBufferedImage(this.bufferedImage);
}
你可以自由实现任意数量的方法。例如,`JImageComponent` 还有一个方法可以通过 `File` 参数来加载指定的图像。
/**
* Loads image from a file.
*
* @param imageLocation
* File to image.
* @throws IOException
* Throws an IOException if file cannot be loaded.
*/
public void loadImage(File imageLocation) throws IOException {
this.bufferedImage = ImageIO.read(imageLocation);
this.setBufferedImage(this.bufferedImage);
}
5. 实现绘制图像的功能
这是你类的核心功能。你需要确保你的类能在图像仅部分可见时绘制自身的部分,同时确保当图像被设置/加载和/或被更改/编辑时,类能够重绘自身。根据你扩展的超类,你可能需要覆盖几个与绘制图像相关的。方法。
Swing 的 `JComponent` 为 `JImageComponent` 完成了大部分繁重的工作,它覆盖了 `paint(Graphics)` 方法和两个 `paintImmediately()` 方法。注意了按组件可见矩形指定的图像的绘制。这将在讨论实现可滚动容器的功能时进行解释。
/*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
// Exit if no image is loaded.
if (this.bufferedImage == null) {
return;
}
// Paint the visible region.
Rectangle rectangle = this.getVisibleRect();
paintImmediately(g, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
};
/*
* @see javax.swing.JComponent#paintImmediately(int, int, int, int)
*/
@Override
public void paintImmediately(int x, int y, int width, int height) {
// Exit if no image is loaded.
if (this.bufferedImage == null) {
return;
}
// Paint the region specified.
this.paintImmediately(super.getGraphics(), x, y, width, height);
}
/*
* @see javax.swing.JComponent#paintImmediately(java.awt.Rectangle)
*/
@Override
public void paintImmediately(Rectangle rectangle) {
// Exit if no image is loaded.
if (this.bufferedImage == null) {
return;
}
// Paint the region specified.
this.paintImmediately(super.getGraphics(), rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
为简单起见,`JImageComponent` 有一个 `private` 方法 `paintImmediately(Graphics, int, int, int, int)` 用于实际绘制图像。
/**
* Paints the image onto the component.
*
* @param g
* The Graphics object of the component onto which the
* image region will be painted.
* @param x
* The x value of the region to be painted.
* @param y
* The y value of the region to be painted.
* @param width
* The width of the region to be painted.
* @param height
* The height of the region to be painted.
*/
private void paintImmediately(Graphics g, int x, int y, int width, int height) {
// Exit if no image is loaded.
if (this.bufferedImage == null) {
return;
}
int imageWidth = this.bufferedImage.getWidth();
int imageHeight = this.bufferedImage.getHeight();
// Exit if the dimension is beyond that of the image.
if (x >= imageWidth || y >= imageHeight) {
return;
}
// Calculate the rectangle of the image that should be rendered.
int x1 = x < 0 ? 0 : x;
int y1 = y < 0 ? 0 : y;
int x2 = x + width - 1;
int y2 = y + height - 1;
if (x2 >= imageWidth) {
x2 = imageWidth - 1;
}
if (y2 >= imageHeight) {
y2 = imageHeight - 1;
}
// Draw the image.
g.drawImage(this.bufferedImage, x1, y1, x2, y2, x1, y1, x2, y2, null);
}
6. 实现组件在可滚动容器中使用时的绘制功能
需要解决的一个挑战是在组件用于可滚动容器时进行绘制。例如,程序员可能希望将尺寸大于显示尺寸的图像放置在 `JScrollPane` 容器中。
`JImageComponent` 通过执行以下三项来解决此挑战:
- 设置/加载图像时设置组件的边界。
(这已经完成了。) - 绘制图像时注意组件的可见矩形。
(这已经完成了。) - 为布局管理器提供适当的布局详细信息。
如果你希望确保你的组件在可滚动容器中正确渲染,你可能需要实现一个功能,为布局管理器提供适当的布局详细信息。
`JImageComponent` 通过覆盖以下方法提供所需的布局详细信息:
/**
* Returns the height of the image.
*/
@Override
public int getHeight() {
if (this.bufferedImage == null) {
return 0;
}
return this.bufferedImage.getHeight();
}
/**
* Returns the size of the image.
*/
@Override
public Dimension getPreferredSize() {
if (this.bufferedImage == null) {
return new Dimension(0, 0);
}
return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
}
/**
* Returns the size of the image.
*/
@Override
public Dimension getSize() {
if (this.bufferedImage == null) {
return new Dimension(0, 0);
}
return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
}
/**
* Returns the width of the image.
*/
@Override
public int getWidth() {
if (this.bufferedImage == null) {
return 0;
}
return this.bufferedImage.getWidth();
}
7. 实现编辑图像的功能
在许多情况下,编辑图像比连续更改图像更有效。例如,当你想显示动画时:更改动画图像期间变化的区域比在内存中创建新图像并将其设置在组件中更有效。
`JImageComponent` 通过提供一个方法来访问图像的 `BufferedImage` 对象,以及一个方法来访问图像的 `Graphics` 对象来实现此功能。
/**
* Returns the BufferedImage object for the image.
*
* @return The buffered image.
*/
public BufferedImage getBufferedImage() {
return this.bufferedImage;
}
/**
* Returns the Graphics object for the image.
*/
@Override
public Graphics getGraphics() {
return this.imageGraphics;
}
`JImageComponent` 还提供了一个用于调整图像大小和/或转换图像的方法。
/**
* Resizes and/or converts the image to the specified dimension and type.
*
* Note that the image is "cropped" from the left top corner).
*
* @param width
* The new width of the image.
* @param height
* The new height of the image.
* @param imageType
* The new image type (BufferedImage type).
* @see type java.awt.image.BufferedImage#BufferedImage(int, int, int)
*/
public void resize(int width, int height, int imageType) {
// Create a new image if none is loaded.
if (this.bufferedImage == null) {
setBufferedImage(new BufferedImage(width, height, imageType));
return;
}
// Create a new temporary image.
BufferedImage tempImage = new BufferedImage(width, height, imageType);
int w = this.bufferedImage.getWidth();
int h = this.bufferedImage.getHeight();
// Crop width if necessary.
if (width < w) {
w = width;
}
// Crop height if necessary.
if (height < h) {
h = height;
}
// Copy if the type is the same.
if (this.bufferedImage.getType() == imageType) {
Graphics g = tempImage.getGraphics();
g.drawImage(this.bufferedImage, 0, 0, w, h, null);
}
// Copy pixels to force conversion.
else {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
tempImage.setRGB(x, y, this.bufferedImage.getRGB(x, y));
}
}
}
// Set the new image.
setBufferedImage(tempImage);
}
Using the Code
使用 `JImageComponent` 的程序员应记住在更改/编辑图像后调用 `repaint()` 方法。
希望实现和测试其 `JComponent` 子类的程序员必须遵守 Swing 关于执行的指南(http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html)。
Java Applet 应使用 `SwingUtilities invokeAndWait()`
Java 应用程序可以使用 `SwingUtilities` 的 `invokeAndWait()` 或 `invokeLater()`。
使用 `invokeAndWait` 的示例
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
catch (InvocationTargetException exception) {
// TODO Auto-generated catch block
exception.printStackTrace();
}
catch (InterruptedException exception) {
// TODO Auto-generated catch block
exception.printStackTrace();
}
使用 `invokeLater` 的示例
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
继续前进
这是关于创建 Java™ Swing `JComponent` 类的子类以显示图像的简要介绍。从这里,有很多可以探索的途径。例如,`JImageComponent` 没有提供设置/更改边框的机制。
遵循相同的步骤可以创建 Java™ Abstract Window Toolkit 的 `Component` 类的子类。但需要特别注意:一个陷阱是确保子类提供了双缓冲机制以消除(可能的)闪烁;另一个可能是扩展该类以正确捕获和/或触发事件。
关注点
程序员可能还对使用 Java™ 的 2D 图形 API 进行图像处理感兴趣。
Swing 的 `JComponent` 不是唯一可以为此目的扩展的类。程序员也可以选择扩展 `JPanel` 类。
在业务应用程序中,我曾不得不使用 `JButton` 和 `JLabel` 等原生类来方便使用。2014 年,我改进了 `JImageComponent` 以用于可滚动容器。现在我继续使用 `JImageComponent` 进行一些有趣的事情,例如测试动画、绘制分形和绘制自定义图像。
历史
- 2014 年 7 月:首次提交