【问题标题】:Java Swing Graphics and EventsJava Swing 图形和事件
【发布时间】:2020-07-12 17:17:53
【问题描述】:

我正在尝试制作一个简单的游戏,它在框架上显示圆圈,单击时圆圈应该消失。我正在学习 Java Swing 如何工作并设法画了一个圆圈(哇,这样的成就)并弄清楚事件是如何工作的。我在圆圈中添加了一个 mouseListener,现在单击时,我想要一个控制台日志,表明它已被单击,但最终结果与预期不符。无论我在哪里点击,我总是得到“点击”控制台日志。例如,当我尝试将侦听器添加到 JButton 时,我得到了最终结果。图形的事件是否不同?


import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.event.*;
import java.sql.SQLOutput;

public class CirclePop {

    JFrame frame;
    Circle circle;

    public static void main(String[] args) {
        CirclePop circlePop = new CirclePop();
        circlePop.drawFrame();
    }

    public void drawFrame() {
        frame = new JFrame();
        circle  = new Circle();

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(circle);

        circle.addMouseListener(new Click());

        frame.setSize(300, 300);
        frame.setVisible(true);
    }

    class Click implements MouseListener {

        @Override
        public void mouseClicked(MouseEvent e) {

        }

        @Override
        public void mousePressed(MouseEvent e) {
            System.out.println("Pressed");
        }

        @Override
        public void mouseReleased(MouseEvent e) {

        }

        @Override
        public void mouseEntered(MouseEvent e) {

        }

        @Override
        public void mouseExited(MouseEvent e) {

        }
    }
}


import java.awt.*;
import javax.swing.*;

class Circle extends JPanel {

    public void paintComponent(Graphics g) {

        g.setColor(Color.red);
        g.fillOval(150, 140, 30, 30);
    }
}

【问题讨论】:

  • 圆有什么界限?我发现 - 有点 - JPanel 的边界和您在其中绘制图形的位置并不完全相同。

标签: java swing


【解决方案1】:

首先,您可能想要extend MouseAdapter 而不是实现MouseListener。这样你就不用“实现”所有这些空方法了。

然后,在您的mousePressed 方法中,您只需计算点击是否发生在圆圈内。这基本上只是毕达哥拉斯:

static class ClickListener extends MouseAdapter {
    private final Circle circle;

    public ClickListener(Circle circle) {
        this.circle = circle;
    }


    @Override
    public void mousePressed(MouseEvent e) {
        int centerX = circle.getCenterX();
        int centerY = circle.getCenterY();
        int radius = circle.getRadius();
        int clickX = e.getX();
        int clickY = e.getY();

        // inside circle: (clickX - centerX)^2 + (clickY - centerY)^2 < radius^2
        double xSquare = Math.pow(clickX - centerX, 2);
        double ySquare = Math.pow(clickY - centerY, 2);

        if (xSquare + ySquare < radius * radius) {
            System.out.println("pressed");
        }
    }
}

我在Circle 类中添加了一些字段,以访问计算所需的属性:

class Circle extends JPanel {
    private final int radius = 30;
    private final int centerX = 150;
    private final int centerY = 140;

    public void paintComponent(Graphics g) {
        g.setColor(Color.red);
        g.fillOval(centerX, centerY, radius, radius);
    }
    // getter, etc.
}

【讨论】:

    【解决方案2】:

    你确实要实现MouseListener接口,鼠标点击后,你必须检查鼠标位置是否包含在你的圆圈区域内。您可以通过比较坐标手动执行此操作,但这可能有点过多的工作。
    我认为创建一个 Shape 对象更容易(事实上,这是学习它的好时机,因为你刚刚开始)用相应的颜色填充,然后检查圆圈是否包含鼠标位置。
    另外,如果您有空闲时间,请查看Shape 课程文档。

    我已经着手对您的代码进行了更改,它现在使用 Shape 类的实例来创建一个圆圈。
    另外,我建议不要实现MouseListener 接口,而是扩展MouseAdapter,因为除了mousePressed() 方法之外,您实际上并没有为接口的任何方法提供任何有意义的实现。
    最后,注意mousePressed() 方法中的shape.contains(event.getPoint()),这就是检查坐标的诀窍。
    其余的代码应该很熟悉了。

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.*;
    
    public class CirclePop {
    
        JFrame frame;
        Circle circle;
        public static void main(String[] args) {
            CirclePop circlePop = new CirclePop();
            circlePop.drawFrame();
        }
    
        public void drawFrame() {
            frame = new JFrame();
            circle  = new Circle();
    
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(circle);
    
            circle.addMouseListener(new Click());
    
            frame.setSize(300, 300);
            frame.setVisible(true);
        }
    
        class Click extends MouseAdapter {
            @Override
            public void mousePressed(MouseEvent e) {
                if (circle.shape.contains(e.getPoint())) {
                    System.out.println("Pressed");
                }
            }
        }
    }
    class Circle extends JPanel {
        Shape shape;
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            shape = new Ellipse2D.Double(150, 140, 30, 30);
            g2.setColor(Color.red);
            g2.fill(shape);
        }
    }
    

    【讨论】:

      【解决方案3】:

      好的,所以,这不会很短

      让我们从 .... 开始吧。

          frame = new JFrame();
          circle  = new Circle();
      
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(circle);
      
          circle.addMouseListener(new Click());
      
          frame.setSize(300, 300);
          frame.setVisible(true);
      

      好的,看起来很简单,但是,您错过的一件事是 JFrame 默认使用 BorderLayout - 这意味着它将使子组件(以及中心/默认位置) 填充框架可视空间的所有可用空间

      如果您执行类似...的操作,您会看到这一点

          frame = new JFrame();
          circle  = new Circle();
          circle.setBackground(Color.RED);
      

      您现在将看到Circle 组件占据了整个框架,因此当您单击它时,您正在单击Circle 组件本身。

      这还不错,但是,您可能想稍微改变一下技巧。例如,让Circle 组件使用它自己的MouseListener,而不是独立于Circle 添加MouseListener...

      class Circle extends JPanel {
      
          public Circle() {
              addMouseListener(new MouseAdapter() {
                  @Override
                  public void mouseClicked(MouseEvent e) {
                      // More to come...
                  }
              });
          }
          
          @Override
          protected void paintComponent(Graphics g) {
              super.paintComponent(g);
      
              g.setColor(Color.red);
              g.fillOval(150, 140, 30, 30);
          }
      }
      

      这意味着您可以控制类内部的大部分逻辑,从而更容易访问一些更关键的信息,而无需进行大量潜在的危险转换。

      所以,现在我们只需要添加逻辑来确定鼠标是否在所需位置被点击...

      public void mouseClicked(MouseEvent e) {
          Point point = e.getPoint();
          if (point.x >= 150 && point.x <= 150 + 30 && point.y >= 140 && point.y <= 140 + 30) {
              System.out.println("You clicked me :(");
          }
      }
      

      好的,这是……基本的

      我们可以稍微简化一下,并通过使用“形状”API 来利用更广泛 API 中的可用功能,例如...

      class Circle extends JPanel {
          
          private Ellipse2D dot = new Ellipse2D.Double(150, 140, 30, 30);
      
          public Circle() {
              addMouseListener(new MouseAdapter() {
                  @Override
                  public void mouseClicked(MouseEvent e) {
                      Point point = e.getPoint();
                      if (dot.contains(point)) {
                          System.out.println("You clicked me :(");
                      }
                  }
              });
          }
      
          @Override
          protected void paintComponent(Graphics g) {
              super.paintComponent(g);
              Graphics2D g2d = (Graphics2D) g.create();
      
              g2d.setColor(Color.red);
              g2d.fill(dot);
              g2d.dispose();
          }
      }
      

      这样做的好处是,除了contains,我们可以相对轻松地更改形状的位置,并且我们的if 语句包含工作?

      我非常愿意,建议您也看看

      【讨论】:

      • 如果该点在圆内,则您的计算不正确。您正在测试该点是否在边长为 30 的某个正方形内,而不是在一个圆内,不是吗?
      • @AlexR 我不是要“超级准确”-我要“使用可用 API”的“基本”-对于“最简单”的应用程序,“正方形”就足够了-除非它超大,否则大多数用户不会注意到 ;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-25
      相关资源
      最近更新 更多