How to animate Rectangle in JPanel?

2020-03-30 09:49发布

问题:

I want to learn some tricks about JAVA for my project.

I want to animate my Rectangle leftoright and righttoleft but I can't apply the same functions for ball animation.

In addition,how can I start my ball in different x-direction with a border of y-coordinate ?

Thanks a lot for your advices and helping.

My codes:

import javax.swing.Timer;
import java.util.ArrayList;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class MultipleBall extends JApplet {
public MultipleBall() {
    add(new BallControl());
}

class BallControl extends JPanel {
    private BallPanel ballPanel = new BallPanel();
    private JButton Suspend = new JButton("Suspend");
    private JButton Resume = new JButton("Resume");
    private JButton Add = new JButton("+1");
    private JButton Subtract = new JButton("-1");
    private JScrollBar Delay = new JScrollBar();

    public BallControl() {
        // Group buttons in a panel
        JPanel panel = new JPanel();
        panel.add(Suspend);
        panel.add(Resume);
        panel.add(Add);
        panel.add(Subtract);

        // Add ball and buttons to the panel
        ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
        Delay.setOrientation(JScrollBar.HORIZONTAL);
        ballPanel.setDelay(Delay.getMaximum());
        setLayout(new BorderLayout());
        add(Delay, BorderLayout.NORTH);
        add(ballPanel, BorderLayout.CENTER);
        add(panel, BorderLayout.SOUTH);

        // Register listeners
        Suspend.addActionListener(new Listener());
        Resume.addActionListener(new Listener());
        Add.addActionListener(new Listener());
        Subtract.addActionListener(new Listener());
        Delay.addAdjustmentListener(new AdjustmentListener() {

            public void adjustmentValueChanged(AdjustmentEvent e) {
                ballPanel.setDelay(Delay.getMaximum() - e.getValue());
            }
        });
    }

    class Listener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == Suspend)
                ballPanel.suspend();
            else if (e.getSource() == Resume)
                ballPanel.resume();
            else if (e.getSource() == Add)
                ballPanel.add();
            else if (e.getSource() == Subtract)
                ballPanel.subtract();
        }
    }
}

class BallPanel extends JPanel {
    private int delay = 30;
    private ArrayList<Ball> list = new ArrayList<Ball>();

    // Create a timer with the initial delay
    protected Timer timer = new Timer(delay, new ActionListener() {
        /** Handle the action event */
        public void actionPerformed(ActionEvent e) {
            repaint();
        }
    });

    public BallPanel() {
        timer.start();
    }

    public void add() {
        list.add(new Ball());
    }

    public void subtract() {
        if (list.size() > 0)
            list.remove(list.size() - 1); // Remove the last ball
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawRect(185, 279, 50, 15);
        g.setColor(Color.RED);
        g.fillRect(185, 279, 50, 15);

        for (int i = 0; i < list.size(); i++) {
            Ball ball = (Ball) list.get(i); // Get a ball
            g.setColor(ball.color); // Set ball color

            // Check boundaries
            if (ball.x < 0 || ball.x > getWidth())
                ball.dx = -ball.dx;

            if (ball.y < 0 || ball.y > getHeight())
                ball.dy = -ball.dy;

            // Adjust ball position
            ball.x += ball.dx;
            // ball.y += ball.dy;
            g.fillOval(ball.x - ball.radius, ball.y - ball.radius,
                    ball.radius * 2, ball.radius * 2);
        }
    }

    public void suspend() {
        timer.stop();
    }

    public void resume() {
        timer.start();
    }

    public void setDelay(int delay) {
        this.delay = delay;
        timer.setDelay(delay);
    }
}

class Ball {
    int x = 20;
    int y = 20; // Current ball position
    int dx = 2; // Increment on ball's x-coordinate
    int dy = 2; // Increment on ball's y-coordinate
    int radius = 15; // Ball radius
    Color color = new Color((int) (Math.random() * 256),
            (int) (Math.random() * 256), (int) (Math.random() * 256));
}

/** Main method */
public static void main(String[] args) {
    JFrame frame = new JFrame();
    JApplet applet = new MultipleBallApp();
    frame.add(applet);
    frame.setTitle("MultipleBallApp");
    frame.setLocationRelativeTo(null); // Center the frame
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(400, 400);
    frame.setLocationRelativeTo(null); // Center the frame
    frame.setVisible(true);
}
}

回答1:

can't apply the same functions for ball animation

This is probably your first mistake. In fact, this is exactly what you should be trying to do. The idea is, you should be trying to devise a means by what is painted/animated is abstract so it doesn't matter what the shape is you want to paint, you can apply it to the sam basic animation process...

For example, you could start with some kind interface which describes the basic properties of an animated entity...

public interface AnimatedShape {
    public void update(Rectangle bounds);
    public void paint(JComponent parent, Graphics2D g2d);
}

This says that an animated entity can be updated (moved) and painted. By convention (and because I'm lazy), I like to create an abstract implementation which implements the most common aspects...

public abstract class AbstractAnimatedShape implements AnimatedShape {

    private Rectangle bounds;
    private int dx, dy;

    public AbstractAnimatedShape() {
    }

    public void setBounds(Rectangle bounds) {
        this.bounds = bounds;
    }

    public Rectangle getBounds() {
        return bounds;
    }

    public int getDx() {
        return dx;
    }

    public int getDy() {
        return dy;
    }

    public void setDx(int dx) {
        this.dx = dx;
    }

    public void setDy(int dy) {
        this.dy = dy;
    }

    @Override
    public void update(Rectangle parentBounds) {
        Rectangle bounds = getBounds();
        int dx = getDx();
        int dy = getDy();
        bounds.x += dx;
        bounds.y += dy;
        if (bounds.x  < parentBounds.x) {
            bounds.x = parentBounds.x;
            setDx(dx *= -1);
        } else if (bounds.x + bounds.width > parentBounds.x + parentBounds.width) {
            bounds.x = parentBounds.x + (parentBounds.width - bounds.width);
            setDx(dx *= -1);
        }
        if (bounds.y < parentBounds.y) {
            bounds.y = parentBounds.y;
            setDy(dy *= -1);
        } else if (bounds.y + bounds.height > parentBounds.y + parentBounds.height) {
            bounds.y = parentBounds.y + (parentBounds.height - bounds.height);
            setDy(dy *= -1);
        }
    }
}

And then start creating implementations...

public class AnimatedBall extends AbstractAnimatedShape {

    private Color color;

    public AnimatedBall(int x, int y, int radius, Color color) {
        setBounds(new Rectangle(x, y, radius * 2, radius * 2));
        this.color = color;
        setDx(Math.random() > 0.5 ? 2 : -2);
        setDy(Math.random() > 0.5 ? 2 : -2);
    }

    public Color getColor() {
        return color;
    }

    @Override
    public void paint(JComponent parent, Graphics2D g2d) {
        Rectangle bounds = getBounds();
        g2d.setColor(getColor());
        g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
    }
}

In this manner, you can customise the way that the entity is animated and painted, but the basic logic for each instance of the entity is the same...

But what's all the point of this...

Basically, what it allows us to do is produce a "virtual" concept of all the animated objects and simplify there management, for example...

Instead of using a "tightly" coupled List, we can use a loosely couple List instead...

private ArrayList<AnimatedShape> list = new ArrayList<AnimatedShape>();

Then when we want the entities to be updated, we simply need to iterate the List and ask the entities to update...

protected Timer timer = new Timer(delay, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        for (AnimatedShape ball : list) {
            ball.update(getBounds());
        }
        repaint();
    }
});

And when they need to be painted...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g;
    for (AnimatedShape ball : list) {
        ball.paint(this, g2d);
    }
}

Because the BallPane doesn't care what actually type of entity it is, but only that it's a type of AnimatedShape...makes life easier...

Now, my implementation of the AnimatedBall already randomise the direction of each instance of the ball, but you can also randomise the starting position when the ball is added using something like...

public void add() {
    int radius = 15;
    // Randomised position
    int x = (int)(Math.random() * (getWidth() - (radius * 2))) + radius;
    int y = (int)(Math.random() * (getHeight() - (radius * 2))) + radius;
    Color color = new Color((int) (Math.random() * 256),
                    (int) (Math.random() * 256), (int) (Math.random() * 256));

    AnimatedBall ball = new AnimatedBall(x, y, radius, color);

    list.add(ball);
}

But how does this help you with adding a rectangle?

You now need to create an AnimatedRectangle that extends from AbstractAnimatedShape and implemented the required methods and add instances of this to the List of AnimatedShapes in the BallPane.

If you don't want the rectangle to be managed within the same list, you could create another list and manage it sepearatly (it create two additional methods, update(List<AnimatedShape>) and paint(List<AnimatedShape>, Graphics2D) passing in each individual list so as to reduce the duplicate code, but that's me)...

You can restrict the rectangles vertical movement by overriding the setDy method and ignoring any changes, for example

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class MultipleBall {

    public MultipleBall() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("MultipleBallApp");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new BallControl());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class BallControl extends JPanel {

        private BallPanel ballPanel = new BallPanel();
        private JButton Suspend = new JButton("Suspend");
        private JButton Resume = new JButton("Resume");
        private JButton Add = new JButton("+1");
        private JButton Subtract = new JButton("-1");
        private JScrollBar Delay = new JScrollBar();

        public BallControl() {
            // Group buttons in a panel
            JPanel panel = new JPanel();
            panel.add(Suspend);
            panel.add(Resume);
            panel.add(Add);
            panel.add(Subtract);

            // Add ball and buttons to the panel
            ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
            Delay.setOrientation(JScrollBar.HORIZONTAL);
            ballPanel.setDelay(Delay.getMaximum());
            setLayout(new BorderLayout());
            add(Delay, BorderLayout.NORTH);
            add(ballPanel, BorderLayout.CENTER);
            add(panel, BorderLayout.SOUTH);

            // Register listeners
            Suspend.addActionListener(new Listener());
            Resume.addActionListener(new Listener());
            Add.addActionListener(new Listener());
            Subtract.addActionListener(new Listener());
            Delay.addAdjustmentListener(new AdjustmentListener() {

                public void adjustmentValueChanged(AdjustmentEvent e) {
                    ballPanel.setDelay(Delay.getMaximum() - e.getValue());
                }
            });
        }

        class Listener implements ActionListener {

            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == Suspend) {
                    ballPanel.suspend();
                } else if (e.getSource() == Resume) {
                    ballPanel.resume();
                } else if (e.getSource() == Add) {
                    ballPanel.add();
                } else if (e.getSource() == Subtract) {
                    ballPanel.subtract();
                }
            }
        }
    }

    class BallPanel extends JPanel {

        private int delay = 30;
        private ArrayList<AnimatedShape> list = new ArrayList<AnimatedShape>();
        private AnimatedRectange rectangle;

        public BallPanel() {
            this.rectangle = new AnimatedRectange(-25, 200, 50, 25, Color.RED);

            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        // Create a timer with the initial delay
        protected Timer timer = new Timer(delay, new ActionListener() {
            /**
             * Handle the action event
             */
            @Override
            public void actionPerformed(ActionEvent e) {
                for (AnimatedShape ball : list) {
                    ball.update(getBounds());
                }
                rectangle.update(getBounds());
                repaint();
            }
        });

        public void add() {
            int radius = 15;
            // Randomised position
            int x = (int) (Math.random() * (getWidth() - (radius * 2))) + radius;
            int y = (int) (Math.random() * (getHeight() - (radius * 2))) + radius;
            Color color = new Color((int) (Math.random() * 256),
                            (int) (Math.random() * 256), (int) (Math.random() * 256));

            AnimatedBall ball = new AnimatedBall(x, y, radius, color);

            list.add(ball);
        }

        public void subtract() {
            if (list.size() > 0) {
                list.remove(list.size() - 1); // Remove the last ball
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g;
            for (AnimatedShape ball : list) {
                ball.paint(this, g2d);
            }
            rectangle.paint(this, g2d);
        }

        public void suspend() {
            timer.stop();
        }

        public void resume() {
            timer.start();
        }

        public void setDelay(int delay) {
            this.delay = delay;
            timer.setDelay(delay);
        }
    }

    public interface AnimatedShape {

        public void update(Rectangle bounds);

        public void paint(JComponent parent, Graphics2D g2d);
    }

    public abstract class AbstractAnimatedShape implements AnimatedShape {

        private Rectangle bounds;
        private int dx, dy;

        public AbstractAnimatedShape() {
        }

        public void setBounds(Rectangle bounds) {
            this.bounds = bounds;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public int getDx() {
            return dx;
        }

        public int getDy() {
            return dy;
        }

        public void setDx(int dx) {
            this.dx = dx;
        }

        public void setDy(int dy) {
            this.dy = dy;
        }

        @Override
        public void update(Rectangle parentBounds) {
            Rectangle bounds = getBounds();
            int dx = getDx();
            int dy = getDy();
            bounds.x += dx;
            bounds.y += dy;
            if (bounds.x  < parentBounds.x) {
                bounds.x = parentBounds.x;
                setDx(dx *= -1);
            } else if (bounds.x + bounds.width > parentBounds.x + parentBounds.width) {
                bounds.x = parentBounds.x + (parentBounds.width - bounds.width);
                setDx(dx *= -1);
            }
            if (bounds.y < parentBounds.y) {
                bounds.y = parentBounds.y;
                setDy(dy *= -1);
            } else if (bounds.y + bounds.height > parentBounds.y + parentBounds.height) {
                bounds.y = parentBounds.y + (parentBounds.height - bounds.height);
                setDy(dy *= -1);
            }
        }
    }

    public class AnimatedBall extends AbstractAnimatedShape {

        private Color color;

        public AnimatedBall(int x, int y, int radius, Color color) {
            setBounds(new Rectangle(x, y, radius * 2, radius * 2));
            this.color = color;
            setDx(Math.random() > 0.5 ? 2 : -2);
            setDy(Math.random() > 0.5 ? 2 : -2);
        }

        public Color getColor() {
            return color;
        }

        @Override
        public void paint(JComponent parent, Graphics2D g2d) {
            Rectangle bounds = getBounds();
            g2d.setColor(getColor());
            g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
        }
    }

    public class AnimatedRectange extends AbstractAnimatedShape {

        private Color color;

        public AnimatedRectange(int x, int y, int width, int height, Color color) {
            setBounds(new Rectangle(x, y, width, height));
            this.color = color;
            setDx(2);
        }

        // Don't want to adjust the vertical speed
        @Override
        public void setDy(int dy) {
        }

        @Override
        public void paint(JComponent parent, Graphics2D g2d) {
            Rectangle bounds = getBounds();
            g2d.setColor(color);
            g2d.fill(bounds);
        }

    }

    /**
     * Main method
     */
    public static void main(String[] args) {
        new MultipleBall();
    }
}

Amendment

  • You really should avoid adding JApplet to a JFrame, an applet has a prescribed life cycle and management process which you are ignoring. Better to focus on just using the BallControl panel as the core UI element and then add this to what ever top level container you want
  • You may find a JSlider more piratical then a JScrollBar, not to mention, it will look better on different platforms, most uses understand what a slider is used for...


回答2:

Add a static variable like ballCount and add 1 to it every time you make a ball. In the Ball class, change the definition of y to something likey = 20 + ballcount*(radius*2+distanceInBalls)

public class RandomTests extends JApplet {


    public RandomTests() {
        add(new BallControl());
    }

    static int ballCount = 0;

    class BallControl extends JPanel {
        private BallPanel ballPanel = new BallPanel();
        private JButton Suspend = new JButton("Suspend");
        private JButton Resume = new JButton("Resume");
        private JButton Add = new JButton("+1");
        private JButton Subtract = new JButton("-1");
        private JScrollBar Delay = new JScrollBar();

        public BallControl() {
            // Group buttons in a panel
            JPanel panel = new JPanel();
            panel.add(Suspend);
            panel.add(Resume);
            panel.add(Add);
            panel.add(Subtract);

            // Add ball and buttons to the panel
            ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
            Delay.setOrientation(JScrollBar.HORIZONTAL);
            ballPanel.setDelay(Delay.getMaximum());
            setLayout(new BorderLayout());
            add(Delay, BorderLayout.NORTH);
            add(ballPanel, BorderLayout.CENTER);
            add(panel, BorderLayout.SOUTH);

            // Register listeners
            Suspend.addActionListener(new Listener());
            Resume.addActionListener(new Listener());
            Add.addActionListener(new Listener());
            Subtract.addActionListener(new Listener());
            Delay.addAdjustmentListener(new AdjustmentListener() {

                public void adjustmentValueChanged(AdjustmentEvent e) {
                    ballPanel.setDelay(Delay.getMaximum() - e.getValue());
                }
            });
        }

        class Listener implements ActionListener {

            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == Suspend) ballPanel.suspend();
                else if (e.getSource() == Resume) ballPanel.resume();
                else if (e.getSource() == Add) ballPanel.add();
                else if (e.getSource() == Subtract) ballPanel.subtract();
            }
        }
    }

    class BallPanel extends JPanel {
        private int delay = 30;
        private ArrayList<Ball> list = new ArrayList<Ball>();

        // Create a timer with the initial delay
        protected Timer timer = new Timer(delay, new ActionListener() {
            /** Handle the action event */
            public void actionPerformed(ActionEvent e) {
                repaint();
            }
        });

        public BallPanel() {
            timer.start();
        }

        public void add() {
            list.add(new Ball());
            ballCount++;
        }

        public void subtract() {
            if (list.size() > 0) list.remove(list.size() - 1); // Remove the last ball
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawRect(185, 279, 50, 15);
            g.setColor(Color.RED);
            g.fillRect(185, 279, 50, 15);

            for (int i = 0; i < list.size(); i++) {
                Ball ball = (Ball) list.get(i); // Get a ball
                g.setColor(ball.color); // Set ball color

                // Check boundaries
                if (ball.x < 0 || ball.x > getWidth()) ball.dx = -ball.dx;

                if (ball.y < 0 || ball.y > getHeight()) ball.dy = -ball.dy;

                // Adjust ball position
                ball.x += ball.dx;
                // ball.y += ball.dy;
                g.fillOval(ball.x - ball.radius, ball.y - ball.radius, ball.radius * 2, ball.radius * 2);
            }
        }

        public void suspend() {
            timer.stop();
        }

        public void resume() {
            timer.start();
        }

        public void setDelay(int delay) {
            this.delay = delay;
            timer.setDelay(delay);
        }
    }

    class Ball {
        int radius = 15; // Ball radius
        int x = radius;
        int y = 20 + (radius * ballCount * 2 + 15); // Current ball position
        int dx = 2; // Increment on ball's x-coordinate
        int dy = 2; // Increment on ball's y-coordinate
        Color color = new Color((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256));
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JApplet applet = new RandomTests();
        frame.add(applet);
        frame.setTitle("MultipleBallApp");
        frame.setLocationRelativeTo(null); // Center the frame
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null); // Center the frame
        frame.setVisible(true);
    }
}