Java Swing:通过鼠标点击JPanel制作一个越来越大的圆圈

Java Swing: Making a growing circle by mouse click on JPanel

我是 Java 的初学者,这次我试图通过查找代码示例并对其进行编辑来学习更多信息,例如从该网站上。我有一个 JFrame,每次单击它(或更准确地说是其中的 JPanel)时,都会绘制一个圆 grow/expand 向外像水波纹一样。每个圆圈都以一定的半径开始,当达到更大的半径时将被删除或重新绘制(我选择半径“r”从 10 到 200)。我有两个程序,几乎可以工作,但缺少一些东西。如果我是对的,我可能也知道他们的问题是什么,但我不知道如何解决它们:

一个生成越来越大的圆,但无论何时生成,它们都具有相同的大小,因为我无法找到如何将半径分配给单个圆。代码改编自:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Ripples extends JPanel implements ActionListener{

    public int r = 10;
    private ArrayList<Point> p;
    
    public Ripples() {
        p = new ArrayList<>();
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                p.add(new Point(e.getX(), e.getY()));
            }
        });
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.CYAN);
        for (Point pt : p) {
            g2.drawOval(pt.x-r, pt.y-r, 2*r, 2*r);
        }
    }
    
        
    @Override
    public void actionPerformed(ActionEvent evt) {
        if(r<200){
            r++;
        } else {
            r = 10;
        }
        repaint();
    }
    
    public static void Gui() {
        JFrame f = new JFrame();
        Ripples p = new Ripples();
        p.setBackground(Color.WHITE);
        f.setContentPane(p);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(500,300);
        f.setVisible(true);
        Timer t = new Timer(20,p);
        t.start();
    }

    public static void main(String[] args) {
        Gui();
    }

}

另一个程序(我忘了我从哪里得到的)有不同半径的圆圈,这取决于它们生成的时间,但是圆圈“闪烁”,因为-据我所知-它们都被处理过同时,因为代码不包含像上面那样单独存储和更新每个圆的数组:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Ripples extends JPanel { 
    int x,y;
    int r = 10;
    
    public Ripples(int x, int y) {
        this.x = x;
        this.y = y; 
        Timer t = new Timer(20, new ActionListener() { 
            @Override
            public void actionPerformed(ActionEvent e) { 
                if (r<200) { 
                    r++;
                } else {
                    r=10;
                }
                revalidate();
                repaint();
            }
        }); 
        t.start(); 
    }

    @Override
    public void paintComponent(Graphics g) { 
        super.paintComponent(g);
        g.setColor(Color.CYAN);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.drawOval(x-r,y-r,2*r,2*r);
    }
    
    public static void Gui() {
        JFrame f = new JFrame("Water Ripples");
        JPanel p0 = new JPanel();
        p0.setBackground(Color.WHITE);
        f.add(p0);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setBounds(100,100,600,500);
        f.setVisible(true);
        f.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) { 
                Ripples rG = new Ripples(e.getX(), e.getY());
                rG.setBackground(Color.WHITE);
                f.add(rG); 
            }
         });
    }

    public static void main(String[] args) {
        Gui();
    }
    
}

那么我怎样才能解决这个问题,让圈子彼此独立成长呢?我更喜欢上面的代码 solution/improvement/hint 因为我认为它的结构比第二个更好。另外,对于没有将代码拆分为更多 类 以及可能不遵守命名约定,我深表歉意。感谢您的帮助,非常感谢!

I'd prefer a solution/improvement/hint for the upper code

第二个代码更好,因为它使用了:

  1. 自定义 class 以包含有关要绘制的对象的信息
  2. 包含要绘制的对象的 ArrayList
  3. 动画计时器。

because I think its structured better than the second one.

不是很好的理由。使用提供所需功能的代码。

自己重构代码。这是学习经验的一部分。

第二个代码有问题:

  1. 它不编译。为什么 post 代码无法编译?这意味着您甚至还没有测试它。
  2. 创建 Position 对象时分配初始半径。
  3. 当计时器触发时,您需要遍历 ArrayList 以更新每个 Position 对象的半径。
  4. 绘制代码中使用了Position对象的半径。

作为附加更改,可能会调用 Position class Ripple。然后你可以为波纹的Color添加另一个自定义属性。然后,当您将 Ripple 添加到 ArrayList 时,您会随机生成一个颜色。然后在画法中你使用了Rippleclass的Color属性。这就是让物体和绘画更加灵活和动态的方式。

我在您的 Ripples 代码中添加了 Circle class。这允许 ActionListener 独立处理每个圆圈。

我通过调用 SwingUtilities invokeLater 方法启动了 GUI。此方法确保 Swing 组件在 Event Dispatch Thread.

上创建和执行

这是代码。

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Ripples extends JPanel implements ActionListener {

    private List<Circle> circles;

    public Ripples() {
        circles = new ArrayList<>();
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent event) {
                circles.add(new Circle(event.getPoint()));
            }
        });
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.CYAN);
        g2.setStroke(new BasicStroke(3f));
        
        for (Circle circle : circles) {
            Point p = circle.getCenter();
            int radius = circle.getRadius();
            g2.drawOval(p.x - radius, p.y - radius,
                    2 * radius, 2 * radius);
        }
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
        for (Circle circle : circles) {
            circle.incrementRadius();
        }
        repaint();
    }

    public static void createGUI() {
        JFrame f = new JFrame("Ripples");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Ripples p = new Ripples();
        p.setBackground(Color.WHITE);
        p.setPreferredSize(new Dimension(500, 500));
        f.setContentPane(p);

        f.pack();
        f.setLocationByPlatform(true);
        f.setVisible(true);

        Timer t = new Timer(20, p);
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGUI();
            }
        });
    }

    public class Circle {

        private int radius;

        private final Point center;

        public Circle(Point center) {
            this.center = center;
            this.radius = 10;
        }

        public void incrementRadius() {
            radius += 1;
            radius = (radius > 200) ? 10 : radius;
        }

        public int getRadius() {
            return radius;
        }

        public Point getCenter() {
            return center;
        }

    }

}

编辑添加:

我修改了 Ripples class 代码以分离关注点。我创建了一个 DrawingPanel class 来保存绘图面板,一个 RipplesListener class 来保存 MouseAdapter 代码,一个 Animation class 来保存运行圆圈动画的 RunnableRipplesModel class 来保存 Circle 个实例的 List,最后,Circle class.

我本可以使用 Swing Timer 制作动画,但我更熟悉创建和 运行 我自己的动画线程。

是的,这段代码比原来的例子更复杂。此处使用的编码风格可以用于更大、更复杂的 Swing GUI 开发。

这是修改后的代码。我希望这是一个更好的例子。

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Ripples implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Ripples());
    }

    private Animation animation;

    private DrawingPanel drawingPanel;

    private RipplesModel model;

    public Ripples() {
        model = new RipplesModel();
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("Ripples");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                stopAnimation();
                frame.dispose();
                System.exit(0);
            }
        });

        drawingPanel = new DrawingPanel(model);
        frame.add(drawingPanel, BorderLayout.CENTER);

        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        animation = new Animation(this, model);
        new Thread(animation).start();
    }

    public void repaint() {
        drawingPanel.repaint();
    }

    private void stopAnimation() {
        if (animation != null) {
            animation.setRunning(false);
        }
    }

    public class DrawingPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        private RipplesModel model;

        public DrawingPanel(RipplesModel model) {
            this.model = model;
            setBackground(Color.WHITE);
            setPreferredSize(new Dimension(500, 500));
            addMouseListener(new RipplesListener(model));
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(3f));

            List<Circle> circles = model.getCircles();
            for (Circle circle : circles) {
                Point p = circle.getCenter();
                int radius = circle.getRadius();
                g2.setColor(circle.getColor());
                g2.drawOval(p.x - radius, p.y - radius,
                        2 * radius, 2 * radius);
            }
        }

    }

    public class RipplesListener extends MouseAdapter {

        private RipplesModel model;

        public RipplesListener(RipplesModel model) {
            this.model = model;
        }

        @Override
        public void mousePressed(MouseEvent event) {
            model.addCircle(new Circle(event.getPoint(),
                    createColor()));
        }

        private Color createColor() {
            Random random = new Random();
            int r = random.nextInt(255);
            int g = random.nextInt(255);
            int b = random.nextInt(255);
            return new Color(r, g, b);
        }
    }

    public class Animation implements Runnable {

        private volatile boolean running;

        private Ripples frame;

        private RipplesModel model;

        public Animation(Ripples frame, RipplesModel model) {
            this.frame = frame;
            this.model = model;
            this.running = true;
        }

        @Override
        public void run() {
            while (running) {
                sleep(20L);
                incrementRadius();
            }
        }

        private void incrementRadius() {
            List<Circle> circles = model.getCircles();
            for (Circle circle : circles) {
                circle.incrementRadius();
            }
            repaint();
        }

        private void sleep(long delay) {
            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private void repaint() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    frame.repaint();
                }
            });
        }

        public synchronized void setRunning(boolean running) {
            this.running = running;
        }

    }

    public class RipplesModel {

        private List<Circle> circles;

        public RipplesModel() {
            this.circles = new ArrayList<>();
        }

        public void addCircle(Circle circle) {
            this.circles.add(circle);
        }

        public List<Circle> getCircles() {
            return circles;
        }

    }

    public class Circle {

        private int radius;

        private final Color color;

        private final Point center;

        public Circle(Point center, Color color) {
            this.center = center;
            this.color = color;
            this.radius = 10;
        }

        public void incrementRadius() {
            radius = (++radius > 200) ? 10 : radius;
        }

        public Color getColor() {
            return color;
        }

        public int getRadius() {
            return radius;
        }

        public Point getCenter() {
            return center;
        }

    }

}