1. main函數(shù)不要通過直接調(diào)用JFrame子類的構造來啟動窗體程序,因為main本身并非運行于EDT中,因此可能會給UI帶來同步問題,建議使用一下方式運行:
1 public static void main(String args[]) { 2 Runnable doCreateAndShowGUI = new Runnable() { 3 @Override 4 public void run() { 5 //該方法為該類的私有靜態(tài)方法,用于啟動JFrame的主界面。 6 createAndShowGUI(); 7 } 8 }; 9 SwingUtilities.invokeLater(doCreateAndShowGUI); 10 }
2. 基于重載JComponent的paint屬性來重繪該組件的所有區(qū)域,paint中的Graphics參數(shù)是自始至終存在并保持一致的,paintComponent中的Graphics參數(shù)則是在swing框架每次調(diào)用paintComponent函數(shù)之前新創(chuàng)建的。
1 public void paint(Graphics g) { 2 // Create an image for the button graphics if necessary 3 if (buttonImage == null || buttonImage.getWidth() != getWidth() || 4 buttonImage.getHeight() != getHeight()) { 5 //該函數(shù)來自Component,用于獲取當前顯示設備的metrics信息, 6 //然后根據(jù)該信息在創(chuàng)建設備兼容的BufferedImage對象。 7 buttonImage = getGraphicsConfiguration().createCompatibleImage(getWidth(), getHeight()); 8 } 9 Graphics gButton = buttonImage.getGraphics(); 10 gButton.setClip(g.getClip()); 11 12 //使用超類中的paint方法將需要顯示的image繪制到該內(nèi)存圖像中,不推薦直接從superclass繪制到g 13 //(子類paint方法的graphics對象),因為這樣做可能會將superclass的graphics中的設備覆蓋掉子 14 //類graphics中的設置,這樣通過間接的內(nèi)存圖像可以避免該問題。 15 super.paint(gButton); 16 17 //這里必須直接目的graphics的composite屬性,如果只是修改內(nèi)存圖像的composite,將只會是組件的 18 //內(nèi)存被渲染成指定的alpha值,但是如果是設置的目的graphics的composite,那么整個組件內(nèi)存圖像的 19 //顯示將被渲染成指定的透明度。 20 Graphics2D g2d = (Graphics2D)g; 21 AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f); 22 g2d.setComposite(newComposite); 23 24 // Copy the button's image to the destination graphics, translucently 25 g2d.drawImage(buttonImage, 0, 0, null); 26 }
3. 不要在EDT(Event Dispatch Thread)中執(zhí)行較長的操作,從而避免UI被凍結的現(xiàn)象發(fā)生。 和操作UI相關的code務必要放到EDT中調(diào)用,否則容易導致死鎖。 SwingUtilities.invodeLater(new Runnable()) 可以在EDT之外執(zhí)行該方法,必將和UI操作相關的coding放到參數(shù)Runnable的實現(xiàn)中。該工具函數(shù)會自動將Runnable投遞到EDT中執(zhí)行。 SwingUtilities.isEventDispatchThread() 如果當前的執(zhí)行線程為EDT,該方法返回true,因此可以直接操作UI,否則表示EDT之外的線程,操作UI的界面不能直接在這里調(diào)用了。
1 private void incrementLabel() { 2 tickCounter++; 3 Runnable code = new Runnable() { 4 public void run() { 5 counter.setText(String.valueOf(tickCounter)); 6 } 7 } 8 9 if (SwingUtilities.isEventDispatchThread()) 10 code.run() 11 else 12 SwingUtilities.invokeLater(code); 13 }
SwingUtilities.invokeAndWait(), 該函數(shù)和invokeLater的主要區(qū)別就是該函數(shù)執(zhí)行后將等待EDT線程執(zhí)行該任務的完成,之后該函數(shù)才正常返回。 非EDT線程可以通過調(diào)用repaint,強制EDT執(zhí)行組件重繪。
4. java.util.Timer 定時器中的TimerTask是在EDT之外的獨立線程中執(zhí)行的,因為不能直接執(zhí)行UI的操作。 java.swing.Timer 定時器中的任務是在EDT中執(zhí)行的,因為可以包含直接操作UI的代碼。 5. 打開抗鋸齒:
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); 以下的例子是根據(jù)OS桌面的設置,為文本的渲染打開抗鋸齒。
1 protected void paintComponent(Graphics g) { 2 Graphics2D g2d = (Graphics2D)g; 3 g2d.setColor(Color.WHITE); 4 g2d.fillRect(0, 0, getWidth(), getHeight()); 5 g2d.setColor(Color.BLACK); 6 //該文本的渲染為非抗鋸齒。 7 g2d.drawString("Unhinted string", 10, 20); 8 9 Toolkit tk = Toolkit.getDefaultToolkit(); 10 desktopHints = (Map)(tk.getDesktopProperty("awt.font.desktophints")); 11 if (desktopHints != null) { 12 g2d.addRenderingHints(desktopHints); 13 } 14 g2d.drawString("Desktop-hinted string", 10, 40); 15 }
6. 圖像縮放的提示: RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR //速度最快,效果最差 RenderingHints.VALUE_INTERPOLATION_BILINEAR //速度和效果適中 RenderingHints.VALUE_INTERPOLATION_BICUBIC //速度最慢,效果最好 7. 通過copyArea可以獲取更好的性能 copyArea(int x,int y,int width,int height,int dx,int dy); 其中,x和y是需要被復制區(qū)域的左上角坐標,width和height分別表示該區(qū)域的寬度和高度,dx和dy表示相對于此區(qū)域的位置,如果為正值,則表示此區(qū)域的右邊和下邊,如果為負值,則表示此區(qū)域的左邊和上邊。
8. 通過逐次迭代的方式可以獲得更好的效果,同時性能也非常不錯,見下例:
1 public BufferedImage getFasterScaledInstance(BufferedImage img, 2 int targetWidth, int targetHeight, Object hint, 3 boolean progressiveBilinear) 4 { 5 int type = (img.getTransparency() == Transparency.OPAQUE) ? 6 BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; 7 BufferedImage ret = img; 8 BufferedImage scratchImage = null; 9 Graphics2D g2 = null; 10 int w, h; 11 int prevW = ret.getWidth(); 12 int prevH = ret.getHeight(); 13 boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE; 14 15 if (progressiveBilinear) { 16 // Use multi-step technique: start with original size, then 17 // scale down in multiple passes with drawImage() 18 // until the target size is reached 19 w = img.getWidth(); 20 h = img.getHeight(); 21 } else { 22 // Use one-step technique: scale directly from original 23 // size to target size with a single drawImage() call 24 w = targetWidth; 25 h = targetHeight; 26 } 27 28 do { 29 if (progressiveBilinear && w > targetWidth) { 30 w /= 2; 31 if (w < targetWidth) 32 w = targetWidth; 33 } 34 35 if (progressiveBilinear && h > targetHeight) { 36 h /= 2; 37 if (h < targetHeight) 38 h = targetHeight; 39 } 40 41 if (scratchImage == null || isTranslucent) { 42 // Use a single scratch buffer for all iterations 43 // and then copy to the final, correctly-sized image 44 // before returning 45 scratchImage = new BufferedImage(w, h, type); 46 g2 = scratchImage.createGraphics(); 47 } 48 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); 49 g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null); 50 prevW = w; 51 prevH = h; 52 53 ret = scratchImage; 54 } while (w != targetWidth || h != targetHeight); 55 56 if (g2 != null) 57 g2.dispose(); 58 59 // If we used a scratch buffer that is larger than our target size, 60 // create an image of the right size and copy the results into it 61 if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) { 62 scratchImage = new BufferedImage(targetWidth, targetHeight, type); 63 g2 = scratchImage.createGraphics(); 64 g2.drawImage(ret, 0, 0, null); 65 g2.dispose(); 66 ret = scratchImage; 67 } 68 return ret; 69 }
9. 將一個普通的圖像復制到一個顯示設備兼容的圖像中,以提高后期渲染操作的性能。 注:由于顯示設備兼容圖像的圖像數(shù)據(jù)存儲方式和設備顯示時的數(shù)據(jù)讀取方式一致,不需要額外的轉(zhuǎn)換,因此只是需要簡單且高效的內(nèi)存copy即可。
1 void drawCompatibleImage(BufferedImage suboptimalImage) { 2 GraphicsConfiguration gc = getConfiguration(); 3 BufferedImage compatibleImage = gc.createCompatibleImage( 4 suboptimalImage.getWidth(),suboptimalImage.getHeight()); 5 Graphics g = compatibleImage.getGraphics(); 6 g.drawImage(suboptimalImage,0,0,null); 7 }
制作一個自己的工具類便于創(chuàng)建設備兼容的圖像。
1 public class MineCompatible { 2 public static GraphicsConfiguration getConfiguration() { 3 return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); 4 } 5 6 public static BufferedImage createCompatibleImage(BufferedImage image) { 7 return createCompatibleImage(image,image.getWidth(),image.getHeight()); 8 } 9 10 public static BufferedImage createCompatibleImage(BufferedImage image,int width,int height) { 11 return getConfiguration().createCompatibleImage(width,height,image.getTransparency()); 12 } 13 14 public static BufferedImage createCompatibleImage(int width,int height) { 15 return getConfiguration().createCompatibleImage(width,height); 16 } 17 18 public static BufferedImage createCompatibleTranslucentImage(int width,int height) { 19 return getConfiguration().createCompatibleImage(width,height,Transparency.TRANSLUCENT); 20 } 21 22 public static BufferedImage loadCompatibleImage(URL resource) throws IOException { 23 BufferedImage image = ImageIO.read(resource); 24 return toCompatibleImage(image); 25 } 26 27 public static BufferedImage toCompatibleImage(BufferedImage image) { 28 GraphicsConfiguration gc = getConfiguration(); 29 if (image.getColorModel().equals(gc.getColorModel()) 30 return image; 31 32 BufferedImage compatibleImage = gc.createCompatibleImage( 33 image.getWidth(),image.getHeight(),image.getTransparency()); 34 Graphics g = compatibleImage.getGraphics(); 35 g.drawImage(image,0,0,null); 36 g.dispose(); 37 return compatibleImage; 38 } 39 }
10. 托管圖像:非托管的圖像在設備顯示時,是從system memory通過總線copy到VRAM的,托管圖像將會在VRAM中創(chuàng)建一個system memory圖像的副本,需要設備顯示時,直接將VRAM中的副本copy到VRAM中用于顯示,從而避免了通過總線將數(shù)據(jù)從system memory拷貝到VRAM了。 抑制圖像自動托管的兩個因素: 1) 通過Image的Raster,調(diào)用dataBuffer方法直接獲取并且操作顯示數(shù)據(jù)時,java 2d將自動關閉圖像托管,由于此時是外部代碼直接以數(shù)組的方式操作顯示圖像數(shù)據(jù),因此java 2d無法監(jiān)控顯示數(shù)據(jù)是否已經(jīng)被改變。注:一旦獲取dataBuffer后,無法在通過將dataBuffer交還的方法重新使該圖像成為托管圖像。 DataBuffer dataBuffer = image.getRaster().getDataBuffer(); 2) 頻繁的渲染到圖像,和1)不同,java 2d可以根據(jù)實際情況動態(tài)的處理是否需要將該圖像設置為托管圖像。 由于渲染操作頻繁,致使從system memory到與VRAM中的副本進行同步的操作變?yōu)轭~外的操作。
11. 通過保存中間圖像的方式盡可能減少實時渲染的操作,在paintComponent中,通過將中間圖像copy到swing后臺緩沖,這樣可以極大的提高顯示效率,見下例:
1 private void drawScaled(Graphics g) { 2 long startTime, endTime, totalTime; 3 4 // Scaled image 5 ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, 6 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 7 startTime = System.nanoTime(); 8 for (int i = 0; i < 100; ++i) { 9 g.drawImage(picture, SCALE_X, DIRECT_Y, scaleW, scaleH, null); 10 } 11 endTime = System.nanoTime(); 12 totalTime = (endTime - startTime) / 1000000; 13 g.setColor(Color.BLACK); 14 g.drawString("Direct: " + ((float)totalTime/100) + " ms", 15 SCALE_X, DIRECT_Y + scaleH + 20); 16 System.out.println("scaled: " + totalTime); 17 18 // Intermediate Scaled 19 // First, create the intermediate image 20 if (scaledImage == null || 21 scaledImage.getWidth() != scaleW || 22 scaledImage.getHeight() != scaleH) 23 { 24 GraphicsConfiguration gc = getGraphicsConfiguration(); 25 scaledImage = gc.createCompatibleImage(scaleW, scaleH); 26 Graphics gImg = scaledImage.getGraphics(); 27 ((Graphics2D)gImg).setRenderingHint(RenderingHints.KEY_INTERPOLATION, 28 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 29 gImg.drawImage(picture, 0, 0, scaleW, scaleH, null); 30 } 31 // Now, copy the intermediate image into place 32 startTime = System.nanoTime(); 33 for (int i = 0; i < 100; ++i) { 34 g.drawImage(scaledImage, SCALE_X, INTERMEDIATE_Y, null); 35 } 36 endTime = System.nanoTime(); 37 totalTime = (endTime - startTime) / 1000000; 38 g.drawString("Intermediate: " + ((float)totalTime/100) + " ms", 39 SCALE_X, INTERMEDIATE_Y + scaleH + 20); 40 System.out.println("Intermediate scaled: " + totalTime); 41 }
需要實時渲染和計算的不規(guī)則圖形也可以很好的利用中間圖像,以避免更多的計算,但是對于不規(guī)則圖像,需要考慮將中間圖像的底色設置為透明,以便在copy的過程中只是復制這個圖形的顏色,而不會復制這個圖像的背景色,見下例(其中BITMASK為設置透明背景):
1 private void renderSmiley(Graphics g, int x, int y) { 2 Graphics2D g2d = (Graphics2D)g.create(); 3 4 // Yellow face 5 g2d.setColor(Color.yellow); 6 g2d.fillOval(x, y, SMILEY_SIZE, SMILEY_SIZE); 7 8 // Black eyes 9 g2d.setColor(Color.black); 10 g2d.fillOval(x + 30, y + 30, 8, 8); 11 g2d.fillOval(x + 62, y + 30, 8, 8); 12 13 // Black outline 14 g2d.drawOval(x, y, SMILEY_SIZE, SMILEY_SIZE); 15 16 // Black smile 17 g2d.setStroke(new BasicStroke(3.0f)); 18 g2d.drawArc(x + 20, y + 20, 60, 60, 190, 160); 19 20 g2d.dispose(); 21 } 22 23 /** 24 * Draws both the direct and intermediate-image versions of a 25 * smiley face, timing both variations. 26 */ 27 private void drawSmiley(Graphics g) { 28 long startTime, endTime, totalTime; 29 30 // Draw smiley directly 31 startTime = System.nanoTime(); 32 for (int i = 0; i < 100; ++i) { 33 renderSmiley(g, SMILEY_X, DIRECT_Y); 34 } 35 endTime = System.nanoTime(); 36 totalTime = (endTime - startTime) / 1000000; 37 g.setColor(Color.BLACK); 38 g.drawString("Direct: " + ((float)totalTime/100) + " ms", 39 SMILEY_X, DIRECT_Y + SMILEY_SIZE + 20); 40 System.out.println("Direct: " + totalTime); 41 42 // Intermediate Smiley 43 // First, create the intermediate image if necessary 44 if (smileyImage == null) { 45 GraphicsConfiguration gc = getGraphicsConfiguration(); 46 smileyImage = gc.createCompatibleImage( 47 SMILEY_SIZE + 1, SMILEY_SIZE + 1, Transparency.BITMASK); 48 Graphics2D gImg = (Graphics2D)smileyImage.getGraphics(); 49 renderSmiley(gImg, 0, 0); 50 gImg.dispose(); 51 } 52 // Now, copy the intermediate image 53 startTime = System.nanoTime(); 54 for (int i = 0; i < 100; ++i) { 55 g.drawImage(smileyImage, SMILEY_X, INTERMEDIATE_Y, null); 56 } 57 endTime = System.nanoTime(); 58 totalTime = (endTime - startTime) / 1000000; 59 g.drawString("Intermediate: " + ((float)totalTime/100) + " ms", 60 SMILEY_X, INTERMEDIATE_Y + SMILEY_SIZE + 20); 61 System.out.println("intermediate smiley: " + totalTime); 62 }
如果中間圖像使用半透明效果,不像以上兩個例子分為使用了不透明和全透明背景,硬件加速將會被抑制。
1. main函數(shù)不要通過直接調(diào)用JFrame子類的構造來啟動窗體程序,因為main本身并非運行于EDT中,因此可能會給UI帶來同步問題,建議使用一下方式運行:
public static void main(String args[]) { Runnable doCreateAndShowGUI = new Runnable() { @Override public void run() { createAndShowGUI(); //該方法為該類的私有靜態(tài)方法,用于啟動JFrame的主界面。 } }; SwingUtilities.invokeLater(doCreateAndShowGUI); } 2. 基于重載JComponent的paint屬性來重繪該組件的所有區(qū)域,paint中的Graphics參數(shù)是自始至終存在并保持一致的,paintComponent中的Graphics參數(shù)則是在swing框架每次調(diào)用paintComponent函數(shù)之前新創(chuàng)建的。 public void paint(Graphics g) { // Create an image for the button graphics if necessary if (buttonImage == null || buttonImage.getWidth() != getWidth() || buttonImage.getHeight() != getHeight()) { //該函數(shù)來自Component,用于獲取當前顯示設備的metrics信息,然后根據(jù)該信息在創(chuàng)建設備兼容的BufferedImage對象。 buttonImage = getGraphicsConfiguration().createCompatibleImage(getWidth(), getHeight()); } Graphics gButton = buttonImage.getGraphics(); gButton.setClip(g.getClip()); //使用超類中的paint方法將需要顯示的image繪制到該內(nèi)存圖像中,不推薦直接從superclass繪制到g(子類paint方法的graphics對象),因為這樣做可能會將superclass的graphics中的設備覆蓋掉子類graphics中的設置,這樣通過間接的內(nèi)存圖像可以避免該問題。 super.paint(gButton); //這里必須直接目的graphics的composite屬性,如果只是修改內(nèi)存圖像的composite,將只會是組件的內(nèi)存被渲染成指定的alpha值,但是如果是設置的目的graphics的composite,那么整個組件內(nèi)存圖像的顯示將被渲染成指定的透明度。 Graphics2D g2d = (Graphics2D)g; AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f); g2d.setComposite(newComposite); // Copy the button's image to the destination graphics, translucently g2d.drawImage(buttonImage, 0, 0, null); }
3. 不要在EDT(Event Dispatch Thread)中執(zhí)行較長的操作,從而避免UI被凍結的現(xiàn)象發(fā)生。 和操作UI相關的code務必要放到EDT中調(diào)用,否則容易導致死鎖。 SwingUtilities.invodeLater(new Runnable()) 可以在EDT之外執(zhí)行該方法,必將和UI操作相關的coding放到參數(shù)Runnable的實現(xiàn)中。該工具函數(shù)會自動將Runnable投遞到EDT中執(zhí)行。 SwingUtilities.isEventDispatchThread() 如果當前的執(zhí)行線程為EDT,該方法返回true,因此可以直接操作UI,否則表示EDT之外的線程,操作UI的界面不能直接在這里調(diào)用了。 private void incrementLabel() { tickCounter++; Runnable code = new Runnable() { public void run() { counter.setText(String.valueOf(tickCounter)); } } if (SwingUtilities.isEventDispatchThread()) code.run() else SwingUtilities.invokeLater(code); } SwingUtilities.invokeAndWait(), 該函數(shù)和invokeLater的主要區(qū)別就是該函數(shù)執(zhí)行后將等待EDT線程執(zhí)行該任務的完成,之后該函數(shù)才正常返回。 非EDT線程可以通過調(diào)用repaint,強制EDT執(zhí)行組件重繪。 4. java.util.Timer 定時器中的TimerTask是在EDT之外的獨立線程中執(zhí)行的,因為不能直接執(zhí)行UI的操作。 java.swing.Timer 定時器中的任務是在EDT中執(zhí)行的,因為可以包含直接操作UI的代碼。 5. 打開抗鋸齒:g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); 以下的例子是根據(jù)OS桌面的設置,為文本的渲染打開抗鋸齒。 protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D)g; g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, getWidth(), getHeight()); g2d.setColor(Color.BLACK); //該文本的渲染為非抗鋸齒。 g2d.drawString("Unhinted string", 10, 20);
Toolkit tk = Toolkit.getDefaultToolkit(); desktopHints = (Map)(tk.getDesktopProperty("awt.font.desktophints")); if (desktopHints != null) { g2d.addRenderingHints(desktopHints); } g2d.drawString("Desktop-hinted string", 10, 40); } 6. 圖像縮放的提示: RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR //速度最快,效果最差 RenderingHints.VALUE_INTERPOLATION_BILINEAR //速度和效果適中 RenderingHints.VALUE_INTERPOLATION_BICUBIC //速度最慢,效果最好 7. 通過copyArea可以獲取更好的性能 copyArea(int x,int y,int width,int height,int dx,int dy); 其中,x和y是需要被復制區(qū)域的左上角坐標,width和height分別表示該區(qū)域的寬度和高度,dx和dy表示相對于此區(qū)域的位置,如果為正值,則表示此區(qū)域的右邊和下邊,如果為負值,則表示此區(qū)域的左邊和上邊。
8. 通過逐次迭代的方式可以獲得更好的效果,同時性能也非常不錯,見下例: public BufferedImage getFasterScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean progressiveBilinear) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage ret = img; BufferedImage scratchImage = null; Graphics2D g2 = null; int w, h; int prevW = ret.getWidth(); int prevH = ret.getHeight(); boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
if (progressiveBilinear) { // Use multi-step technique: start with original size, then // scale down in multiple passes with drawImage() // until the target size is reached w = img.getWidth(); h = img.getHeight(); } else { // Use one-step technique: scale directly from original // size to target size with a single drawImage() call w = targetWidth; h = targetHeight; } do { if (progressiveBilinear && w > targetWidth) { w /= 2; if (w < targetWidth) w = targetWidth; }
if (progressiveBilinear && h > targetHeight) { h /= 2; if (h < targetHeight) h = targetHeight; }
if (scratchImage == null || isTranslucent) { // Use a single scratch buffer for all iterations // and then copy to the final, correctly-sized image // before returning scratchImage = new BufferedImage(w, h, type); g2 = scratchImage.createGraphics(); } g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null); prevW = w; prevH = h;
ret = scratchImage; } while (w != targetWidth || h != targetHeight); if (g2 != null) g2.dispose();
// If we used a scratch buffer that is larger than our target size, // create an image of the right size and copy the results into it if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) { scratchImage = new BufferedImage(targetWidth, targetHeight, type); g2 = scratchImage.createGraphics(); g2.drawImage(ret, 0, 0, null); g2.dispose(); ret = scratchImage; } return ret; } 9. 將一個普通的圖像復制到一個顯示設備兼容的圖像中,以提高后期渲染操作的性能。 注:由于顯示設備兼容圖像的圖像數(shù)據(jù)存儲方式和設備顯示時的數(shù)據(jù)讀取方式一致,不需要額外的轉(zhuǎn)換,因此只是需要簡單且高效的內(nèi)存copy即可。 void drawCompatibleImage(BufferedImage suboptimalImage) { GraphicsConfiguration gc = getConfiguration(); BufferedImage compatibleImage = gc.createCompatibleImage( suboptimalImage.getWidth(),suboptimalImage.getHeight()); Graphics g = compatibleImage.getGraphics(); g.drawImage(suboptimalImage,0,0,null); } 制作一個自己的工具類便于創(chuàng)建設備兼容的圖像。 public class MineCompatible { public static GraphicsConfiguration getConfiguration() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); }
public static BufferedImage createCompatibleImage(BufferedImage image) { return createCompatibleImage(image,image.getWidth(),image.getHeight()); } public static BufferedImage createCompatibleImage(BufferedImage image,int width,int height) { return getConfiguration().createCompatibleImage(width,height,image.getTransparency()); } public static BufferedImage createCompatibleImage(int width,int height) { return getConfiguration().createCompatibleImage(width,height); } public static BufferedImage createCompatibleTranslucentImage(int width,int height) { return getConfiguration().createCompatibleImage(width,height,Transparency.TRANSLUCENT); } public static BufferedImage loadCompatibleImage(URL resource) throws IOException { BufferedImage image = ImageIO.read(resource); return toCompatibleImage(image); } public static BufferedImage toCompatibleImage(BufferedImage image) { GraphicsConfiguration gc = getConfiguration(); if (image.getColorModel().equals(gc.getColorModel()) return image; BufferedImage compatibleImage = gc.createCompatibleImage( image.getWidth(),image.getHeight(),image.getTransparency()); Graphics g = compatibleImage.getGraphics(); g.drawImage(image,0,0,null); g.dispose(); return compatibleImage; } } 10. 托管圖像:非托管的圖像在設備顯示時,是從system memory通過總線copy到VRAM的,托管圖像將會在VRAM中創(chuàng)建一個system memory圖像的副本,需要設備顯示時,直接將VRAM中的副本copy到VRAM中用于顯示,從而避免了通過總線將數(shù)據(jù)從system memory拷貝到VRAM了。 抑制圖像自動托管的兩個因素: 1) 通過Image的Raster,調(diào)用dataBuffer方法直接獲取并且操作顯示數(shù)據(jù)時,java 2d將自動關閉圖像托管,由于此時是外部代碼直接以數(shù)組的方式操作顯示圖像數(shù)據(jù),因此java 2d無法監(jiān)控顯示數(shù)據(jù)是否已經(jīng)被改變。注:一旦獲取dataBuffer后,無法在通過將dataBuffer交還的方法重新使該圖像成為托管圖像。 DataBuffer dataBuffer = image.getRaster().getDataBuffer(); 2) 頻繁的渲染到圖像,和1)不同,java 2d可以根據(jù)實際情況動態(tài)的處理是否需要將該圖像設置為托管圖像。 由于渲染操作頻繁,致使從system memory到與VRAM中的副本進行同步的操作變?yōu)轭~外的操作。
11. 通過保存中間圖像的方式盡可能減少實時渲染的操作,在paintComponent中,通過將中間圖像copy到swing后臺緩沖,這樣可以極大的提高顯示效率,見下例: private void drawScaled(Graphics g) { long startTime, endTime, totalTime; // Scaled image ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); startTime = System.nanoTime(); for (int i = 0; i < 100; ++i) { g.drawImage(picture, SCALE_X, DIRECT_Y, scaleW, scaleH, null); } endTime = System.nanoTime(); totalTime = (endTime - startTime) / 1000000; g.setColor(Color.BLACK); g.drawString("Direct: " + ((float)totalTime/100) + " ms", SCALE_X, DIRECT_Y + scaleH + 20); System.out.println("scaled: " + totalTime); // Intermediate Scaled // First, create the intermediate image if (scaledImage == null || scaledImage.getWidth() != scaleW || scaledImage.getHeight() != scaleH) { GraphicsConfiguration gc = getGraphicsConfiguration(); scaledImage = gc.createCompatibleImage(scaleW, scaleH); Graphics gImg = scaledImage.getGraphics(); ((Graphics2D)gImg).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); gImg.drawImage(picture, 0, 0, scaleW, scaleH, null); } // Now, copy the intermediate image into place startTime = System.nanoTime(); for (int i = 0; i < 100; ++i) { g.drawImage(scaledImage, SCALE_X, INTERMEDIATE_Y, null); } endTime = System.nanoTime(); totalTime = (endTime - startTime) / 1000000; g.drawString("Intermediate: " + ((float)totalTime/100) + " ms", SCALE_X, INTERMEDIATE_Y + scaleH + 20); System.out.println("Intermediate scaled: " + totalTime); } 需要實時渲染和計算的不規(guī)則圖形也可以很好的利用中間圖像,以避免更多的計算,但是對于不規(guī)則圖像,需要考慮將中間圖像的底色設置為透明,以便在copy的過程中只是復制這個圖形的顏色,而不會復制這個圖像的背景色,見下例(其中BITMASK為設置透明背景): private void renderSmiley(Graphics g, int x, int y) { Graphics2D g2d = (Graphics2D)g.create(); // Yellow face g2d.setColor(Color.yellow); g2d.fillOval(x, y, SMILEY_SIZE, SMILEY_SIZE); // Black eyes g2d.setColor(Color.black); g2d.fillOval(x + 30, y + 30, 8, 8); g2d.fillOval(x + 62, y + 30, 8, 8); // Black outline g2d.drawOval(x, y, SMILEY_SIZE, SMILEY_SIZE); // Black smile g2d.setStroke(new BasicStroke(3.0f)); g2d.drawArc(x + 20, y + 20, 60, 60, 190, 160); g2d.dispose(); } /** * Draws both the direct and intermediate-image versions of a * smiley face, timing both variations. */ private void drawSmiley(Graphics g) { long startTime, endTime, totalTime;
// Draw smiley directly startTime = System.nanoTime(); for (int i = 0; i < 100; ++i) { renderSmiley(g, SMILEY_X, DIRECT_Y); } endTime = System.nanoTime(); totalTime = (endTime - startTime) / 1000000; g.setColor(Color.BLACK); g.drawString("Direct: " + ((float)totalTime/100) + " ms", SMILEY_X, DIRECT_Y + SMILEY_SIZE + 20); System.out.println("Direct: " + totalTime); // Intermediate Smiley // First, create the intermediate image if necessary if (smileyImage == null) { GraphicsConfiguration gc = getGraphicsConfiguration(); smileyImage = gc.createCompatibleImage( SMILEY_SIZE + 1, SMILEY_SIZE + 1, Transparency.BITMASK); Graphics2D gImg = (Graphics2D)smileyImage.getGraphics(); renderSmiley(gImg, 0, 0); gImg.dispose(); } // Now, copy the intermediate image startTime = System.nanoTime(); for (int i = 0; i < 100; ++i) { g.drawImage(smileyImage, SMILEY_X, INTERMEDIATE_Y, null); } endTime = System.nanoTime(); totalTime = (endTime - startTime) / 1000000; g.drawString("Intermediate: " + ((float)totalTime/100) + " ms", SMILEY_X, INTERMEDIATE_Y + SMILEY_SIZE + 20); System.out.println("intermediate smiley: " + totalTime); }
如果中間圖像使用半透明效果,不像以上兩個例子分為使用了不透明和全透明背景,硬件加速將會被抑制。 12. 明確的告訴java 2d你將要完成的繪制,而不是使用一個更為通用的方式,這樣能夠帶來更好的性能。 //畫線的bad way Shape line = new Line2D.Double(LINE_X, BAD_Y, LINE_X + 50, BAD_Y + 50); g2d.draw(line); //畫線的good way g.drawLine(LINE_X, GOOD_Y, LINE_X + 50, GOOD_Y + 50); //畫rectangle的bad way Shape rect = new Rectangle(RECT_X, BAD_Y, 50, 50); g2d.fill(rect); //畫rectangle的good way g.fillRect(RECT_X, GOOD_Y, 50, 50); 13. 圖像合成,其中最為有用的三個規(guī)則分別是clear、SrcOver(swing缺省)和SrcIn。 Clear:是擦掉一個圖像的背景以便使他變得完全透明的一個容易的方式,可以將其理解為Photoshop中的橡皮擦,通過Clear可以清除任意形狀的區(qū)域。 public void exampleForClear() { BufferedImage image = new BufferedImage(200,200,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = image.createGraphics(); //draw something here. //... //Erase the content of the image. g2.setComposite(AlphaComposite.Clear); //The color,the Paint, etc. do not matter g2.fillRect(0,0,image.getWidth(),image.getHeight()); } SrcOver: 其運算公式為Ar = As + Ad * (1 - As), Cr = Cs + Cd * (1 - As), 其中Ar為結果Alpha,As表示源圖像的Alpha,As為目的圖像的Alpha,Cr表示(RGB)中每個通道的結果值,Cs為源圖像中(RGB)單個通道的值,Cd為目的圖像的單個通道值。 一般的用法為在目的圖像上繪制半透明的源圖像。 SrcIn:位于目的地內(nèi)部的那部分源代替目的地,位于目的地之外的那部分源丟棄掉。 protected void paintComponent(Graphics g) { BufferedImage temp = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = temp.createGraphics(); if (shadow.isSelected()) { int x = (getWidth() - image.getWidth()) / 2; int y = (getHeight() - image.getHeight()) / 2; g2.drawImage(image, x + 4, y + 10, null);
Composite oldComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 0.75f)); g2.setColor(Color.BLACK); g2.fillRect(0, 0, getWidth(), getHeight()); g2.setComposite(oldComposite); g2.drawImage(image, x, y, null); } else { int x = (getWidth() - image.getWidth()) / 2; int y = (getHeight() - image.getHeight()) / 2; g2.drawImage(image, x, y, null);
Composite oldComposite = g2.getComposite(); g2.setComposite(AlphaComposite.SrcIn); x = (getWidth() - landscape.getWidth()) / 2; y = (getHeight() - landscape.getHeight()) / 2; g2.drawImage(landscape, x, y, null); g2.setComposite(oldComposite); } g2.dispose(); g.drawImage(temp, 0, 0, null); }
14. 利用漸變完成的反射效果,主要分為3個步驟完成,見下例: private BufferedImage createReflection(BufferedImage image) { int height = image.getHeight(); BufferedImage result = new BufferedImage(image.getWidth(), height * 2, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = result.createGraphics(); //1. 想渲染正常物體一樣渲染它。 g2.drawImage(image, 0, 0, null); //2. 渲染這個物體上下顛倒的一個副本 g2.scale(1.0, -1.0); g2.drawImage(image, 0, -height - height, null); g2.scale(1.0, -1.0);
// Move to the origin of the clone g2.translate(0, height); //3. 模糊這個副本的一部分以使它淡出,隨著它遠離最初的物體。 GradientPaint mask; //目的顏色RGB無關重要,alpha值必須為0。 mask = new GradientPaint(0, 0, new Color(1.0f, 1.0f, 1.0f, 0.5f), 0, height / 2, new Color(1.0f, 1.0f, 1.0f, 0.0f)); Paint oldPaint = g2.getPaint(); g2.setPaint(mask); // Sets the alpha composite g2.setComposite(AlphaComposite.DstIn); //盡量覆蓋全部顛倒圖像,以避免因覆蓋不全而造成的偽影。 g2.fillRect(0, 0, image.getWidth(), height); g2.dispose(); return result; } 15. 線性漸變LinearGradientPaint(float startX,float startY,float endX,float endY,float[] fractions,Color[] colors),這里包含兩個數(shù)組參數(shù),其中第一個float類型的數(shù)組包含漸變中使用的每個顏色的位置。每一對位置/顏色被稱為一個停頓,見下例: protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; Paint oldPaint = g2.getPaint(); LinearGradientPaint p; p = new LinearGradientPaint(0.0f, 0.0f, 0.0f, 20.0f, new float[] { 0.0f, 0.5f, 0.501f, 1.0f }, new Color[] { new Color(0x63a5f7), new Color(0x3799f4), new Color(0x2d7eeb), new Color(0x30a5f9) }); g2.setPaint(p); g2.fillRect(0, 0, getWidth(), 21); g2.setPaint(oldPaint); super.paintComponent(g); }
16. 優(yōu)化漸變的3個技巧: 1) 緩存這個漸變:該解決方案是把這個漸變變成一個圖像并僅僅繪制那個圖像,但是缺點是需要消耗更多的內(nèi)存。 protected void paintComponent(Graphics g) { if (gradientImage == null || gradientImage.getWidth() != getWidth() || gradientImage.getHeight() != getHeight()) { gradientImage = new BufferedImage(getWidth(),getHeigth(),BufferedImage.TYPE_INT_RGB); Graphics2D g2d = (Graphics2D)gradientImage.getGraphics(); g2d.setPaint(backgroundGradient); g2d.fillRect(0,0,getWidth(),getHeight()); g2d.dispose() } g.drawImage(gradientImage,0,0,null); } 2) 更巧妙的緩存:當繪制一個垂直或者水平漸變時,每一列或者每一行都是相同的,因此可以只是保留一列或者一行的數(shù)據(jù),然在需要覆蓋漸變時在拉伸該列或者該行。 protected void paintComponent(Graphics g) { if (gradientImage == null || gradientImage.getHeight() != getHeight()) { gradientImage = MineCompatible.createCompatibleImage(1,getHeight()); Graphics2D g2d = (Graphics2D)gradientImage.getGraphics(); g2d.setPaint(backgroundGradient); g2d.fillRect(0,0,1,getHeight()); g2d.dispose(); } g.drawImage(gradientImage,0,0,getWidth(),getHeigth(),null); } 3) 使用循環(huán)漸變的優(yōu)化:如果漸變只是被覆蓋組件高度的一半時,如以下代碼: protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D)g.createGraphics(); g2d.setPaint(new GradientPaint(0.0f,0.0f,Color.WHITE,0.0f,getHeigth()/2.0f,Color.DARK_GRAY); g2d.fillRect(0,0,getWidth(),getHeight()); } 該代碼將會從組件的(0,0)到(0,height/2)繪制漸變,同時利用這個漸變的最后顏色填充剩下的像素,為了做到這一點,java 2d將不斷的檢查是否當前的像素位于這個漸變區(qū)域的外面,因此對于成千上萬的像素來說,將會花費很多時間。如果使用循環(huán)漸變的方式,java 2d內(nèi)部在渲染的時候?qū)贿M行該判斷,從而大大提高了整體的效率,見如下代碼: //循環(huán)GradientPaint new GradientPaint(new Point(0,0),Color.WHITE,new Point(0,getHeight()),Color.DARK_GRAY,true/*該標志表示循環(huán)*/); //循環(huán)LinearGradientPaint new LinearGradientPaint(new Point(0,0),new Point(0,getHeigth()),new float[] {0.0f,1.0f},new Color[] {Color.WHITE,Color.DARK_GRAY},MultipleGradientPaint.CycleMethod.REPEAT);
17. 圖像處理: 1) AffineTransformOp public BufferedImage makeeAffineTransformOp(BufferedImage srcImage) { //高度和寬度均為源圖像的50%。 AffineTransform transform = AffineTransform.getScaleInstance(0.5, 0.5); AffineTransformOp op = new AffineTransformOp(transform,AffineTransformOp.TYPE_BILINEAR); return op.filter(srcImage,null); } 2) RescaleOp private BufferedImage makeRescaleOp(BufferedImage srcImage) { BufferedImage dstImage = null; float[] factors = new float[] { 1.4f, 1.4f, 1.4f }; float[] offsets = new float[] { 0.0f, 0.0f, 30.0f }; //RGB每個顏色通道的亮度增加40%,B通道增加30/256=12%的顏色分量。 RescaleOp op = new RescaleOp(factors, offsets, null); return op.filter(srcImage,null); }
18. 玻璃窗格的基本繪制技巧: 1). 給當前JFrame安裝玻璃窗格,安裝后該玻璃窗格的缺省顯示方式是隱藏顯示,即setVisible(false),如果之前JFrame已經(jīng)使用了玻璃窗格,本次操作只是替換一個新的對象,那么該窗格的visible屬性將和原有窗格的visible屬性保持一致。 public ApplicationFrame() { //ApplicationFrame為應用程序的主窗體,繼承自JFrame initComponents(); //安裝玻璃窗格,glassPane是JComponent的子類。 setGlassPane(glassPane = new ProgressGlassPane()); } 2). 實現(xiàn)玻璃窗格的paintComponent方法 protected void paintComponent(Graphics g) { // enables anti-aliasing Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // gets the current clipping area Rectangle clip = g.getClipBounds(); // sets a 65% translucent composite AlphaComposite alpha = AlphaComposite.SrcOver.derive(0.65f); Composite composite = g2.getComposite(); g2.setComposite(alpha); // fills the background g2.setColor(getBackground()); g2.fillRect(clip.x, clip.y, clip.width, clip.height); // centers the progress bar on screen FontMetrics metrics = g.getFontMetrics(); int x = (getWidth() - BAR_WIDTH) / 2; int y = (getHeight() - BAR_HEIGHT - metrics.getDescent()) / 2; // draws the text g2.setColor(TEXT_COLOR); g2.drawString(message, x, y); // goes to the position of the progress bar y += metrics.getDescent(); // computes the size of the progress indicator int w = (int) (BAR_WIDTH * ((float) progress / 100.0f)); int h = BAR_HEIGHT; // draws the content of the progress bar Paint paint = g2.getPaint(); // bar's background Paint gradient = new GradientPaint(x, y, GRADIENT_COLOR1, x, y + h, GRADIENT_COLOR2); g2.setPaint(gradient); g2.fillRect(x, y, BAR_WIDTH, BAR_HEIGHT); // actual progress gradient = new LinearGradientPaint(x, y, x, y + h,GRADIENT_FRACTIONS, GRADIENT_COLORS); g2.setPaint(gradient); g2.fillRect(x, y, w, h); g2.setPaint(paint); // draws the progress bar border g2.drawRect(x, y, BAR_WIDTH, BAR_HEIGHT); g2.setComposite(composite); } 3). 主窗體中的工作線程需要調(diào)用的方法,以便更新進度條的顯示狀態(tài) public void setProgress(int progress) { int oldProgress = this.progress; this.progress = progress; // computes the damaged area FontMetrics metrics = getGraphics().getFontMetrics(getFont()); int w = (int) (BAR_WIDTH * ((float) oldProgress / 100.0f)); int x = w + (getWidth() - BAR_WIDTH) / 2; int y = (getHeight() - BAR_HEIGHT) / 2; y += metrics.getDescent() / 2; w = (int) (BAR_WIDTH * ((float) progress / 100.0f)) - w; int h = BAR_HEIGHT; //The reason why uses the following repaint(x, y, w, h) not repaint() is to //avoid repainting all the area to improve the performance. repaint(x, y, w, h); } 19. 玻璃窗格中屏蔽輸入事件,上例中繪制的玻璃窗格只是完成了基本的顯示效果,用戶仍然可以操作玻璃窗格覆蓋下的控件,這樣會給用戶帶來非常迷惑的感覺,因此需要屏蔽玻璃窗格覆蓋下的控件獲取來自鼠標和鍵盤的事件。 1). 為玻璃窗格控件自身添加空的鼠標和鍵盤的監(jiān)聽器 public ProgressGlassPane() { // blocks all user input addMouseListener(new MouseAdapter() { }); addMouseMotionListener(new MouseMotionAdapter() { }); addKeyListener(new KeyAdapter() { }); } 2). 以上操作只是較好的屏蔽了鼠標事件,但是對于鍵盤事件,由于swing將鍵盤事件直接發(fā)送到當前聚焦的控件,因此如果有一組控件已經(jīng)獲取了焦點,它仍然可以收到鍵盤按鍵事件,甚至可以通過tab或ctrl+tab在各個控件之間切換焦點。要完成該功能,需要在玻璃窗體變成可見時調(diào)用requestFocusInWindow()以奪取焦點,因此該段代碼仍然需要放在該對象的構造函數(shù)中,如下: public ProgressGlassPane() { // blocks all user input addMouseListener(new MouseAdapter() { }); addMouseMotionListener(new MouseMotionAdapter() { }); addKeyListener(new KeyAdapter() { });
//This event will be triggered when this component turn to be visible. addComponentListener(new ComponentAdapter() { public void componentShown(ComponentEvent evt) { requestFocusInWindow(); } }); } 3). 此時用戶仍然可以通過tab鍵將焦點傳入玻璃窗格覆蓋的控件中,因此需要在構造函數(shù)中調(diào)用setFocusTraversalKeysEnabled(false)以便禁用該功能。 public ProgressGlassPane() { // blocks all user input addMouseListener(new MouseAdapter() { }); addMouseMotionListener(new MouseMotionAdapter() { }); addKeyListener(new KeyAdapter() { });
setFocusTraversalKeysEnabled(false); //This event will be triggered when this component turn to be visible. addComponentListener(new ComponentAdapter() { public void componentShown(ComponentEvent evt) { requestFocusInWindow(); } }); } 20. 屏蔽玻璃窗格中部分區(qū)域的鼠標事件,比如在一個完全透明的窗格中的左下角繪制一個公司的logo,其他部分則完全透明,此時,如果用戶將鼠標放到玻璃窗格下面的控件上方時,由于JFrame的最頂層組件是玻璃窗格,因此他攔截了鼠標光標的顯示效果,比如其下擺放了一組輸入框,如果沒有玻璃窗格,那么當鼠標停留在控件上方時,swing會根據(jù)實際控件的類型更新鼠標光標的形狀。此時由于玻璃窗格的存在,swing將無法在完成此項功能,因此我們需要為玻璃窗格組件重載public boolean contains(int x,int y)方法,以便通知swing框架,哪些x,y值不包含在玻璃窗格的攔截范圍之內(nèi),見如下代碼: @Override public boolean contains(int x, int y) { //when none of mouse events exist if (getMouseListeners().length == 0 && getMouseMotionListeners().length == 0 && getMouseWheelListeners().length == 0 && getCursor() == Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)) { if (image == null) { return false; } else { int imageX = getWidth() - image.getWidth(); int imageY = getHeight() - image.getHeight(); // if the mouse cursor is on a non-opaque(transparent) pixel, mouse events // are allowed int inImageX = x - imageX; int inImageY = y - imageY; if (inImageX >= 0 && inImageY >= 0 && inImageX < image.getWidth() && inImageY < image.getHeight()) { int color = image.getRGB(inImageX, inImageY); //it must be transparent if alpha is 0. return (color >> 24 & 0xFF) > 0; } return x > imageX && x < getWidth() && y > imageY && y < getHeight(); } } return super.contains(x, y); }
21. 分層窗格:JLayoutPane組件是swing的一個容器,是一個容納幾個子層的面板,swing框架依賴一個分層窗格以顯示必須橫跨其他組件的特定組件。分層窗格的每一層都通過一個整數(shù)來識別,這個整數(shù)定義為在這個層的堆棧的深度。最大值表示這個堆棧的最高層次,即顯示層的最上方。JLayerPane提供幾個層標識符以便容易的把組件插入到正確的層。 JLayeredPane.DEFAULT_LAYER = 0; 一般放置按鈕和表格等正規(guī)組件。 JLayeredPane.PALETTE_LAYER = 100; 一般用于面板和浮動工具欄。 JLayeredPane.MODAL_LAYER = 200; 模式對話框。 JLayeredPane.POPUP_LAYER = 300; 顯示彈出式窗口,包括工具提示、組合框下拉列表、框架菜單和上下文菜單。 JLayeredPane.DRAG_LAYER = 400; 用于顯示拖拽操作過程中的項。 swing用間隔100的單位設置這些層,以便使用者可以在他們之間容易的插入自己的層而不引起問題。具體插入方法如下: private void addLayeredComponent() { MyComponent validator = new MyComponent(); JLayeredPane layeredPane = getRootPane().getLayeredPane(); //分層組件需要使用OverlayLayout布局管理器,或者使用自定義的管理器才能讓該層的組件正確的顯示 layeredPane.setLayout(new OverlayLayout(layeredPane)); layeredPane.add(validator, (Integer)(JLayeredPane.DEFAULT_LAYER + 50)); } 如果JLayeredPane使用了普通的布局管理器,該管理器將不會考慮JLayeredPane中各個組件的層級關系,而是簡單的將他們視為同一層級,并且繼續(xù)按照該管理器既有的布局邏輯管理所有的組件,即便他們位于JLayeredPane的不同層級。 private void loadImagesInLayers() { layeredPane.setLayout(new FlowLayout()); for (int i = 2; i <= 5; i++) { String name = "images/photo" + i + ".jpg"; URL url = getClass().getResource(name); Icon icon = new ImageIcon(url); JLabel label = new JLabel(icon); layeredPane.add(label,(Integer)(JLayeredPane.DEFAULT_LAYER + (i - 1) * 2)); } }
22. 重繪管理器(RepaintManager):在Swing的框架中只存在一個RepaintManager,可以通過RepaintManager的靜態(tài)方法currentManager獲取,用戶也可以根據(jù)自己的需要自定義一個RepaintManager的子類,同時通過setCurrentManager方法設置新的RepaintManager。該類主要用于攔截所有swing組件通過repaint方法刷新組件的顯示區(qū)域,該類在攔截并處理后,在交給EDT繼續(xù)處理,因此有些特殊的效果需要通過重載RepaintManager才能很好的完成。如下代碼: //class ReflectionRepaintManager extends RepaintManager private void installRepaintManager() { ReflectionRepaintManager manager = new ReflectionRepaintManager(); RepaintManager.setCurrentManager(manager); }
class ReflectionRepaintManager extends RepaintManager { //該方法重載自RepaintManagr,當用戶代碼調(diào)用repaint之后,swing框架會將需要重繪的臟區(qū)域 //傳遞給RepaintManager的addDirtyRegion方法,該方法中將會根據(jù)自己的需要自行擴展臟區(qū)域, //之后在通過調(diào)用父類RepaintManager缺省的addDirtyRegion方法,將更新后的重繪區(qū)域重新交給 //swing的EDT去處理。 public void addDirtyRegion(JComponent c, int x, int y, int w, int h) { Rectangle dirtyRegion = getDirtyRegion(c); int lastDeltaX = c.getX(); int lastDeltaY = c.getY(); Container parent = c.getParent(); while (parent instanceof JComponent) { if (!parent.isVisible()) { return; } //如果父類是反射Panel,則將當前需要重繪的區(qū)域直接覆蓋到相應的反射區(qū)域,以便是 //相應的反射區(qū)域也能和原本需要更新區(qū)域一同更新。 if (parent instanceof ReflectionPanel) { x += lastDeltaX; y += lastDeltaY; int gap = contentPane.getHeight() - h - y; h += 2 * gap + h; lastDeltaX = lastDeltaY = 0; c = (JComponent)parent; } lastDeltaX += parent.getX(); lastDeltaY += parent.getY(); parent = parent.getParent(); } super.addDirtyRegion(c, x, y, w, h); } }
|