創建一個 Swing 組件 —— JImageComponent
- </li>
- </li>
-
</li>
</ul>
介紹Introduction
本文展示了如何使用Java?來創建一個用來在Java? 的applet和/或應用程序中展示圖片的Swing類. 它還包括了使得圖片渲染加快需要的步驟,還有在滾動容器中的使用方法.
為了更好的理解,特別是對于初學者而言,本文使用了 JImageComponent 的實現作為引用,它擴展了 Swing 的 Component.
說明
1. 創建一個子類
創建一個子類繼承擴展你的類。其父類通常是Java? Swing諸多類中的一個.
JImageComponent擴展了 Swing 的 JComponent:
public class JImageComponent extends javax.swing.JComponent {
/* Constructs a new JImageComponent object. */ public JImageComponent() { } }</pre>
2. 創建類變量
你的類將需要幾個變量來持有重要的數據. 通常,這將在你擴張類的功能時進行. 一般情況下,它至少要包含兩個變量:一個BufferedImage對象用來持有需要繪制出來的圖像,還有一個對應的 Graphics 對象.
JImageComponent 包含兩個私有變量:
/* Holds the <code>BufferedImage for the image. / private BufferedImage bufferedImage = null;
/* Holds the Graphics for the image. / private Graphics imageGraphics = null;</pre>
3. 實現圖像的Set/Change功能
你的類將會繪制其變量所描述的圖像。你可能需要實現在構造時設置圖像,或者在運行期間設置或者修改圖像的功能.
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 URL. * * @param imageLocation * URL to image. * @throws Exception * Throws an <code>IOException if file cannot be loaded. */ public void loadImage(URL imageLocation) throws IOException { this.bufferedImage = ImageIO.read(imageLocation); this.setBufferedImage(this.bufferedImage); }
你可以任意實現必要多的方法。 例如JImageComponent,還會有一個用來加載一張由一個文件參數指定的圖像的方法.
/** * Loads image from a file. * * @param imageLocation * File to image * @throws IOException * Throws an <code>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(java.awt.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); }</pre>
簡單起見,JImageComponent 會有一個私有的方法 ,paintImmediately(Graphics, int, int, int, int) ,來做實際上的圖形的繪制.
/** * Paints the image onto the component. * * @param g * The <code>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 width 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通過做下面三件事情來解決了這個問題:
-
當圖片被設置/加載時,設置組件的邊界.
</li>
(這已經是完成了的.) -
在繪制圖像時,注意組件的可是矩形.
</li>
(這也已經是完成了的.) -
提供帶有適當布局細節的布局管理器.
</li> </ul>你可能需要實現在你想要確保你的組件在滾動容器中可以被正確渲染時,能提供帶有適當布局細節的布局管理器的功能.
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(); }</pre>
7. 實現編輯圖像的功能
在編輯圖像時許多的實例將會比持續的修改圖像來得更加高效. 一個例子就是當你想要展示一個動畫的時候,修改每張動畫圖像改變的區域,會比在內存中創建一張圖像并在組件中對其進行設置更加高效.
JImageComponent 通過提供一個訪問其 BufferedImage的方法,以及一個訪問圖像的 Graphics 對象的方法來允許這個功能.
/* Returns the <code>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; }</pre>
JImageComponent 還提供了一個用來調整/轉換圖像的方法:
/* Resizes the image to the given dimensions 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 (<code>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); }</pre>
使用代碼
使用了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和JLable這樣的本地類,以方便使用. 在 2014 年, 我改進了用于滾動容器中的 JImageComponent . 現在,我會使用 JImageComponent 來做一些像測試動畫,繪制分形以及繪制自定義圖像這些有趣的事情.
-