Анимация мяча в Swing

Проблема заключается в том, что когда я изменяю размер JFrame, анимация продолжается с предварительно заданными размерами для JComponent. Есть ли способ обновить переменные width и height при изменении размера JFrame, чтобы анимация могла работать вместе с новыми координатами.

Проще говоря, скажем, JComponent имеет начальные width = 300 и height = 300, поэтому BALL перемещается внутри указанных Co-ordinates. Теперь, если я изменю размер своего JFrame, размер для JComponent останется прежним, то есть width = 300 и height = 300, но я надеялся, что это способ изменить эти переменные с текущим размером окна. Дайте мне знать, если мне чего-то не хватает в объяснении моей проблемы.

Вот код, который я использую:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class BallAnimation
{
    private int x;
    private int y;
    private int count;
    private int width;
    private int height;
    private int speedValue;
    private boolean flag;
    /*
     * These variables are used to keep track of 
     * the ball, either it is going LEFT or RIGHT
     * depending on that, we will set the 
     * Co-ordinates.
     */
    private boolean toLeft, toRight;

    private boolean fromTop, fromBottom;

    private Timer timer;

    private JButton button;

    private ActionListener actionTimer; 
    private ActionListener buttonAction;

    public BallAnimation()
    {
        x = y = count = 0;
        flag = toLeft = false;
        toRight = true;
        fromTop = true;
        fromBottom = false;
        speedValue = 5;
    }

    public static void main(String args[])
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                BallAnimation animation = new BallAnimation();
                animation.go();
            }
        };      
        SwingUtilities.invokeLater(runnable);
    }

    public void go()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //JPanel contentPane = new JPanel();

        /*
         * Class Name : 
         * Java Naming Convention says that class names 
         * should be in Pascal Case, i.e. the first
         * letter of the class name should be capitalized
         * and every new word must start with a capitalized 
         * Alphabet.
         * For Example : 
         * public class ClassName{...}
         * ----------------------------------------------------------
         * Variable Name : 
         * Java Naming Convention says that the variable name
         * should be in Camel Case, i.e. the first letter of 
         * the variable name should be small case or _ (underscore)
         * and every new word must start with a capitalized
         * Alphabet.
         * ---------------------------------------------------------
         */
        final MyDraw drawPanel = new MyDraw(0, 0);
        x = drawPanel.getXValue();
        y = drawPanel.getYValue();
        //contentPane.add(drawPanel);

        actionTimer = new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {               
                if (fromTop && !fromBottom && x < width && y < height 
                            && toRight && !toLeft)
                {
                    x += speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x < width && y >= height
                                 && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the TOP LEFT Side
                     * touched the BOTTOM of the JPanel.
                     */
                    y -= speedValue;
                    x += speedValue;
                    fromTop = false;
                    fromBottom = true;
                }
                else if (!fromTop && fromBottom && x < width && y <= 0
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from BOTTOM LEFT Side
                     * touched the TOP of the JPanel. 
                     */
                    fromTop = true;
                    fromBottom = false;
                    x += speedValue;
                    y += speedValue;
                }
                else if (!fromTop && fromBottom && x < width && y < height
                                  && toRight && !toLeft)
                {
                    x += speedValue;
                    y -= speedValue;
                }
                else if (!fromTop && fromBottom && x >= width && y < height
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM LEFT Side
                     * touched the RIGHT Side of the JPanel.
                     */
                    toRight = false;
                    toLeft = true;
                    x -= speedValue;
                    y -= speedValue;
                }                
                else if (!fromTop && fromBottom && x < width && y <= 0
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM RIGHT Side
                     * touched the Top Side of the JPanel.
                     */
                    fromTop = true;
                    fromBottom = false;
                    x -= speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x <= 0 && y < height
                                 && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the TOP RIGHT Side
                     * touched the LEFT Side of the JPanel.
                     */
                    toRight = true;
                    toLeft = false;
                    x += speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x >= width && y < height
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the TOP LEFT Side
                     * touched the RIGHT Side of the JPanel
                     */
                    toRight = false;
                    toLeft = true;
                    x -= speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x < width && y < height
                                  && !toRight && toLeft)
                {
                    x -= speedValue;
                    y += speedValue;
                }
                else if (!fromTop && fromBottom && x <= 0 && y < height
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM RIGHT Side
                     * touched the LEFT Side of the JPanel.
                     */
                    toRight = true;
                    toLeft = false;
                    x += speedValue;
                    y -= speedValue;
                }
                else if (!fromTop && fromBottom && x < width && y < height
                                  && !toRight && toLeft)
                {
                    x -= speedValue;
                    y -= speedValue;
                }
                else if (fromTop && !fromBottom && x < width && y >= height
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the TOP RIGHT Side
                     * touched the BOTTOM Side of the JPanel.
                     */
                    fromTop = false;
                    fromBottom = true;
                    x -= speedValue;
                    y -= speedValue;
                }
                System.out.println("X : " + x);
                System.out.println("Y : " + y);
                System.out.println("Direction is LEFT : " + toLeft);
                System.out.println("Direction is RIGHT : " + toRight);
                System.out.println("Coming from TOP : " + fromTop);
                System.out.println("Coming from BOTTOM : " + fromBottom);
                drawPanel.setXYValues(x, y);
            }
        };

        buttonAction = new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {
                if (!flag)
                {
                    timer.start();
                    button.setText("STOP ANIMATION");
                    flag = true;
                }
                else if (flag)
                {
                    timer.stop();
                    button.setText("START ANIMATION");
                    flag = false;
                }
            }
        };

        button = new JButton("START ANIMATION");
        button.addActionListener(buttonAction);

        frame.getContentPane().add(drawPanel, BorderLayout.CENTER);
        frame.getContentPane().add(button, BorderLayout.PAGE_END);
        frame.setSize(300, 300);
        //frame.pack();
        frame.setVisible(true);        

        timer = new Timer(40, actionTimer);
        width = drawPanel.getWidth() - 30;
        System.out.println("WIDTH : " + width);
        height = drawPanel.getHeight() - 30;    
        System.out.println("HEIGHT : " + height);
    }
    class MyDraw extends JComponent
    {
        private int x;
        private int y;
        private Timer timer;

        public MyDraw(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public int getXValue()
        {
            return x;
        }

        public int getYValue()
        {
            return y;
        }

        public void setXYValues(int x, int y)
        {
            this.x = x;
            this.y = y;
            repaint();
        }

        public Dimension getPreferredSize()
        {
            return (new Dimension(300, 300));
        }

        public void paintComponent(Graphics g)
        {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(Color.BLUE);
            g.fillOval(x, y, 40, 40);
        }
    }
}

person nIcE cOw    schedule 21.03.2012    source источник
comment
Вы знаете, что огромное количество логики if можно было бы сжать примерно до 1/4 этого размера и намного меньше кода, верно? Использование скоростей x и y (которые могут быть отрицательными) устранит логические флаги, превратит весь код движения в x+=xVel;y+=yVel;, и тогда вы просто останетесь с проверкой выхода за пределы поля, которая толкает мяч обратно внутрь. ограничивает и инвертирует соответствующую скорость.   -  person zebediah49    schedule 21.03.2012
comment
@zebediah49 zebediah49: Пожалуйста, объясните мне эту логику немного подробнее в своем ответе :-)   -  person nIcE cOw    schedule 21.03.2012
comment
Это примерно 7-летний фрагмент кода, поэтому обработка графики устарела. Тем не менее, управление и логика стены стандартны. Он такой длинный, потому что у него есть как твердые (bounce), так и периодические (!bounce) граничные условия. pastebin.com/UT8fyAaB   -  person zebediah49    schedule 21.03.2012
comment
во время изменения размера родителя должен быть неактивен Swing Timer или удерживаться последнее известное измерение +1   -  person mKorbel    schedule 21.03.2012
comment
@zebediah49 Забавный код! Эти предупреждения можно исправить в нескольких строках кода. Поэтому я исправил их и добавил новую пасту. ;)   -  person Andrew Thompson    schedule 21.03.2012
comment
@mKorbel: Спасибо за эту информацию об отключении таймера во время изменения размера, этот вопрос только что прояснился относительно того, где я должен поместить код для остановки таймера :-)   -  person nIcE cOw    schedule 21.03.2012
comment
Вам также может понравиться этот KineticModel, в котором используется ComponentListener.   -  person trashgod    schedule 21.03.2012
comment
@trashgod : Вау, это потрясающе, кажется, у тебя есть пример на все случаи жизни, хе-хе, слишком хорошо :-) Спасибо, что поделился этой ссылкой :-) LOL, делая один мяч, я думал, что сделал что-то отличное, но по этой ссылке , LOL, их слишком много, кажется, мне нужно слишком многому научиться :-)   -  person nIcE cOw    schedule 21.03.2012


Ответы (3)


Теперь это целая куча кода! Попробуйте этот вариант (ломает одну или две вещи, но устраняет основную проблему). Исправление заключается в том, чтобы основывать ширину/высоту на текущем размере компонента.

package test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class BallAnimation
{
    private int x;
    private int y;
    private int count;
    //private int drawPanel.getWidth();
    //private int drawPanel.getHeight();
    private int speedValue;
    private boolean flag;
    /*
     * These variables are used to keep track of 
     * the ball, either it is going LEFT or RIGHT
     * depending on that, we will set the 
     * Co-ordinates.
     */
    private boolean toLeft, toRight;

    private boolean fromTop, fromBottom;

    private Timer timer;

    private JButton button;

    private ActionListener actionTimer; 
    private ActionListener buttonAction;

    MyDraw drawPanel;

    public BallAnimation()
    {
        x = y = count = 0;
        flag = toLeft = false;
        toRight = true;
        fromTop = true;
        fromBottom = false;
        speedValue = 5;
    }

    public static void main(String args[])
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                BallAnimation animation = new BallAnimation();
                animation.go();
            }
        };      
        SwingUtilities.invokeLater(runnable);
    }

    public void go()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //JPanel contentPane = new JPanel();

        /*
         * Class Name : 
         * Java Naming Convention says that class names 
         * should be in Pascal Case, i.e. the first
         * letter of the class name should be capitalized
         * and every new word must start with a capitalized 
         * Alphabet.
         * For Example : 
         * public class ClassName{...}
         * ----------------------------------------------------------
         * Variable Name : 
         * Java Naming Convention says that the variable name
         * should be in Camel Case, i.e. the first letter of 
         * the variable name should be small case or _ (underscore)
         * and every new word must start with a capitalized
         * Alphabet.
         * ---------------------------------------------------------
         */
        drawPanel = new MyDraw(0, 0);
        x = drawPanel.getXValue();
        y = drawPanel.getYValue();
        //contentPane.add(drawPanel);

        actionTimer = new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {               
                if (fromTop && !fromBottom && x < drawPanel.getWidth() && y < drawPanel.getHeight() 
                            && toRight && !toLeft)
                {
                    x += speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x < drawPanel.getWidth() && y >= drawPanel.getHeight()
                                 && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the TOP LEFT Side
                     * touched the BOTTOM of the JPanel.
                     */
                    y -= speedValue;
                    x += speedValue;
                    fromTop = false;
                    fromBottom = true;
                }
                else if (!fromTop && fromBottom && x < drawPanel.getWidth() && y <= 0
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from BOTTOM LEFT Side
                     * touched the TOP of the JPanel. 
                     */
                    fromTop = true;
                    fromBottom = false;
                    x += speedValue;
                    y += speedValue;
                }
                else if (!fromTop && fromBottom && x < drawPanel.getWidth() && y < drawPanel.getHeight()
                                  && toRight && !toLeft)
                {
                    x += speedValue;
                    y -= speedValue;
                }
                else if (!fromTop && fromBottom && x >= drawPanel.getWidth() && y < drawPanel.getHeight()
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM LEFT Side
                     * touched the RIGHT Side of the JPanel.
                     */
                    toRight = false;
                    toLeft = true;
                    x -= speedValue;
                    y -= speedValue;
                }                
                else if (!fromTop && fromBottom && x < drawPanel.getWidth() && y <= 0
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM RIGHT Side
                     * touched the Top Side of the JPanel.
                     */
                    fromTop = true;
                    fromBottom = false;
                    x -= speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x <= 0 && y < drawPanel.getHeight()
                                 && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the TOP RIGHT Side
                     * touched the LEFT Side of the JPanel.
                     */
                    toRight = true;
                    toLeft = false;
                    x += speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x >= drawPanel.getWidth() && y < drawPanel.getHeight()
                                  && toRight && !toLeft)
                {
                    /*
                     * Since the ball coming from the TOP LEFT Side
                     * touched the RIGHT Side of the JPanel
                     */
                    toRight = false;
                    toLeft = true;
                    x -= speedValue;
                    y += speedValue;
                }
                else if (fromTop && !fromBottom && x < drawPanel.getWidth() && y < drawPanel.getHeight()
                                  && !toRight && toLeft)
                {
                    x -= speedValue;
                    y += speedValue;
                }
                else if (!fromTop && fromBottom && x <= 0 && y < drawPanel.getHeight()
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the BOTTOM RIGHT Side
                     * touched the LEFT Side of the JPanel.
                     */
                    toRight = true;
                    toLeft = false;
                    x += speedValue;
                    y -= speedValue;
                }
                else if (!fromTop && fromBottom && x < drawPanel.getWidth() && y < drawPanel.getHeight()
                                  && !toRight && toLeft)
                {
                    x -= speedValue;
                    y -= speedValue;
                }
                else if (fromTop && !fromBottom && x < drawPanel.getWidth() && y >= drawPanel.getHeight()
                                  && !toRight && toLeft)
                {
                    /*
                     * Since the ball coming from the TOP RIGHT Side
                     * touched the BOTTOM Side of the JPanel.
                     */
                    fromTop = false;
                    fromBottom = true;
                    x -= speedValue;
                    y -= speedValue;
                }
                System.out.println("X : " + x);
                System.out.println("Y : " + y);
                System.out.println("Direction is LEFT : " + toLeft);
                System.out.println("Direction is RIGHT : " + toRight);
                System.out.println("Coming from TOP : " + fromTop);
                System.out.println("Coming from BOTTOM : " + fromBottom);
                drawPanel.setXYValues(x, y);
            }
        };

        buttonAction = new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {
                if (!flag)
                {
                    timer.start();
                    button.setText("STOP ANIMATION");
                    flag = true;
                }
                else if (flag)
                {
                    timer.stop();
                    button.setText("START ANIMATION");
                    flag = false;
                }
            }
        };

        button = new JButton("START ANIMATION");
        button.addActionListener(buttonAction);

        frame.getContentPane().add(drawPanel, BorderLayout.CENTER);
        frame.getContentPane().add(button, BorderLayout.PAGE_END);
        frame.setSize(300, 300);
        //frame.pack();
        frame.setVisible(true);        

        timer = new Timer(40, actionTimer);
        System.out.println("WIDTH : " + drawPanel.getWidth());
        System.out.println("HEIGHT : " + drawPanel.getHeight());
    }
    class MyDraw extends JComponent
    {
        private int x;
        private int y;
        private Timer timer;

        public MyDraw(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public int getXValue()
        {
            return x;
        }

        public int getYValue()
        {
            return y;
        }

        public void setXYValues(int x, int y)
        {
            this.x = x;
            this.y = y;
            repaint();
        }

        public Dimension getPreferredSize()
        {
            return (new Dimension(300, 300));
        }

        public void paintComponent(Graphics g)
        {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(Color.BLUE);
            g.fillOval(x, y, 40, 40);
        }
    }
}
person Andrew Thompson    schedule 21.03.2012
comment
Извините за поздний ответ, хотя мне понравился этот ответ (+1 за это), у меня все заработало, но, как указал @zebediah49, мой код - дерьмо, что касается логики, поэтому похоже на добавление вызова функции это ухудшит ситуацию вместе с проверкой состояния. Спасибо за помощь в этом вопросе :-) - person nIcE cOw; 21.03.2012
comment
Сегодня я изменил логическую часть своей программы, и, похоже, то, что вы сказали в своем ответе, работает слишком хорошо, так что ничего Listener не требуется. Раньше я проверял и x, и y вместе, что приводило к слишком большому количеству проверок условий. Я разделил их, что привело к нескольким строкам проверки, а ваш совет сработал безупречно :-) Спасибо за это :-) - person nIcE cOw; 23.03.2012
comment
изменил логическую часть моей программы Отлично! Рад видеть, что вы воспользовались советом zebediah49 (рефакторить логику). :) - person Andrew Thompson; 23.03.2012
comment
Да, я учился отовсюду :-), чтобы сосредоточиться на моей логической части от zebediah49, больше информации о шаровых штуковинах от @trashgod и, очевидно, также по вашему предложению, в конце все сотворило чудеса для моего небольшого приложения :-) - person nIcE cOw; 23.03.2012
comment
Я учился везде Везде здорово. Я часто делаю быстрый поиск в Google и заканчиваю 3 отдельными потоками в SO, получая лакомые кусочки от каждого. ;) - person Andrew Thompson; 23.03.2012

просто нужно addHierarchyBoundsListener(...) к вашему объекту MyDraw, то есть drawPanel, как описано ниже:

private HierarchyBoundsListener boundsListener = 
                                new HierarchyBoundsListener()
{
    public void ancestorMoved(HierarchyEvent he)
    {
    }

    public void ancestorResized(HierarchyEvent he)
    {
        JComponent component = (JComponent) he.getComponent();
        width = component.getWidth() - 30;
        height = component.getHeight() - 30;
    }
};

И чтобы добавить это к вашему объекту drawPanel, вы должны сделать:

drawPanel.addHierarchyBoundsListener(boundsListener);
person Community    schedule 21.03.2012
comment
Это сделало мой день, добавив HierarchyBoundsListener. Спасибо за посильную помощь :-) - person nIcE cOw; 21.03.2012

Я ожидаю, что вам просто нужно привязать какой-то тип слушателя (WindowEventListener?) к JFrame, чтобы width и height обновлялись всякий раз, когда изменяются размеры окна.

person zebediah49    schedule 21.03.2012
comment
Нет, лучше основывать ширину/высоту на текущем размере компонента, как видно в моем варианте. - person Andrew Thompson; 21.03.2012
comment
Я не обязательно не согласен, но мне любопытно, почему лучше получать его каждый раз, когда вы его используете? (Кроме того, почему бы не присвоить его локальной переменной вместо того, чтобы вызывать его каждый раз?) - person zebediah49; 21.03.2012
comment
Вызов текущего размера требует минимальных затрат, и, как отметил OP, текущая версия не работает при изменении размера. Можно было бы реализовать обновление размера с помощью прослушивателя, но это кажется неуклюжим хаком по сравнению с запросом размера, когда это необходимо. - person Andrew Thompson; 21.03.2012
comment
@ zebediah49 : Спасибо, что указали на то, что мой код - дерьмо, +1 за это :-), просто это моя первая картина, поэтому я никогда особо не беспокоился об этом, за исключением текущей задачи, которая заключается в рисовании :-) . Спасибо за вашу помощь в этом. - person nIcE cOw; 21.03.2012