2013-01-31 41 views
1

我需要2个独立的JPanels(或任何轻量级组件),并最终嵌入到JPanel中,直接或通过类似JLayeredPane的东西嵌入JPanel。因此,没有重量较大的组件或玻璃板。较低的JPanel(名为BackgroundPanel)绘制背景图像或播放视频,同时保持宽高比并使用Alpha。上面的面板(称为CompassPanel)上有图标,并允许用户添加图标,删除它们并将它们移动(如图表库,该功能与此帖子无直接关系)。由于我的JNLP应用程序和部署环境的带宽限制,我无法添加许多外部依赖项。但是,如果有人知道一个轻量级的图表库可以处理高宽比保持的背景图像和视频,我就是游戏。否则,我不能为我的生活弄清楚为什么这个空间正在调整大小后分配: JLayeredPane taking up space that BackgroundPanel should draw on"JLayeredPane,背景图像+“图标”图层

我已经没有布局管理器(不是我所想要做的去阅读JAVA tutorial,你在哪里GBL !?),但是对于这些缩放要求,并且在调整大小等时使图标保留在图像的相同部分等等。我想不出另一种方式来做到这一点。

这是代码,我正在使用Java 1.7。此外,这是我的第一个计算器,不要温柔;-)

import java.awt.AlphaComposite; 
import java.awt.Color; 
import java.awt.Composite; 
import java.awt.Cursor; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.Image; 
import java.awt.Insets; 
import java.awt.Rectangle; 
import java.awt.event.ComponentAdapter; 
import java.awt.event.ComponentEvent; 
import java.awt.image.BufferedImage; 
import java.io.IOException; 
import java.net.URL; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.imageio.ImageIO; 
import javax.swing.BorderFactory; 
import javax.swing.ImageIcon; 
import javax.swing.JComponent; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JLayeredPane; 
import javax.swing.JPanel; 
import javax.swing.border.BevelBorder; 

public class Panel extends JPanel { 

    private static final Logger logger = Logger.getLogger(Panel.class.getName()); 

    public Panel() throws IOException { 

     final BufferedImage backgroundImage = ImageIO.read(new URL(
       "http://www.windpoweringamerica.gov/images/windmaps/us_windmap_80meters_820w.jpg")); 
     final Dimension backgroundImageSize = new Dimension(backgroundImage.getWidth(), backgroundImage.getHeight()); 
     logger.log(Level.INFO, "Image dimensions: {0}", backgroundImageSize); 
     setToolTipText("This is the panel"); 

     final JLayeredPane layeredPane = new JLayeredPane(); 
     layeredPane.setBorder(BorderFactory.createLineBorder(Color.RED, 10)); 
     layeredPane.setToolTipText("This is the layered pane!"); 
     layeredPane.getInsets().set(0, 0, 0, 0); 

     final BackgroundPanel backgroundImagePanel = new BackgroundPanel(backgroundImage); 
     final CompassPanel compassPanel = new CompassPanel(); 
     backgroundImagePanel.setToolTipText("You'll probably never see me, I'm in the background, forever beneath the compass panel"); 
     compassPanel.setToolTipText("I'm the compass panel"); 

     // Per http://docs.oracle.com/javase/tutorial/uiswing/layout/none.html, for every container w/o a layout manager, I must: 
     // 1) Set the container's layout manager to null by calling setLayout(null). -- I do this here 
     // 2) Call the Component class's setbounds method for each of the container's children. --- I do this when resizing 
     // 3) Call the Component class's repaint method. --- I do this when resizing 

     setLayout(null); 
     add(layeredPane); 
     layeredPane.add(backgroundImagePanel, JLayeredPane.DEFAULT_LAYER); 
     layeredPane.add(compassPanel, JLayeredPane.PALETTE_LAYER); 

     // Whenever this panel gets resized, make sure the layered pane gets resized to preserve the aspect ratio of the background image 
     addComponentListener(new ComponentAdapter() { 
      @Override 
      public void componentResized(ComponentEvent evt) { 
       Dimension availableSize = calculateAvailableSize(Panel.this); 
       Rectangle contentBounds = calculateBoundsToFitImage(availableSize, backgroundImageSize); 
       // Ok, this is a big deal. Now I know how big everything has to be, lets force it all to be the right size & repaint. 
       layeredPane.setBounds(contentBounds); 
       backgroundImagePanel.setBounds(contentBounds); 
       compassPanel.setBounds(contentBounds); 

       Panel.this.repaint(); 
       logger.info(String.format("Panel size: %s. Available size: %s. Content Bounds: %s", getSize(), availableSize, contentBounds)); 
      } 
     }); 
    } 

    /** 
    * Paints the constant fitted aspect-ratio background image with an alpha of 0.5 
    */ 
    private static class BackgroundPanel extends JPanel { 

     private static final Logger logger = Logger.getLogger(BackgroundPanel.class.getName()); 
     private final AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f); 
     private final BufferedImage backgroundImage; 

     BackgroundPanel(BufferedImage backgroundImage) { 
      setLayout(null); 
      this.backgroundImage = backgroundImage; 
     } 
     private Dimension lastPaintedDimensions = null; 

     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      final Dimension size = getSize(); 
      if (lastPaintedDimensions == null || !size.equals(lastPaintedDimensions)) { 
       logger.log(Level.INFO, String.format("Painting background on %d x %d", size.width, size.height)); 
      } 
      final Image paintMe = backgroundImage.getScaledInstance(size.width, size.height, Image.SCALE_SMOOTH); 
      final Graphics2D g2 = (Graphics2D) g.create(); 
      g2.drawImage(paintMe, 0, 0, this); 
      g2.setColor(Color.BLUE); 
      g2.dispose(); 
      lastPaintedDimensions = size; 
     } 
    }; 

    private static class CompassPanel extends JPanel { 

     final List<Compass> compassLabels = new ArrayList<>(); 

     CompassPanel() { 
      setLayout(null); 
      setOpaque(false); 
      setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 
     } 
    } 

    private static class Compass extends JLabel { 

     private static final BufferedImage compassImage; 

     static { 
      try { 
       compassImage = ImageIO.read(new URL("http://cdn1.iconfinder.com/data/icons/gur-project-1/32/1_7.png")); 
      } catch (IOException ex) { 
       throw new RuntimeException("Failed to read compass image", ex); 
      } 
     } 
     final float xPercent, yPercent; 

     public Compass(float xPercent, float yPercent) { 
      this.xPercent = xPercent; 
      this.yPercent = yPercent; 
      setIcon(new ImageIcon(compassImage)); 
      setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); 
      setOpaque(true); 
      setCursor(Cursor.getDefaultCursor()); 
     } 
    } 

    public static void main(String[] args) throws IOException { 
     final JFrame frame = new JFrame("Hello Stackoverflowwwwwww! Here is a Dynamic Layered Pane Question."); 
     frame.setLayout(null); 
     frame.setContentPane(new Panel()); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setSize(800, 600); 
     frame.setVisible(true); 
    } 

    private static Dimension calculateAvailableSize(final JComponent component) { 

     int availableHeight = component.getSize().height; 
     int availableWidth = component.getSize().width; 

     final Insets insets = component.getInsets(); 

     availableHeight -= insets.top; 
     availableHeight -= insets.bottom; 

     availableWidth -= insets.left; 
     availableWidth -= insets.right; 

     if (component.getBorder() != null) { 
      Insets borderInsets = component.getBorder().getBorderInsets(component); 
      if (borderInsets != null) { 
       availableHeight -= borderInsets.top; 
       availableHeight -= borderInsets.bottom; 

       availableWidth -= borderInsets.left; 
       availableWidth -= borderInsets.right; 
      } 
     } 

     return new Dimension(availableWidth, availableHeight); 
    } 

    private static Rectangle calculateBoundsToFitImage(Dimension parentSize, Dimension imageSize) { 
     final double scaleFactor; 
     final int xOffset, yOffset, scaledHeight, scaledWidth; 
     { 
      final double xScaleFactor = (double) parentSize.width/imageSize.width; 
      final double yScaleFactor = (double) parentSize.height/imageSize.height; 
      scaleFactor = xScaleFactor > yScaleFactor ? yScaleFactor : xScaleFactor; 
      scaledHeight = (int) Math.round(scaleFactor * imageSize.height); 
      scaledWidth = (int) Math.round(scaleFactor * imageSize.width); 
     } 

     xOffset = (int) ((parentSize.width - scaledWidth)/2.0); 
     yOffset = (int) ((parentSize.height - scaledHeight)/2.0); 
     return new Rectangle(xOffset, yOffset, scaledWidth, scaledHeight); 
    } 
} 
+0

我只注意到BackgroundPanel#的paintComponent的图形组件都有不同的“剪辑范围”比我预期的要小,比JLayerPane小。我不知道为什么,但看起来paintComponent只绘制了我认为是的一个子集。 –

+0

'paintComponent'将尝试优化重绘,只绘制它认为需要更新的那部分画面。您可以使用这些信息来确定是否应该绘制组件的某些部分,尤其是在绘制复杂的绘画时。看看[在AWT和Swing中绘画](http://www.oracle.com/technetwork/java/painting-140037.html)以获取更多信息 – MadProgrammer

+0

从我读过的内容看,你似乎在做出好的选择实现你的目标。 – MadProgrammer

回答

1

Doh。我只是回答了我自己的问题。调用#setBounds是相对于你的父容器,所以X & y偏移需要适当说明,所以这修正了:

backgroundImagePanel.setBounds(0, 0, contentBounds.width, contentBounds.height); 
compassPanel.setBounds(0, 0, contentBounds.width, contentBounds.height); 
+0

我仍然有兴趣知道是否有人有改进建议或图书馆来帮助实现这一点! –

+1

另请参见[tag:jmapviewer],适用于[示例](http://stackoverflow.com/a/10747783/230513)。 – trashgod

+1

@trashgod看起来不错!我会看看我能否使用JMF以某种方式在该层后面放置电影,或者至少一部电影图像。不确定,这是一个奇怪的要求。我的图片通常不是地图,而是恰巧在上面的示例中使用了地图。好的图书馆,谢谢! –