1. main函数不要通过直接调用JFrame子类的构造来启动窗体程序,因为main本身并非运行于EDT中,因此可能会给UI带来同步问题,建议使用一下方式运行:
1 public static void main(String args[]) { 2 Runnable doCreateAndShowGUI = new Runnable() { 3 @Override 4 public void run() { 5 //该方法为该类的私有静态方法,用于启动JFrame的主界面。 6 createAndShowGUI(); 7 } 8 }; 9 SwingUtilities.invokeLater(doCreateAndShowGUI); 10 }
2. 基于重载JComponent的paint属性来重绘该组件的所有区域,paint中的Graphics参数是自始至终存在并保持一致的,paintComponent中的Graphics参数则是在swing框架每次调用paintComponent函数之前新创建的。
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 //该函数来自Component,用于获取当前显示设备的metrics信息, 6 //然后根据该信息在创建设备兼容的BufferedImage对象。 7 buttonImage = getGraphicsConfiguration().createCompatibleImage(getWidth(), getHeight()); 8 } 9 Graphics gButton = buttonImage.getGraphics(); 10 gButton.setClip(g.getClip()); 11 12 //使用超类中的paint方法将需要显示的image绘制到该内存图像中,不推荐直接从superclass绘制到g 13 //(子类paint方法的graphics对象),因为这样做可能会将superclass的graphics中的设备覆盖掉子 14 //类graphics中的设置,这样通过间接的内存图像可以避免该问题。 15 super.paint(gButton); 16 17 //这里必须直接目的graphics的composite属性,如果只是修改内存图像的composite,将只会是组件的 18 //内存被渲染成指定的alpha值,但是如果是设置的目的graphics的composite,那么整个组件内存图像的 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)中执行较长的操作,从而避免UI被冻结的现象发生。 和操作UI相关的code务必要放到EDT中调用,否则容易导致死锁。 SwingUtilities.invodeLater(new Runnable()) 可以在EDT之外执行该方法,必将和UI操作相关的coding放到参数Runnable的实现中。该工具函数会自动将Runnable投递到EDT中执行。 SwingUtilities.isEventDispatchThread() 如果当前的执行线程为EDT,该方法返回true,因此可以直接操作UI,否则表示EDT之外的线程,操作UI的界面不能直接在这里调用了。
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(), 该函数和invokeLater的主要区别就是该函数执行后将等待EDT线程执行该任务的完成,之后该函数才正常返回。 非EDT线程可以通过调用repaint,强制EDT执行组件重绘。
4. java.util.Timer 定时器中的TimerTask是在EDT之外的独立线程中执行的,因为不能直接执行UI的操作。 java.swing.Timer 定时器中的任务是在EDT中执行的,因为可以包含直接操作UI的代码。 5. 打开抗锯齿:
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); 以下的例子是根据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是需要被复制区域的左上角坐标,width和height分别表示该区域的宽度和高度,dx和dy表示相对于此区域的位置,如果为正值,则表示此区域的右边和下边,如果为负值,则表示此区域的左边和上边。
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. 将一个普通的图像复制到一个显示设备兼容的图像中,以提高后期渲染操作的性能。 注:由于显示设备兼容图像的图像数据存储方式和设备显示时的数据读取方式一致,不需要额外的转换,因此只是需要简单且高效的内存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 }
制作一个自己的工具类便于创建设备兼容的图像。
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中创建一个system memory图像的副本,需要设备显示时,直接将VRAM中的副本copy到VRAM中用于显示,从而避免了通过总线将数据从system memory拷贝到VRAM了。 抑制图像自动托管的两个因素: 1) 通过Image的Raster,调用dataBuffer方法直接获取并且操作显示数据时,java 2d将自动关闭图像托管,由于此时是外部代码直接以数组的方式操作显示图像数据,因此java 2d无法监控显示数据是否已经被改变。注:一旦获取dataBuffer后,无法在通过将dataBuffer交还的方法重新使该图像成为托管图像。 DataBuffer dataBuffer = image.getRaster().getDataBuffer(); 2) 频繁的渲染到图像,和1)不同,java 2d可以根据实际情况动态的处理是否需要将该图像设置为托管图像。 由于渲染操作频繁,致使从system memory到与VRAM中的副本进行同步的操作变为额外的操作。
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 }
需要实时渲染和计算的不规则图形也可以很好的利用中间图像,以避免更多的计算,但是对于不规则图像,需要考虑将中间图像的底色设置为透明,以便在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函数不要通过直接调用JFrame子类的构造来启动窗体程序,因为main本身并非运行于EDT中,因此可能会给UI带来同步问题,建议使用一下方式运行:
public static void main(String args[]) { Runnable doCreateAndShowGUI = new Runnable() { @Override public void run() { createAndShowGUI(); //该方法为该类的私有静态方法,用于启动JFrame的主界面。 } }; SwingUtilities.invokeLater(doCreateAndShowGUI); } 2. 基于重载JComponent的paint属性来重绘该组件的所有区域,paint中的Graphics参数是自始至终存在并保持一致的,paintComponent中的Graphics参数则是在swing框架每次调用paintComponent函数之前新创建的。 public void paint(Graphics g) { // Create an image for the button graphics if necessary if (buttonImage == null || buttonImage.getWidth() != getWidth() || buttonImage.getHeight() != getHeight()) { //该函数来自Component,用于获取当前显示设备的metrics信息,然后根据该信息在创建设备兼容的BufferedImage对象。 buttonImage = getGraphicsConfiguration().createCompatibleImage(getWidth(), getHeight()); } Graphics gButton = buttonImage.getGraphics(); gButton.setClip(g.getClip()); //使用超类中的paint方法将需要显示的image绘制到该内存图像中,不推荐直接从superclass绘制到g(子类paint方法的graphics对象),因为这样做可能会将superclass的graphics中的设备覆盖掉子类graphics中的设置,这样通过间接的内存图像可以避免该问题。 super.paint(gButton); //这里必须直接目的graphics的composite属性,如果只是修改内存图像的composite,将只会是组件的内存被渲染成指定的alpha值,但是如果是设置的目的graphics的composite,那么整个组件内存图像的显示将被渲染成指定的透明度。 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)中执行较长的操作,从而避免UI被冻结的现象发生。 和操作UI相关的code务必要放到EDT中调用,否则容易导致死锁。 SwingUtilities.invodeLater(new Runnable()) 可以在EDT之外执行该方法,必将和UI操作相关的coding放到参数Runnable的实现中。该工具函数会自动将Runnable投递到EDT中执行。 SwingUtilities.isEventDispatchThread() 如果当前的执行线程为EDT,该方法返回true,因此可以直接操作UI,否则表示EDT之外的线程,操作UI的界面不能直接在这里调用了。 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(), 该函数和invokeLater的主要区别就是该函数执行后将等待EDT线程执行该任务的完成,之后该函数才正常返回。 非EDT线程可以通过调用repaint,强制EDT执行组件重绘。 4. java.util.Timer 定时器中的TimerTask是在EDT之外的独立线程中执行的,因为不能直接执行UI的操作。 java.swing.Timer 定时器中的任务是在EDT中执行的,因为可以包含直接操作UI的代码。 5. 打开抗锯齿:g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); 以下的例子是根据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是需要被复制区域的左上角坐标,width和height分别表示该区域的宽度和高度,dx和dy表示相对于此区域的位置,如果为正值,则表示此区域的右边和下边,如果为负值,则表示此区域的左边和上边。
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. 将一个普通的图像复制到一个显示设备兼容的图像中,以提高后期渲染操作的性能。 注:由于显示设备兼容图像的图像数据存储方式和设备显示时的数据读取方式一致,不需要额外的转换,因此只是需要简单且高效的内存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); } 制作一个自己的工具类便于创建设备兼容的图像。 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中创建一个system memory图像的副本,需要设备显示时,直接将VRAM中的副本copy到VRAM中用于显示,从而避免了通过总线将数据从system memory拷贝到VRAM了。 抑制图像自动托管的两个因素: 1) 通过Image的Raster,调用dataBuffer方法直接获取并且操作显示数据时,java 2d将自动关闭图像托管,由于此时是外部代码直接以数组的方式操作显示图像数据,因此java 2d无法监控显示数据是否已经被改变。注:一旦获取dataBuffer后,无法在通过将dataBuffer交还的方法重新使该图像成为托管图像。 DataBuffer dataBuffer = image.getRaster().getDataBuffer(); 2) 频繁的渲染到图像,和1)不同,java 2d可以根据实际情况动态的处理是否需要将该图像设置为托管图像。 由于渲染操作频繁,致使从system memory到与VRAM中的副本进行同步的操作变为额外的操作。
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); } 需要实时渲染和计算的不规则图形也可以很好的利用中间图像,以避免更多的计算,但是对于不规则图像,需要考虑将中间图像的底色设置为透明,以便在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. 图像合成,其中最为有用的三个规则分别是clear、SrcOver(swing缺省)和SrcIn。 Clear:是擦掉一个图像的背景以便使他变得完全透明的一个容易的方式,可以将其理解为Photoshop中的橡皮擦,通过Clear可以清除任意形状的区域。 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:位于目的地内部的那部分源代替目的地,位于目的地之外的那部分源丢弃掉。 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),这里包含两个数组参数,其中第一个float类型的数组包含渐变中使用的每个颜色的位置。每一对位置/颜色被称为一个停顿,见下例: 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. 优化渐变的3个技巧: 1) 缓存这个渐变:该解决方案是把这个渐变变成一个图像并仅仅绘制那个图像,但是缺点是需要消耗更多的内存。 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) 更巧妙的缓存:当绘制一个垂直或者水平渐变时,每一列或者每一行都是相同的,因此可以只是保留一列或者一行的数据,然在需要覆盖渐变时在拉伸该列或者该行。 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) 使用循环渐变的优化:如果渐变只是被覆盖组件高度的一半时,如以下代码: 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将不断的检查是否当前的像素位于这个渐变区域的外面,因此对于成千上万的像素来说,将会花费很多时间。如果使用循环渐变的方式,java 2d内部在渲染的时候将会不进行该判断,从而大大提高了整体的效率,见如下代码: //循环GradientPaint new GradientPaint(new Point(0,0),Color.WHITE,new Point(0,getHeight()),Color.DARK_GRAY,true/*该标志表示循环*/); //循环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已经使用了玻璃窗格,本次操作只是替换一个新的对象,那么该窗格的visible属性将和原有窗格的visible属性保持一致。 public ApplicationFrame() { //ApplicationFrame为应用程序的主窗体,继承自JFrame initComponents(); //安装玻璃窗格,glassPane是JComponent的子类。 setGlassPane(glassPane = new ProgressGlassPane()); } 2). 实现玻璃窗格的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). 主窗体中的工作线程需要调用的方法,以便更新进度条的显示状态 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). 为玻璃窗格控件自身添加空的鼠标和键盘的监听器 public ProgressGlassPane() { // blocks all user input addMouseListener(new MouseAdapter() { }); addMouseMotionListener(new MouseMotionAdapter() { }); addKeyListener(new KeyAdapter() { }); } 2). 以上操作只是较好的屏蔽了鼠标事件,但是对于键盘事件,由于swing将键盘事件直接发送到当前聚焦的控件,因此如果有一组控件已经获取了焦点,它仍然可以收到键盘按键事件,甚至可以通过tab或ctrl+tab在各个控件之间切换焦点。要完成该功能,需要在玻璃窗体变成可见时调用requestFocusInWindow()以夺取焦点,因此该段代码仍然需要放在该对象的构造函数中,如下: 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键将焦点传入玻璃窗格覆盖的控件中,因此需要在构造函数中调用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. 屏蔽玻璃窗格中部分区域的鼠标事件,比如在一个完全透明的窗格中的左下角绘制一个公司的logo,其他部分则完全透明,此时,如果用户将鼠标放到玻璃窗格下面的控件上方时,由于JFrame的最顶层组件是玻璃窗格,因此他拦截了鼠标光标的显示效果,比如其下摆放了一组输入框,如果没有玻璃窗格,那么当鼠标停留在控件上方时,swing会根据实际控件的类型更新鼠标光标的形状。此时由于玻璃窗格的存在,swing将无法在完成此项功能,因此我们需要为玻璃窗格组件重载public boolean contains(int x,int y)方法,以便通知swing框架,哪些x,y值不包含在玻璃窗格的拦截范围之内,见如下代码: @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框架依赖一个分层窗格以显示必须横跨其他组件的特定组件。分层窗格的每一层都通过一个整数来识别,这个整数定义为在这个层的堆栈的深度。最大值表示这个堆栈的最高层次,即显示层的最上方。JLayerPane提供几个层标识符以便容易的把组件插入到正确的层。 JLayeredPane.DEFAULT_LAYER = 0; 一般放置按钮和表格等正规组件。 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中各个组件的层级关系,而是简单的将他们视为同一层级,并且继续按照该管理器既有的布局逻辑管理所有的组件,即便他们位于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的静态方法currentManager获取,用户也可以根据自己的需要自定义一个RepaintManager的子类,同时通过setCurrentManager方法设置新的RepaintManager。该类主要用于拦截所有swing组件通过repaint方法刷新组件的显示区域,该类在拦截并处理后,在交给EDT继续处理,因此有些特殊的效果需要通过重载RepaintManager才能很好的完成。如下代码: //class ReflectionRepaintManager extends RepaintManager private void installRepaintManager() { ReflectionRepaintManager manager = new ReflectionRepaintManager(); RepaintManager.setCurrentManager(manager); }
class ReflectionRepaintManager extends RepaintManager { //该方法重载自RepaintManagr,当用户代码调用repaint之后,swing框架会将需要重绘的脏区域 //传递给RepaintManager的addDirtyRegion方法,该方法中将会根据自己的需要自行扩展脏区域, //之后在通过调用父类RepaintManager缺省的addDirtyRegion方法,将更新后的重绘区域重新交给 //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,则将当前需要重绘的区域直接覆盖到相应的反射区域,以便是 //相应的反射区域也能和原本需要更新区域一同更新。 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); } }
|