2013-01-31 46 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以某種方式在該層後面放置電影,或者至少一部電影圖像。不確定,這是一個奇怪的要求。我的圖片通常不是地圖,而是恰巧在上面的示例中使用了地圖。好的圖書館,謝謝! –