Java Swing: создание растущего круга щелчком мыши на JPanel

Я новичок в Java, и на этот раз я пытаюсь узнать больше, находя примеры кода и редактируя их, например, с этого сайта. У меня есть JFrame, и каждый раз, когда на него (точнее, на JPanel в нем) нажимают, рисуется круг, который будет расти/расширяться наружу, как водная рябь. Каждый круг начинается с определенного радиуса и будет удаляться или перерисовываться при достижении большего радиуса (я выбрал радиус 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();
    }
    
}

Итак, как я могу решить эту проблему, чтобы круги росли независимо друг от друга? Я бы предпочел решение/улучшение/подсказку для верхнего кода, потому что я думаю, что он структурирован лучше, чем второй. Кроме того, я приношу свои извинения за то, что не разделил код на несколько классов и, возможно, не придерживаюсь соглашений об именах. Я ценю вашу помощь, большое спасибо!


person Community    schedule 08.09.2020    source источник
comment
Для первого кода похоже, что r — константа, поэтому круги будут одинакового размера. Вам нужно будет изменить r в зависимости от того, где находится ваша мышь.   -  person NomadMaker    schedule 08.09.2020
comment
Спасибо за быстрый ответ. Я уже пытался конвертировать int r в int[] r и создавать циклы for для методов, использующих r. Также я пытался использовать не ArrayList‹Point›, а массив, который хранит значения для x, y и r, создавая новый класс, оба подхода не работали.   -  person    schedule 08.09.2020
comment
Вы должны показать нам код, в котором вы использовали объект с точкой и r в качестве нового класса. Это звучит как многообещающая атака.   -  person NomadMaker    schedule 08.09.2020
comment
Я перезаписал его, но это было так (класс и реализация): class Position { int x; инт у; инт г; публичная позиция (int x, int y, int r) { this.x = x; это.у = у; это.г = г; } } \\В классе Ripples: private ArrayList‹Position› p; \\В событии mousePressed конструктора Ripple: p.add(new Position(e.getX(),e.getY(),r));   -  person    schedule 08.09.2020
comment
Извините, но я не могу читать код в комментариях. Код предназначен для чтения в форматированном виде.   -  person NomadMaker    schedule 08.09.2020
comment
Отредактировал, спасибо!   -  person    schedule 08.09.2020
comment
Второй пример мерцает, потому что несколько панелей Ripples расположены друг над другом. Я не уверен, как это возможно; f.add(rG) следует удалить все предыдущие панели из BorderLayout панели содержимого JFrame перед добавлением новой.   -  person VGR    schedule 08.09.2020
comment
Поскольку вы новичок, вы не можете отличить плохие примеры от хороших. В руководстве по Oracle Swing полно хороших примеров. Пока вы не поймете принципы Java Swing, вы не сможете создавать графические интерфейсы.   -  person Gilbert Le Blanc    schedule 08.09.2020


Ответы (2)


Я добавил класс Circle в ваш код Ripples. Это позволяет ActionListener обрабатывать каждый круг независимо.

Я запустил GUI вызовом метода SwingUtilities invokeLater. Этот метод обеспечивает создание и выполнение компонентов Swing в потоке отправки событий. .

Вот код.

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, чтобы разделить проблемы. Я создал класс DrawingPanel для панели рисования, класс RipplesListener для хранения кода MouseAdapter, класс Animation для хранения Runnable, который запускает анимацию кругов, класс RipplesModel для хранения List из Circle экземпляров, и, наконец, Circle класс.

Я мог бы использовать 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;
        }

    }

}
person Gilbert Le Blanc    schedule 08.09.2020
comment
Первое предложение из ОП. - Я новичок в Java, и на этот раз я пытаюсь узнать больше, находя примеры кода и редактируя их - трудно редактировать примеры, которые делают все, что хочет OP. - person camickr; 08.09.2020
comment
Большое спасибо! Я очень ценю это. - person ; 08.09.2020

Я бы предпочел решение/улучшение/подсказку для верхнего кода

Второй код лучше, потому что он использует:

  1. пользовательский класс, содержащий информацию об объекте, который нужно нарисовать
  2. ArrayList, содержащий объекты для рисования
  3. Таймер для анимации.

потому что я думаю, что он структурирован лучше, чем второй.

Не очень хорошая причина. Используйте код, обеспечивающий требуемую функциональность.

Реструктурируйте код самостоятельно. Это часть опыта обучения.

Проблемы со вторым кодом:

  1. Он не компилируется. Зачем публиковать код, который не компилируется? Это означает, что вы даже не тестировали его.
  2. начальный радиус назначается при создании объекта Position.
  3. Когда таймер срабатывает, вам нужно пройти через ArrayList, чтобы обновить радиус каждого объекта Position.
  4. Радиус объекта Position используется в коде рисования.

В качестве дополнительного изменения, возможно, вызовите класс Position Ripple. Затем вы можете добавить еще одно пользовательское свойство для Color ряби. Затем, когда вы добавляете Ripple в ArrayList, вы случайным образом генерируете цвет. Затем в методе рисования вы используете свойство Color класса Ripple. Таким образом вы делаете объекты и рисование более гибкими и динамичными.

person camickr    schedule 08.09.2020
comment
Спасибо за этот подробный ответ! Я думаю, что мой пост был недостаточно хорошо структурирован, потому что я слишком часто редактировал его - какое-то время у меня было три программы (третья была другой версией первой), потому что в комментариях меня попросили показать другой подход, который я пробовал (и знал, что это не сработало). - person ; 08.09.2020
comment
@user14240934 user14240934, я думал, ты хочешь учиться самостоятельно и менять код самостоятельно? Многому ли вы научитесь, если просто скопируете код, не зная, для чего он используется? Тем не менее, вы принимаете ответ, что просто ложка кормит код без каких-либо объяснений. Как это поможет в будущем? Причина, по которой вам пришлось задать вопрос, заключалась в том, что вы просто скопировали код, не понимая, что он делает. - person camickr; 08.09.2020
comment
Что ж, я принял этот ответ, потому что он выделяет решение не только для меня, но и для всех, кому это может быть интересно, поскольку есть много похожих сообщений (например, те, из которых я получил программы). - person ; 09.09.2020