Несколько анимаций через InvokeLater — Java

Я пытаюсь создать графический интерфейс, в котором по существу есть несколько прыгающих мячей. Мячи добавляются через JButton. Мне удалось успешно создать класс Ball и анимировать его на экране только с одним мячом, однако мне не удалось добавить несколько мячей с помощью кнопки. Я попытался создать ActionListener, который создает новые потоки и вызывает SwingUtilities.InvokeLater, но это только заморозило графический интерфейс. Я пробовал следовать этому руководству по использованию InvokeLater: http://www.javamex.com/tutorials/threads/invokelater.shtml

Вот мой код до сих пор. Любая помощь приветствуется. (Я понимаю, что вопрос о прыгающем мяче задавался здесь раньше, но я не смог понять методы, описанные в ответах, и подумал, что задам здесь, а не прокомментирую вопрос двухлетней давности)

Класс мяча

package BouncingBall;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Random;

public class Ball extends JComponent
{
    private double dx;
    private double dy;
    private double x;
    private double y;
    private Color color;
    private Random rand = new Random();
    private Ellipse2D.Double ball;
    private final static int diam = 10;
    private final static int xLim = BallFrame.FRAME_WIDTH-diam;
    private final static int yLim = BallFrame.FRAME_HEIGHT-diam*7;
    boolean xUpperBound = true;
    boolean yUpperBound = true;

    public Ball()
    {
        color = new Color(rand.nextFloat(),rand.nextFloat(),rand.nextFloat());
        x = rand.nextInt(xLim);
        y = rand.nextInt(yLim);
        dx = rand.nextInt(9)+1;
        dy = rand.nextInt(9)+1;
    }

    public void paintComponent(Graphics g)
    {
        Graphics2D g2 = (Graphics2D) g;
        draw(g2);
    }

    public void draw(Graphics2D g2)
    {
        ball = new Ellipse2D.Double(x,y,diam,diam);
        g2.setColor(color);
        g2.fill(ball);
    }

    public void move()
    {
        if (((x+dx) < xLim) && xUpperBound)
            x+=dx;
        else if (x > 0)
        {
            xUpperBound = false;
            x-=dx;
        }
        else
            xUpperBound = true;

        if (((y+dy) < yLim) && yUpperBound)
            y+=dy;
        else if (y > 0)
        {
            yUpperBound = false;
            y-=dy;
        }
        else
            yUpperBound = true;
    }

    public void animate()
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    while (true)
                    {
                        repaint();
                        move();
                        Thread.sleep(40);
                    }
                }
                catch (InterruptedException e)
                {
                    System.out.println("Thread was interrupted!");
                }
            }
        });
    }
}

Класс BallFrame

package BouncingBall;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

public class BallFrame extends JFrame
{
    public final static int FRAME_WIDTH = 800;
    public final static int FRAME_HEIGHT = 600;

    public BallFrame()
    {
        class ballListener implements ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent actionEvent)
            {
                Thread thread = new Thread()
                {
                    public void run()
                    {
                        Ball temp = new Ball();
                        temp.animate();
                        add(temp);
                    }
                };
                thread.start();
            }
        }

        setLayout(new BorderLayout());
        JButton addBall = new JButton("Add Ball");
        ActionListener listener = new ballListener();
        addBall.addActionListener(listener);
        add(addBall, BorderLayout.SOUTH);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                BallFrame frame = new BallFrame();
                frame.setSize(FRAME_WIDTH, FRAME_HEIGHT );
                frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
                frame.setVisible( true );
            }
        });
    }
}

person Mo2    schedule 22.05.2014    source источник
comment
Thread.sleep(40); Это неправильный подход к анимации. Я также не хотел бы, чтобы класс Ball расширял JComponent и не пытался (сам) выполнять цикл рисования. Вместо этого используйте Swing Timer в классе BallPanel (новый) и вызовите метод Ball.move() перед Ball.draw(g). используя pack().   -  person Andrew Thompson    schedule 22.05.2014
comment
Я тоже думал об использовании таймера, но было предложено использовать Thread.sleep в PDF-файле задания. Однако здесь нужно использовать многопоточность, так что любой способ работает. Что касается отказа от расширения JComponent, можете ли вы объяснить, почему? Я никогда не выполнял никаких сложных операций со свингом, поэтому почти все, что я делал, было с использованием расширения JComponent.   -  person Mo2    schedule 22.05.2014


Ответы (1)


Проблема, с которой я уже столкнулся при разработке, заключается в том, что вы делаете Ball расширяющим JComponent, что во многих отношениях уже ограничивает вас. Вместо этого вы должны просто сделать Ball простым классом держателя состояния/манипулирования состоянием с методом для анимации и рисования состояния мяча.

Тогда просто используйте один класс JComponent/JPanel для рисования шаров. В этом классе у вас может быть List<Ball>, который вы будете рисовать в методе paintComponent. Всякий раз, когда вы хотите добавить мяч, просто добавьте Ball в список.

Кроме того, вместо использования отдельных потоков, что не следует делать с рисованием (поскольку все рисование должно выполняться в EDT), вы должны вместо этого использовать javax.swing.Timer для временной анимации. Дополнительную информацию см. в разделе Как использовать таймеры Swing.

Вы также можете увидеть несколько примеров вышеуказанных методов здесь и здесь и здесь и здесь и здесь и здесь.


ОБНОВЛЕНИЕ

«Можете ли вы объяснить, что расширение JComponent ограничивает его? Что касается неиспользования потоков. Как мне сделать так, чтобы несколько объектов Ball анимировались независимо? Разве для этого не требуется несколько потоков?»

  1. Вы должны добавить несколько компонентов к одной видимой «поверхности рисования». Таким образом, в основном вам придется накладывать эти компоненты на поверхность, помимо прочего, имея дело с непрозрачностью компонентов.
  2. Вы используете javax.swing.Timer, в котором вы будете перебирать список и вызывать метод animate() каждого шара.
  3. NO.

См. примеры из ссылок, которые я публикую. У них есть примеры использования таймера и список объектов

Вот основная идея в некоторых фрагментах кода

Класс мяча

public class Ball {
    public void draw(Graphics g) {}
    public void animate() {}
}

Класс BallPanel

public class BallPanel extends JPanel {
    List<Ball> balls;

    public BallPanel() {
        Timer timer = new Timer(40, new ActionListener(){
            public void actionPerformed(ActionEvent e) {
                for (Ball ball : balls) {
                    ball.animate();
                }
                repaint();
            }
        });
        timer.start();
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Ball ball : ball ) {
            ball.draw(g);
        }
    }

    public void addBall(...) {
        balls.add(new Ball(..));
    }
}
person Paul Samsotha    schedule 22.05.2014
comment
Можете ли вы объяснить, что расширение JComponent ограничивает его? Что касается неиспользования потоков. Как сделать так, чтобы несколько объектов Ball анимировались независимо? Разве для этого не требуется несколько потоков? - person Mo2; 22.05.2014
comment
Спасибо тебе за это. После просмотра ваших примеров концепция стала яснее для меня. Единственное ограничение заключается в том, что это было для задания, которое специально требовало использования потоков, поскольку тема была о многопоточности. Любые идеи? - person Mo2; 22.05.2014
comment
Указывает ли требование использовать разные нити для каждого шара? - person Paul Samsotha; 22.05.2014