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

你说的曾经没有我的故事 提交于 2021-01-29 22:39:32

问题


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. 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. 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). 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!


回答1:


I added a Circle class to your Ripples code. This allows the ActionListener to treat each circle independently.

I started the GUI with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.

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. 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.

I could have used a Swing Timer for the animation, but I'm more familiar with creating and running my own animation thread.

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.

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

    }

}



回答2:


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
  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.
  3. When the Timer fires you need to iterate through the ArrayList to update the radius of each Position object.
  4. The radius of the Position object is used in the painting code.

As an added change, maybe call the Position class Ripple. Then you can add another custom property for the Color of the ripple. Then when you add the Ripple to the ArrayList you randomly generate a Color. Then in the painting method you use the Color property of the Ripple class. This is how you make objects and painting more flexible and dynamic.



来源:https://stackoverflow.com/questions/63793252/java-swing-making-a-growing-circle-by-mouse-click-on-jpanel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!