如何在JPanel中为Rectangle设置动画?

问题描述:

我想为我的项目学习一些关于JAVA的技巧。

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

我想动画我的Rectangle leftoright和righttoleft但是我不能为球应用相同的功能动画。

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

此外,我怎样才能在不同的x方向上以y坐标的边界开始我的球?

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.

我的代码:

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



不能对球动画应用相同的功能

can't apply the same functions for ball animation

这可能是你的第一个错误。事实上,这正是你应该尝试做的事情。这个想法是,你应该尝试通过绘制/动画的内容来设计一种手段是抽象的,所以你想要绘制的形状无关紧要,你可以将它应用到sam基本动画过程...

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

这表示动画实体可以更新(移动)和绘制。按照惯例(因为我很懒),我喜欢创建一个实现最常见方面的 abstract 实现...

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

因为 BallPane 并不关心实际的实体类型,只是它是一种 AnimatedShape ......让生活更轻松......

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

现在,我的 AnimatedBall 的实现已经随机化了每个球的实例方向,但你也可以随机启动使用类似的东西添加球时的位置...

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?

您现在需要创建一个 AnimatedRectangle ,它扩展自 AbstractAnimatedShape 并实现了必需的方法并将此实例添加到 BallPane中的 List AnimatedShape

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.

如果您不希望在同一列表中管理矩形,您可以创建另一个列表并单独管理它(它创建两个其他方法,更新(List< AnimatedShape>) paint(List< AnimatedShape>,Graphics2D)传递每个人列表以减少重复的代码,但那是我... ...

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

您可以通过覆盖 setDy来限制矩形的垂直移动方法并忽略任何更改,例如

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

修订


  • 你真的应该避免将 JApplet 添加到 JFrame ,applet具有您忽略的规定生命周期和管理流程。最好只关注使用 BallControl 面板作为核心UI元素,然后将其添加到您想要的*容器中

  • 您可以找到 JSlider 更多piratical然后 JScrollBar ,更不用说,它会在不同的平台上看起来更好,大多数使用了解滑块的用途...

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