【问题标题】:Java Swing Graphical Glitches Dealing with Transparency and Images处理透明度和图像的 Java Swing 图形故障
【发布时间】:2013-08-06 20:47:00
【问题描述】:

所以我有这个登录表单和一张“用户照片”。我正在尝试这样做,以便当您将鼠标悬停在照片区域上时,会出现一个带有彩色背景的透明标签(以产生“选择照片”的效果)。它看起来像这样:

一旦你将鼠标移开它,它就会回到“取消选择”状态。

现在我的问题是,如果您先将鼠标悬停在登录按钮上,然后将鼠标移到照片上,则会出现“幽灵登录按钮”。它看起来像这样:

我不知道为什么会这样。有人可以帮忙吗?以下是相关代码:

package com.stats;

public class Stats extends JFrame implements Serializable {

    private JLabel fader;

    public Stats() {

    try {
        Image image = ImageIO.read(new File(System.getenv("APPDATA")
                                   + "\\Stats\\Renekton_Cleave.png"));
        JLabel labelUserPhoto = new JLabel(new ImageIcon(image));
        fader = new JLabel();
        fader.setBounds(97, 44, 100, 100);
        fader.setOpaque(true);
        fader.setBackground(new Color(0, 0, 0, 0));
        labelUserPhoto.setBounds(97, 44, 100, 100);
        PicHandler ph = new PicHandler();
        contentPane.add(fader);
        contentPane.add(labelUserPhoto);
        fader.addMouseMotionListener(ph);
    } catch(Exception e) {
        e.printStackTrace();
    }
}

private class PicHandler implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) { }
    public void mouseMoved(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();

        System.out.println("x: " + x + ", y: " + y);

        if ((x > 16 && x < 80) && (y > 16 && y < 80)) {
            if (!fader.isOpaque()) {
                fader.setOpaque(true);
                fader.setBackground(new Color(0, 0, 0, 40));
                fader.repaint();
            }
        } else {
            if (fader.isOpaque()) {
                fader.setOpaque(false);
                fader.repaint();
            }
        }
    }
}

【问题讨论】:

  • 我没有看到您给super.paintComponent() 的电话,正如here 所讨论的那样。
  • 是的,我读过一篇关于这个的帖子,但我不知道我需要把它放在哪里,更不用说它的作用了。 :S
  • 这里有更多关于opacity属性的信息。
  • 一般来说,Swing 组件不支持 alpha 颜色,所以我不认为使用 fader.setBackground(new Color(0, 0, 0, 40)); 对你有任何好处
  • 和其他人已经说过的一样,只是为了强调(不能经常重复:):带有isOpaque() == true的组件必须在其区域中填充每个像素完全不透明(又名:alpha == 255)颜色。您违反了该合同,因此您将获得绘画文物

标签: java swing graphics awt transparency


【解决方案1】:

在移动或调整大小后,我遇到了类似的重影问题。 @MadProgrammer 有一些非常好的观点,但最后,它们对我不起作用(可能是因为我有多个使用 0.0 alpha 值颜色的图层,其中一些也有图像,所以我无法设置合成整个组件的值)。最后,修复它的是一个简单的调用

<contentpane>.repaint()

在所有绘制命令都执行之后。

【讨论】:

    【解决方案2】:

    从 jdk7 开始有一种新的机制来应用视觉装饰(并监听子组件的事件):这就是 JLayer/LayerUI 对。

    在您的情况下,自定义 layerUI 会

    • 在翻转时触发重绘
    • 实现绘制以应用透明色

    下面是一个例子,类似于教程中的WallPaperUI:

    // usage: create the component and decorate it with the custom ui
    JLabel label = new JLabel(myIcon);
    content.add(new JLayer(label, new RolloverUI()));
    
    // custom layerUI
    public static class RolloverUI extends LayerUI<JComponent> {
    
        private Point lastMousePoint;
    
        private JLayer layer;
    
        /**
         * Implemented to install the layer and enable mouse/motion events.
         */
        @Override
        public void installUI(JComponent c) {
            super.installUI(c);
            this.layer = (JLayer) c;
            layer.setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK
                    | AWTEvent.MOUSE_EVENT_MASK);
        }
    
        @Override
        protected void processMouseMotionEvent(MouseEvent e,
                JLayer<? extends JComponent> l) {
            updateLastMousePoint(e.getPoint());
        }
    
        @Override
        protected void processMouseEvent(MouseEvent e,
                JLayer<? extends JComponent> l) {
            if (e.getID() == MouseEvent.MOUSE_EXITED) {
                updateLastMousePoint(null);
            } else if (e.getID() == MouseEvent.MOUSE_ENTERED) {
                updateLastMousePoint(e.getPoint());
            }
        }
    
        /**
         * Updates the internals and calls repaint.
         */
        protected void updateLastMousePoint(Point e) {
            lastMousePoint = e;
            layer.repaint();
        }
    
        /**
         * Implemented to apply painting decoration below the component.
         */
        @Override
        public void paint(Graphics g, JComponent c) {
            if (inside()) {
                Graphics2D g2 = (Graphics2D) g.create();
    
                int w = c.getWidth();
                int h = c.getHeight();
                g2.setComposite(AlphaComposite.getInstance(
                        AlphaComposite.SRC_OVER, .5f));
                g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h,
                        Color.red));
                g2.fillRect(0, 0, w, h);
    
                g2.dispose();
            }
            super.paint(g, c);
        }
    
        protected boolean inside() {
            if (lastMousePoint == null || lastMousePoint.x < 0
                    || lastMousePoint.y < 0)
                return false;
            Rectangle r = layer.getView().getBounds();
            r.grow(-r.width / 10, -r.height / 10);
            return r.contains(lastMousePoint);
        }
    }
    

    【讨论】:

      【解决方案3】:

      我可以看到您的示例存在许多问题,但最重要的是使用具有 alpha 值的颜色。

      fader.setBackground(new Color(0, 0, 0, 40));
      

      Swing 不能很好地渲染具有基于 alpha 的颜色的组件(在此上下文中)。通过使组件不透明,然后将背景颜色设置为使用 alpha 值,您就是在告诉 Swing 它不需要担心绘制组件下方的内容,这不是真的...

      Graphics 上下文也是共享资源,这意味着在您的组件之前绘制的任何内容仍然“绘制”,您需要在绘制之前清除 Graphics 上下文。

      这个例子使用了一个相当讨厌的技巧来完成它的工作。因为所有的绘制都发生在 UI 委托中,如果我们只是允许默认绘制链继续,我们将无法在图标下方进行渲染。相反,我们接管了“脏”细节的控制权并代表父级绘制背景。

      如果我们简单地从JPanel 之类的东西扩展并自己绘制图像,这将更容易实现

      import java.awt.AlphaComposite;
      import java.awt.Color;
      import java.awt.EventQueue;
      import java.awt.Graphics;
      import java.awt.Graphics2D;
      import java.awt.GridBagLayout;
      import java.awt.event.MouseAdapter;
      import java.awt.event.MouseEvent;
      import java.awt.image.BufferedImage;
      import java.io.File;
      import java.io.IOException;
      import javax.imageio.ImageIO;
      import javax.swing.Icon;
      import javax.swing.ImageIcon;
      import javax.swing.JFrame;
      import javax.swing.JLabel;
      import javax.swing.UIManager;
      import javax.swing.UnsupportedLookAndFeelException;
      
      public class FadingIcon {
      
        public static void main(String[] args) {
          new FadingIcon();
        }
      
        public FadingIcon() {
          startUI();
        }
      
        public void startUI() {
          EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
              try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
              } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                ex.printStackTrace();
              }
      
              BufferedImage img = null;
              try {
                img = ImageIO.read(new File("C:\\Users\\swhitehead\\Documents\\My Dropbox\\Ponies\\SmallPony.png"));
              } catch (IOException ex) {
                ex.printStackTrace();
              }
      
              JFrame frame = new JFrame("Testing");
              frame.setLayout(new GridBagLayout());
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.add(new FadingLabel(new ImageIcon(img)));
              frame.pack();
              frame.setLocationRelativeTo(null);
              frame.setVisible(true);
            }
          });
        }
      
        public class FadingLabel extends JLabel {
      
          private boolean mouseIn = false;
          private MouseHandler mouseHandler;
      
          public FadingLabel(Icon icon) {
            super(icon);
            setBackground(Color.RED);
            super.setOpaque(false)(
          }
      
          @Override
          public void setOpaque(boolean opaque) {
          }
      
          @Override
          public final boolean isOpaque() {
              return false;
          }
      
          protected MouseHandler getMouseHandler() {
            if (mouseHandler == null) {
              mouseHandler = new MouseHandler();
            }
            return mouseHandler;
          }
      
          @Override
          public void addNotify() {
            super.addNotify();
            addMouseListener(getMouseHandler());
          }
      
          @Override
          public void removeNotify() {
            removeMouseListener(getMouseHandler());
            super.removeNotify();
          }
      
          @Override
          protected void paintComponent(Graphics g) {
            if (mouseIn) {
              Graphics2D g2d = (Graphics2D) g.create();
              g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
              g2d.setColor(getBackground());
              g2d.fillRect(0, 0, getWidth(), getHeight());
              g2d.dispose();
            }
            getUI().paint(g, this);
          }
      
          public class MouseHandler extends MouseAdapter {
      
            @Override
            public void mouseEntered(MouseEvent e) {
              mouseIn = true;
              repaint();
            }
      
            @Override
            public void mouseExited(MouseEvent e) {
              mouseIn = false;
              repaint();
            }
      
          }
      
        }
      
      }
      

      我还建议您花时间学习如何使用适当的布局管理器,它们会为您省去很多麻烦

      查看A Visual Guide to Layout ManagersLaying Out Components Within a Container

      【讨论】:

      • hmm .. 如果 opaque == true,您的paintComponent 似乎违反了不透明合同:您用透明颜色填充背景,因此不会使每个像素完全不透明。我错过了什么吗?
      • @kleopatra 是的,我想到了,不太确定该怎么办
      • @kleopatra 在那里,我已经设置它应该几乎总是透明的......:P
      • 这就足够安全了,除了覆盖 isOpaque 的恶意子类 为了真正安全,也覆盖它并使其成为最终的.. 只是说:-)
      • @kleopatra 同意。应该坚持使用JPaneldrawImage:P
      【解决方案4】:

      由于我无法发表评论,因此我必须将其作为答案:

      正如垃圾神所提到的,这个问题可能是由于缺少 super.paintComponent(g) 调用而导致的,但是您似乎根本没有覆盖 paintComponent 方法(至少您没有在此处显示它)。如果您确实覆盖了 JFrame 或任何 JPanel 的 paintComponent 方法,则需要:

      public void paintComponent(Graphics g) {
          super.paintComponent(g);
          //rest of your drawing code....
      }
      

      但如果您根本没有使用过,那么问题可能是由其他原因引起的。

      【讨论】:

      • 我从未重写paintComponent。 :(
      • 尝试在我的答案中添加该方法,因为它是扩展 JFrame 的类。据我了解,您应该在此方法中进行所有绘图,但从技术上讲,您只是将图像添加到 JLabel 中,而不是使用图形对象进行任何绘图。
      • 所以 super 应该是指 JFrame 对吧?好吧,我正在使用 Eclipse,它表明 JFrame 有一个“paintComponents”方法,而不是一个 paintComponent 方法。另外,这是否意味着我应该将整个“图像到 JLabel”代码添加到 paintComponent 方法中?
      • paintComponent 方法是受保护的,这意味着您不能从包之外的另一个类调用它,但子类仍然可以调用(并覆盖)它。我不是说你应该在paintcomponent方法中将图像添加到JLabel,但我之前也遇到过同样的错误(一个按钮出现在不应该出现的奇怪地方),这是由于缺少超级.paintComponent(g) 调用。如果你有链接到 Eclipse 的源代码,你可以在光标位于 JFrame 上方时按 f3,然后查看源代码,你会发现它确实有一个 paintComponent 方法。
      • 抱歉,您不能直接在 JFrame 上绘图,因为它们没有该方法。您应该将 JPanel 添加到 JFrame,然后将所有内容添加到此 JPanel 而不是 JFrame。这可能是您不使用面板的原因。
      猜你喜欢
      • 2014-07-09
      • 1970-01-01
      • 1970-01-01
      • 2022-01-01
      • 2011-12-21
      • 1970-01-01
      • 2013-01-22
      • 1970-01-01
      • 2022-08-09
      相关资源
      最近更新 更多