简体   繁体   English

使用计时器对Java线条​​图进行动画处理

[英]Animate a Java Line drawing using a timer

I am trying to draw two circle on a panel with a line joining them, all after a button is pressed. 我试图在面板上绘制两个圆圈,并用一条线将它们连接起来,所有这些都在按下按钮之后完成。 So far (apart from tweaking locations of the line) this is ok. 到目前为止(除了调整生产线的位置),这还可以。 However, I would like to animate it using a timer. 但是,我想使用计时器为其设置动画。 The first circle should appear, then gradually the line will be revealed, and finally the second circle. 应该出现第一个圆圈,然后逐渐显示该线,最后是第二个圆圈。

I have looked at many examples of timers, but I can't seem to get it to work for me. 我看过许多计时器示例,但似乎无法使它对我有用。 I must be misunderstanding something. 我一定是误会了

here is the ball class (for each circle): 这是球类(每个圆圈):

package twoBalls;

import java.awt.Color;
import java.awt.Point;

public class Ball {

    private int x;
    private int y;
    private int r;
    private Color color;
    private Point location;
    private Ball parent;

    public Ball(int x, int y, int r) {
        this.x = x;
        this.y = y;
        this.r = r;
        Point p = new Point(x, y);
        setLocation(p);
    }

    public void setParent(Ball b) {
        parent = b;
    }

    public Ball getParent() {
        return parent;
    }

    public void setx(int x) {
        this.x = x;
    }

    public void sety(int y) {
        this.y = y;
    }

    public int getx() {
        return x;
    }

    public int gety() {
        return y;
    }

    public int getr() {
        return r;
    }

    public void setPreferedSize() {

    }

    public void setLocation(Point p) {
        setx(p.x);
        sety(p.y);
        location = p;
    }

    public Point getLocation() {
        return location;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return color;
    }

}

then the class that will store balls in an arrayList. 然后将球存储在arrayList中的类。 And I think that this is where the actual drawing should take place, along with the timer. 我认为这是应该与计时器一起进行实际绘制的地方。 I am trying to set the start and end point of the line to be the same, and increment the end point until it is where it should be, using the timer. 我正在尝试使用计时器将线的起点和终点设置为相同,并增加终点,直到终点。 I'm probably way of track, but that was the intention! 我可能是跟踪的人,但这就是意图!

I have change this class, the if statements in the while loop can now be entered, as I am now comparing different point. 我已经更改了此类,现在可以在while循环中输入if语句,因为我现在正在比较不同的点。 But the line doesn't get drawn at all still. 但是,这条线还没有画出来。

package twoBalls;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.Timer;

public class BallsArray extends JPanel implements ActionListener {

    private ArrayList<Ball> balls;
    private Timer timer;
    private final int DELAY = 25;
    private int xDest;
    private int yDest;
    private Point dest;
    private Point starts;
    private int xStart;
    private int yStart;

    public BallsArray() {
        balls = new ArrayList<Ball>();
        timer = new Timer(DELAY, this);
        yDest = 0;
        xDest = 0;
        dest = new Point(xDest, yDest);
        starts = new Point(xStart, yStart);

    }

    public void setDestXY(int x, int y) {
        xDest = x;
        yDest = y;
        dest = new Point(xDest, yDest);
        setDest(dest);
    }

    public void setDest(Point p) {
        dest = p;

    }

    public Point getDest() {
        return dest;
    }

    public void setStartsXY(int x, int y) {
        xStart = x;
        yStart = y;
        starts = new Point(xStart, yStart);
        setStarts(starts);
    }

    public void setStarts(Point p) {
        starts = p;
    }

    public Point getStarts() {
        return starts;
    }

    public void addBall(Ball b) {
        balls.add(b);
    }

    public void addBall(int x, int y, int r) {
        balls.add(new Ball(x, y, r));

    }

    public void paintComponent(Graphics g) {

        Graphics2D g2 = (Graphics2D) g;
        for (int i = 0; i < balls.size(); i++) {
            if (i == 0) {
                paintBall(balls.get(0), g2);
            }
            if (i != 0) {
                int j = i - 1;
                Ball bp = balls.get(j);
                Ball bc = balls.get(i);
                bc.setParent(bp);
                paintLine(bc, g2);
                paintBall(bc, g2);
            }

        }
    }

    public void paintBall(Ball b, Graphics2D g2d) {
        Ellipse2D circ = new Ellipse2D.Float(b.getx(), b.gety(), b.getr(),
                b.getr());
        g2d.draw(circ);
    }

    public void paintLine(Ball b, Graphics2D g2d) {
        timer.start();
        if (b != null && b.getLocation() != null) {
            Ball parent = b.getParent();
            if (parent != null) {
                g2d.setColor(Color.GRAY);
                if (parent.getLocation() != null && b.getLocation() != null) {
                    setDest(parent.getLocation());
                    setStarts(parent.getLocation());
                    g2d.draw(new Line2D.Float(starts, dest));
                }
            }
        }

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // Not sure what I need to do here
        // increment second location somehow
        // Point s = getStarts();
        Point p = getDest();
        Point t = this.getLocation();
        while (p != t) {

            if (p.x != t.x && p.y != t.y) {
                System.out.println("hello");
                int x = dest.x;
                int y = dest.y;
                x++;
                y++;
                setDestXY(x, y);
                p = getDest();
                repaint();
            } else if (p.x == t.x && p.y != t.y) {
                System.out.println("part 2");
                int y = dest.y;
                y++;
                setDestXY(dest.x, y);
                p = getDest();
                repaint();
            } else if (p.x != t.x && p.y == t.y) {
                System.out.println("part 3");
                int x = dest.x;
                x++;
                setDestXY(x, dest.y);
                p = getDest();
                repaint();
            }
            repaint();
        }
    }
}

I have had a lot of help online getting this far, I worry I am just beyond my depth now!. 我在网上已经获得了很多帮助,我担心我现在超出了我的深度! I am unsure about the EventQueue/run part below. 我不确定下面的EventQueue / run部分。 Here is the class to set it all up: 这是设置所有内容的类:

package twoBalls;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;

public class Display implements ActionListener {

    private JFrame frame;
    private JButton button;
    private BallsArray b;

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Display ex = new Display();

            }
        });

    }

    public Display() {
        b = new BallsArray();
        frame = new JFrame();
        frame.setSize(800, 500);
        frame.setTitle("Show balls");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        button = new JButton("New Ball");
        frame.add(button, BorderLayout.SOUTH);
        frame.setVisible(true);
        button.addActionListener(this);

    }

    @Override
    public void actionPerformed(ActionEvent e) {

        Ball ball1 = new Ball(100, 100, 50);
        b.addBall(ball1);
        b.addBall(200, 200, 50);
        frame.add(b, BorderLayout.CENTER);
        frame.revalidate();
        frame.repaint();

    }
}

At the moment it draws the two circles, but not the line at all. 目前,它绘制了两个圆圈,但根本没有画线。

When you make an animation, it helps to use the model / view / controller pattern . 制作动画时,有助于使用模型/视图/控制器模式

Here's the GUI I created from your code. 这是我根据您的代码创建的GUI。

显示球动画

I simplified your Ball class. 我简化了您的Ball课程。 This is all you need to define a ball. 这就是定义一个球所需要的。

package twoBalls;

import java.awt.Color;
import java.awt.Point;

public class Ball {

    private final int radius;

    private final Color color;

    private final Point center;

    public Ball(int x, int y, int radius, Color color) {
        this(new Point(x, y), radius, color);
    }

    public Ball(Point center, int radius, Color color) {
        this.center = center;
        this.radius = radius;
        this.color = color;
    }

    public int getRadius() {
        return radius;
    }

    public Color getColor() {
        return color;
    }

    public Point getCenter() {
        return center;
    }

}

I created the GUIModel class to hold all of the information your GUI needs. 我创建了GUIModel类来保存您的GUI所需的所有信息。 This separates the model from the view. 这会将模型与视图分开。

package twoBalls;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

public class GUIModel {

    private double direction;
    private double distance;

    private List<Ball> balls;

    private Point lineStartPoint;
    private Point lineEndPoint;

    public GUIModel() {
        this.balls = new ArrayList<>();
    }

    public void addBall(Ball ball) {
        this.balls.add(ball);
    }

    public List<Ball> getBalls() {
        return balls;
    }

    public void calculatePoints() {
        this.lineStartPoint = balls.get(0).getCenter();
        this.lineEndPoint = balls.get(1).getCenter();

        this.distance = Point.distance(lineStartPoint.x, lineStartPoint.y,
                lineEndPoint.x, lineEndPoint.y);
        this.direction = Math.atan2(lineEndPoint.y - lineStartPoint.y,
                lineEndPoint.x - lineStartPoint.x);
    }

    public Point getCurrentPoint(int pos, int total) {
        double increment = distance / total;
        double length = increment * pos;

        double x = lineStartPoint.x + Math.cos(direction) * length;
        double y = lineStartPoint.y - Math.sin(direction) * length;

        x = Math.round(x);
        y = Math.round(y);

        return new Point((int) x, (int) y);
    }

    public Point getLineStartPoint() {
        return lineStartPoint;
    }

}

This class holds the two Ball instances, and calculates the length and direction of the line, divided into total increments. 此类包含两个Ball实例,并计算线的长度和方向,分为总增量。

Now that we've defined the model classes, let's look at the view classes. 现在我们已经定义了模型类,让我们来看一下视图类。 The first is your Display class. 第一个是您的Display类。

package twoBalls;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Display implements Runnable {

    private GUIModel guiModel;

    private JFrame frame;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Display());
    }

    public Display() {
        this.guiModel = new GUIModel();
        Ball ball1 = new Ball(150, 200, 50, Color.BLUE);
        Ball ball2 = new Ball(450, 200, 50, Color.GREEN);
        guiModel.addBall(ball1);
        guiModel.addBall(ball2);
        guiModel.calculatePoints();
    }

    @Override
    public void run() {
        frame = new JFrame();
        frame.setTitle("Show Balls Animation");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        DrawingPanel drawingPanel = new DrawingPanel(guiModel);
        panel.add(drawingPanel, BorderLayout.CENTER);

        panel.add(createButtonPanel(drawingPanel), BorderLayout.SOUTH);

        frame.add(panel);

        frame.pack();
        frame.setVisible(true);
    }

    private JPanel createButtonPanel(DrawingPanel drawingPanel) {
        JPanel panel = new JPanel();

        JButton startButton = new JButton("Start Animation");
        startButton.addActionListener(new StartAnimation(drawingPanel));
        panel.add(startButton);

        return panel;
    }

    public class StartAnimation implements ActionListener {

        private DrawingPanel drawingPanel;

        public StartAnimation(DrawingPanel drawingPanel) {
            this.drawingPanel = drawingPanel;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            LineRunnable runnable = new LineRunnable(drawingPanel);
            new Thread(runnable).start();
        }

    }

}

The constructor of the Display class sets up the GUI model. Display类的构造函数设置GUI模型。

The run method of the Display class constructs the GUI, and starts the animation. Display类的run方法构造GUI,然后启动动画。

See how I've separated the model and view. 查看我如何分离模型和视图。

The StartAnimation class is your controller. StartAnimation类是您的控制器。 It starts the animation when you left click on the JButton. 当您左键单击JButton时,它将启动动画。 I'll discuss the LineRunnable class later. 稍后我将讨论LineRunnable类。

Next, let's take a look at the DrawingPanel class. 接下来,让我们看一下DrawingPanel类。

package twoBalls;

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 javax.swing.JPanel;

public class DrawingPanel extends JPanel {

    private static final long serialVersionUID = -3709678584255542338L;

    private boolean drawLine;

    private int pos;
    private int total;

    private GUIModel guiModel;

    public DrawingPanel(GUIModel guiModel) {
        this.guiModel = guiModel;
        this.drawLine = false;
        this.setPreferredSize(new Dimension(600, 400));
    }

    public boolean isDrawLine() {
        return drawLine;
    }

    public void setDrawLine(boolean drawLine) {
        this.drawLine = drawLine;
    }

    public void setPos(int pos) {
        this.pos = pos;
        repaint();
    }

    public void setTotal(int total) {
        this.total = total;
    }

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

        Graphics2D g2d = (Graphics2D) g;

        for (Ball ball : guiModel.getBalls()) {
            g2d.setColor(ball.getColor());
            Point center = ball.getCenter();
            int radius = ball.getRadius();
            g2d.fillOval(center.x - radius, center.y - radius, radius + radius,
                    radius + radius);
        }

        if (isDrawLine()) {
            g2d.setColor(Color.BLACK);
            g2d.setStroke(new BasicStroke(5.0F));
            Point a = guiModel.getLineStartPoint();
            Point b = guiModel.getCurrentPoint(pos, total);
            g2d.drawLine(a.x, a.y, b.x, b.y);
        }
    }

}

The only thing this view class does is draw the balls and the line. 该视图类唯一要做的就是绘制球和线条。 The responsibility for calculating the length of the line belongs in the model. 模型中负责计算线的长度。

I set the preferred size here, and use the pack method in the Display class to get the size of the JFrame. 我在这里设置首选大小,并在Display类中使用pack方法获取JFrame的大小。 You usually want to know the dimensions of the drawing area, rather than the entire window. 通常,您想知道绘图区域的尺寸,而不是整个窗口的尺寸。

Finally, let's look at the LineRunnable class. 最后,让我们看一下LineRunnable类。 This is the class that controls the animation. 这是控制动画的类。

package twoBalls;

import java.awt.EventQueue;

public class LineRunnable implements Runnable {

    private int total;

    private DrawingPanel drawingPanel;

    public LineRunnable(DrawingPanel drawingPanel) {
        this.drawingPanel = drawingPanel;
        this.total = 240;
    }

    @Override
    public void run() {
        setDrawLine();
        for (int pos = 0; pos <= total; pos++) {
            setPos(pos);
            sleep(50L);
        }
    }

    private void setDrawLine() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                drawingPanel.setDrawLine(true);
                drawingPanel.setTotal(total);
            }
        });
    }

    private void setPos(final int pos) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                drawingPanel.setPos(pos);
            }
        });
    }

    private void sleep(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {

        }
    }

}

In the run method, we divide the line into 240 segments, and draw a segment every 50 milliseconds. 在run方法中,我们将线划分为240个段,并每50毫秒绘制一个段。 It takes the GUI 12 seconds to draw the line. GUI需要12秒钟来绘制线条。 You can play with these numbers if you wish. 如果愿意,您可以玩这些号码。

The for loop is a classic animation loop. for循环是经典的动画循环。 First you update the model, which I'm doing through the drawing panel. 首先,您更新模型,这是我在绘图面板中所做的。 Then you sleep. 那你睡吧

This animation loop is running on a different thread from the GUI thread. 此动画循环在与GUI线程不同的线程上运行。 This keeps the GUI responsive. 这样可以使GUI保持响应状态。 Since the loop is running on a different thread, we have to use the invokeLater method to draw on the Event Dispatch thread. 由于循环在其他线程上运行,因此我们必须使用invokeLater方法在事件分发线程上进行绘制。

I hope this was helpful to you. 希望对您有帮助。 Divide and conquer. 分而治之。 Don't let a class do more than one thing. 不要让一个班级做太多事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM