简体   繁体   English

Java Swing:通过在 JPanel 上单击鼠标制作一个不断增长的圆圈

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

I'm a beginner in Java and this time I'm trying to learn more by finding code examples and editing them, for example from this website.我是 Java 的初学者,这次我试图通过查找代码示例并编辑它们来了解更多信息,例如从这个网站。 I have a JFrame and each time it (or more precise the JPanel in it) is clicked on, a circle is drawn which will grow/expand outwards like a water ripple.我有一个 JFrame,每次点击它(或更准确地说是其中的 JPanel)时,都会绘制一个圆圈,该圆圈将像水波纹一样向外扩展/扩展。 Each circle starts with a certain radius and will be removed or redrawn when reaching a bigger radius (I chose radius "r" from 10 to 200).每个圆都以一定的半径开始,当达到更大的半径时将被删除或重新绘制(我选择了半径“r”从 10 到 200)。 I have two programs for this which almost work but are missing something.我有两个程序,它们几乎可以工作,但缺少一些东西。 If I'm correct, I might also know what their problems are but I can't figure out how to solve them:如果我是对的,我可能也知道他们的问题是什么,但我不知道如何解决它们:

One generates growing circles but all of them have the same size, no matter when they're generated, since I can't find out how to assign the radius to a single circle.一个生成越来越大的圆,但无论何时生成,它们都具有相同的大小,因为我不知道如何将半径分配给单个圆。 The code is adapted from here :代码改编自这里

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();
    }

}

The other program(I've forgotten where I got it from) has circles with different radii depending on when they were generated, however the circles "flicker", because -as far as I understand- they are all processed at the same time, because the code doesn't include an Array to store and update each circle individually like the one above does:另一个程序(我忘了我从哪里得到的)有不同半径的圆,这取决于它们何时生成,但是这些圆“闪烁”,因为——据我所知——它们都是同时处理的,因为代码不包含一个数组来单独存储和更新每个圆,就像上面的那样:

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();
    }
    
}

So how can I solve this so that I get the circles growing independent from each other?那么我该如何解决这个问题,以便让圆圈相互独立? I'd prefer a solution/improvement/hint for the upper code because I think its structured better than the second one.我更喜欢上层代码的解决方案/改进/提示,因为我认为它的结构比第二个更好。 Also, I apologize for not splitting the code into more classes and for possibly not sticking to naming conventions.另外,我很抱歉没有将代码分成更多的类,并且可能没有遵守命名约定。 I appreciate your help, thank you very much!感谢您的帮助,非常感谢!

I'd prefer a solution/improvement/hint for the upper code我更喜欢上层代码的解决方案/改进/提示

The second code is better because it uses:第二个代码更好,因为它使用:

  1. a custom class to contain information about the object to be painted包含有关要绘制的对象的信息的自定义类
  2. an ArrayList to contain the objects to be painted一个 ArrayList 包含要绘制的对象
  3. a Timer for the animation.动画的计时器。

because I think its structured better than the second one.因为我认为它的结构比第二个更好。

Not a good reason.不是一个很好的理由。 Use the code that provides the functionality that you require.使用提供所需功能的代码。

Restructure the code yourself.自己重构代码。 That is part of the learning experience.这是学习经历的一部分。

Issues with the second code:第二个代码的问题:

  1. It doesn't compile.它不编译。 Why post code that doesn't compile?为什么发布无法编译的代码? This implies you haven't even tested it.这意味着您甚至还没有测试过它。
  2. the initial radius is assigned when the Position object is created.创建Position对象时分配初始半径。
  3. When the Timer fires you need to iterate through the ArrayList to update the radius of each Position object.当 Timer 触发时,您需要遍历 ArrayList 以更新每个Position对象的半径。
  4. The radius of the Position object is used in the painting code. Position对象的半径用于绘制代码。

As an added change, maybe call the Position class Ripple .作为附加的更改,可以调用PositionRipple Then you can add another custom property for the Color of the ripple.然后您可以为涟漪的Color添加另一个自定义属性。 Then when you add the Ripple to the ArrayList you randomly generate a Color.然后,当您将Ripple添加到 ArrayList 时,您会随机生成一个颜色。 Then in the painting method you use the Color property of the Ripple class.然后在绘制方法中使用Ripple类的 Color 属性。 This is how you make objects and painting more flexible and dynamic.这就是您使对象和绘画更加灵活和动态的方式。

I added a Circle class to your Ripples code.我在你的Ripples代码中添加了一个Circle类。 This allows the ActionListener to treat each circle independently.这允许ActionListener独立处理每个圆。

I started the GUI with a call to the SwingUtilities invokeLater method.我通过调用SwingUtilities invokeLater方法启动了 GUI。 This method ensures that the Swing components are created and executed on the Event Dispatch Thread .此方法可确保在Event Dispatch Thread上创建和执行 Swing 组件。

Here's the code.这是代码。

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;
        }

    }

}

Edited to add:编辑添加:

I reworked the Ripples class code to separate the concerns.我重新编写了Ripples类代码以分离关注点。 I created a DrawingPanel class to hold the drawing panel, a RipplesListener class to hold the MouseAdapter code, an Animation class to hold the Runnable that runs the animation of the circles, a RipplesModel class to hold the List of Circle instances, and finally, the Circle class.我创建了一个DrawingPanel类来保存绘图面板、一个RipplesListener类来保存MouseAdapter代码、一个Animation类来保存运行圆圈动画的Runnable 、一个RipplesModel类来保存Circle实例List ,最后是Circle类。

I could have used a Swing Timer for the animation, but I'm more familiar with creating and running my own animation thread.我本可以为动画使用 Swing Timer ,但我更熟悉创建和运行我自己的动画线程。

Yes, this code is more complicated than the original example.是的,此代码比原始示例更复杂。 The coding style used here can be carried into larger, more complex Swing GUI development.此处使用的编码风格可以用于更大、更复杂的 Swing GUI 开发。

Here's the revised code.这是修改后的代码。 I hope it's a better example.我希望这是一个更好的例子。

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;
        }

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM