简体   繁体   English

如何使用 java awt 制作动画

[英]How to animate with java awt

I'm trying to make an animation of a red oval that will move to the right of the screen.我正在尝试制作一个红色椭圆形的 animation ,它将移动到屏幕的右侧。 But it just draws the oval.但它只是画了椭圆形。 I don't know what I'm doing wrong and I literally can't find anything about how to do this.我不知道我做错了什么,而且我真的找不到任何关于如何做到这一点的信息。 Any help would be awesome, thanks.任何帮助都会很棒,谢谢。

import java.awt.*;  

public class mainClass  
{    
    public mainClass()    
    {    

        Frame f = new Frame("Canvas Example");   
        f.add(new MyCanvas());    

        f.setLayout(null);    
        f.setSize(400, 400);   
        f.setVisible(true);    
    }    

    public static void main(String args[])    
    {    
        new mainClass();
    }    
}    


class MyCanvas extends Canvas    
{    
    int x = 75;
    public MyCanvas() {    
        setBackground (Color.BLACK);    
        setSize(400, 400);    
    }    

    public void paint(Graphics g)    
    {     
        g.setColor(Color.red);    
        g.fillOval(x, 75, 150, 75);
    }
    public void update(Graphics g)    
    {     
        x++;
    }  
}     

The reason this doesn't animate is that nothing triggers the component to update and repaint itself.这没有动画的原因是没有任何东西触发组件更新和重绘自身。 There are a few things that need to be considered:有几点需要考虑:

  1. Something needs to call the update method.有些东西需要调用update方法。 Ordinarily, this is triggered by a call to repaint() on the component, but nothing in this code calls that method.通常,这是通过调用组件上的repaint()来触发的,但这段代码中没有任何内容调用该方法。

  2. It's important for an overridden update method to call super.update(g) to ensure the default behavior is invoked (clearing the canvas and painting it again).调用super.update(g)以确保调用默认行为(清除 canvas 并再次绘制它)对于重写的update方法很重要。

  3. Animation has a time component: the oval should move over some period of time. Animation 有一个时间分量:椭圆应该在一段时间内移动。 This needs to be incorporated into the logic.这需要纳入逻辑。 AWT has no built-in mechanism for timed behavior. AWT 没有内置的定时行为机制。

    If you're able to use classes from Swing, the javax.swing.Timer class is very useful for animation.如果您能够使用 Swing 中的类,则javax.swing.Timer class 对 Z62F1C25ED192099 非常有用。 It executes your callback on the AWT thread, and therefore means that you don't have to take special measures to ensure thread safety.它在 AWT 线程上执行您的回调,因此意味着您不必采取特殊措施来确保线程安全。

    If you can't use Swing, it can use java.util.Timer or a custom thread, but will need to manage thread synchronization directly.如果不能使用 Swing,可以使用java.util.Timer或自定义线程,但需要直接管理线程同步。

  4. You'll probably also want the animation to stop once the oval reaches the edge of the canvas.一旦椭圆到达 canvas 的边缘,您可能还希望 animation 停止。

Here's an example using javax.swing.Timer (assuming Java 8 or later).这是一个使用javax.swing.Timer的示例(假设 Java 8 或更高版本)。 Note that all of the animation logic is in the ActionListener attached to the Timer , so the overridden update method has been removed:请注意,所有 animation 逻辑都在附加到TimerActionListener中,因此已删除覆盖的update方法:

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

public class MainClass {
    public static final int CANVAS_SIZE = 400;

    public MainClass() {

        Frame f = new Frame("Canvas Example");
        f.add(new MyCanvas(CANVAS_SIZE));

        f.setLayout(null);
        f.setSize(CANVAS_SIZE, CANVAS_SIZE);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        new MainClass();
    }
}


class MyCanvas extends Canvas {
    public static final int INITIAL_POSITION = 75;
    public static final int HEIGHT = 75;
    public static final int WIDTH = 150;

    private static final int TIMER_DELAY_MILLIS = 1000 / 30; // 30 FPS

    private int x = INITIAL_POSITION;
    private final Timer timer;

    public MyCanvas(int canvasSize) {
        setBackground(Color.BLACK);
        setSize(canvasSize, canvasSize);

        timer = new Timer(TIMER_DELAY_MILLIS, (event) -> {
            // ensure the oval stays on the canvas
            if (x + WIDTH < getWidth())  {
                x++;
                repaint();
            } else {
                stopAnimation();
            }
        });
        timer.start();
    }

    public void paint(Graphics g) {
        g.setColor(Color.red);
        g.fillOval(x, INITIAL_POSITION, WIDTH, HEIGHT);
    }

    private void stopAnimation() {
        timer.stop();
    }
}

This code has a few additional incidental changes.此代码有一些额外的附带更改。

  • Updated the name of mainClass to MainClass (leading capital "M") to comply with standard Java naming conventions.MainClass mainClass前导大写“M”)以符合标准 Java 命名约定。
  • Changed String args[] to String[] args for the same reason.出于同样的原因,将String args[]更改为String[] args
  • Extracted numeric constants to named static final fields.将数字常量提取到命名为static final字段。
  • Made the canvas size a constructor parameter, controlled by the caller.使 canvas 大小成为构造函数参数,由调用者控制。
  • Made x private.x私有。
  • Minor formatting changes to ensure a consistent style.较小的格式更改以确保一致的样式。

One option that doesn't use javax.swing.Timer (with unchanged code omitted):一种不使用javax.swing.Timer的选项(省略未更改的代码):

private final AtomicInteger x = new AtomicInteger(INITIAL_POSITION);

public MyCanvas(int canvasSize) {
    setBackground(Color.BLACK);
    setSize(canvasSize, canvasSize);

    new Thread(() -> {
        try {
            // ensure the oval stays on the canvas
            while (x.incrementAndGet() + WIDTH < getWidth())  {
                Thread.sleep(TIMER_DELAY_MILLIS);
                repaint();
            }
        } catch (InterruptedException e) {
            // Just let the thread exit
            Thread.currentThread().interrupt();
        }
    }).start();
}

Theory理论

Animation is hard, I mean, really good animation is hard. Animation 很难,我的意思是,非常好的 animation 很难。 There is a not of theory which goes into creating good animation, things like easement, anticipation, squish... I could go on, but I'm boring myself.没有理论可以创建好的 animation,比如地役权、预期、挤压……我可以打开 go,但我自己很无聊。

The point is, simply incrementing a value (AKA linear progression) is a poor approach to animation.关键是,简单地增加一个值(AKA 线性进展)对于 animation 来说是一种糟糕的方法。 If the system is slow, busy or for some other reason isn't keeping up, the animation will suffer because it (stuttering, pauses, etc).如果系统很慢、很忙或由于其他原因无法跟上,animation 将因此受到影响(卡顿、暂停等)。

A "better" solution is to use a time based progression. “更好”的解决方案是使用基于时间的进程。 That is, you specify the amount of time it will take to move from the current state to it's new state and the continuously loop and update the state until you run out of time.也就是说,您指定从当前 state 移动到新的 state 和持续循环并更新 state 所需的时间,直到您用完为止。

The "main loop" “主循环”

If you do any research into game development, they always talk about this thing called the "main loop".如果你对游戏开发进行任何研究,他们总是谈论这个叫做“主循环”的东西。

The "main loop" is responsible for updating the game state and scheduling paint passes. “主循环”负责更新游戏 state 并安排绘制通道。

In terms to your question, you need a "main loop" which can update the position of the oval until it reaches it's target position.就您的问题而言,您需要一个“主循环”,它可以更新椭圆的 position 直到它达到目标 position。

Because most GUI frameworks are already running within their own thread context, you need to setup your "main loop" in another thread因为大多数 GUI 框架已经在它们自己的线程上下文中运行,你需要在另一个线程中设置你的“主循环”

AWT AWT

Some theory一些理论

AWT is the original GUI framework, so it's "old". AWT 是原始的 GUI 框架,所以它是“旧的”。 While Swing does sit on top of it, you'll find more people have experience with Swing then they do AWT.虽然 Swing 确实位于它之上,但您会发现更多的人有使用 Swing 的经验,然后他们使用 AWT。

One of the important things to keep in mind is, Canvas is not double buffered, so, if you're updating the component fast enough, it will flash.要记住的重要事项之一是, Canvas不是双缓冲的,因此,如果您更新组件的速度足够快,它将是 flash。

To overcome this, you need to implement some kind of double buffering workflow.为了克服这个问题,您需要实现某种双缓冲工作流程。

Runnable example可运行示例

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;

public class Test {
    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Frame frame = new Frame();
                frame.add(new TestCanvas());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Ticker implements Runnable {

        public interface Callbck {
            public void didTick(Ticker ticker);
        }

        private boolean isRunning = false;
        private Thread thread;

        private Callbck callback;

        public void setCallback(Callbck tick) {
            this.callback = tick;
        }

        public void start() {
            if (isRunning) {
                return;
            }
            isRunning = true;
            thread = new Thread(this);
            thread.setDaemon(false);
            thread.start();
        }

        public void stop() {
            if (!isRunning) {
                return;
            }
            isRunning = false;
            thread.interrupt();
            thread = null;
        }

        @Override
        public void run() {
            while (isRunning) {
                try {
                    Thread.sleep(5);
                    if (callback != null) {
                        callback.didTick(this);
                    }
                } catch (InterruptedException ex) {
                    isRunning = false;
                }
            }
        }

    }

    public class TestCanvas extends Canvas {

        private BufferedImage buffer;

        int posX;

        private Ticker ticker;
        private Instant startedAt;
        private Duration duration = Duration.ofSeconds(5);

        public TestCanvas() {
            ticker = new Ticker();
            ticker.setCallback(new Ticker.Callbck() {
                @Override
                public void didTick(Ticker ticker) {
                    if (startedAt == null) {
                        startedAt = Instant.now();
                    }
                    Duration runtime = Duration.between(startedAt, Instant.now());
                    double progress = runtime.toMillis() / (double)duration.toMillis();

                    if (progress >= 1.0) {
                        stopAnimation();
                    }

                    posX = (int)(getWidth() * progress);
                    repaint();
                }
            });
        }

        protected void startAnimtion() {
            ticker.start();
        }

        protected void stopAnimation() {
            ticker.stop();
        }

        @Override
        public void setBounds(int x, int y, int width, int height) {
            buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            super.setBounds(x, y, width, height);
        }

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

        @Override
        public void addNotify() {
            super.addNotify();
            startAnimtion();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            buffer = null;
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            if (buffer == null) {
                return;
            }
            Graphics2D g2d = buffer.createGraphics();
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.setColor(Color.RED);
            int midY = getHeight() / 2;
            g2d.fillOval(posX, midY - 5, 10, 10);
            g2d.dispose();

            g.drawImage(buffer, 0, 0, this);
        }

    }
}

What is Canvas go for...? Canvas go 是什么...?

In most cases, you should avoid using Canvas , for many of the reasons mentioned above, but one of the reasons you might consider using Canvas is if you want to take full control over the painting process.在大多数情况下,由于上述许多原因,您应该避免使用Canvas ,但您可能考虑使用Canvas的原因之一是如果您想完全控制绘画过程。 You might do this if you want to create a complex game which and you want to get the best possible performance out of the rendering pipeline.如果您想创建一个复杂的游戏,并且希望从渲染管道中获得最佳性能,您可能会这样做。

See BufferStrategy and BufferCapabilities and the JavaDocs for more detail有关更多详细信息,请参阅BufferStrategy 和 BufferCapabilities以及JavaDocs

A Swing based implementation基于 Swing 的实现

Hopefully I've convinced you that a Swing implementation might be a better solution, which in that case you should make use of a Swing Timer instead of Thread , as Swing is not thread safe希望我已经说服您 Swing 实现可能是一个更好的解决方案,在这种情况下您应该使用 Swing Timer而不是Thread ,因为 Z198B44A40AA77F2260886010C7 不是线程安全的

See Concurrency in Swing andHow to Use Swing Timers for more details有关详细信息,请参阅Swing 中的并发以及如何使用 Swing 计时器

Runnable example可运行示例

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Ticker {

        public interface Callbck {
            public void didTick(Ticker ticker);
        }

        private Timer timer;

        private Callbck callback;

        public void setCallback(Callbck tick) {
            this.callback = tick;
        }

        public void start() {
            if (timer != null) {
                return;
            }
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (callback == null) {
                        return;
                    }
                    callback.didTick(Ticker.this);
                }
            });
            timer.start();
        }

        public void stop() {
            if (timer == null) {
                return;
            }
            timer.stop();
            timer = null;
        }

    }

    public class TestPane extends JPanel {

        int posX;

        private Ticker ticker;
        private Instant startedAt;
        private Duration duration = Duration.ofSeconds(5);

        public TestPane() {
            ticker = new Ticker();
            ticker.setCallback(new Ticker.Callbck() {
                @Override
                public void didTick(Ticker ticker) {
                    if (startedAt == null) {
                        startedAt = Instant.now();
                    }
                    Duration runtime = Duration.between(startedAt, Instant.now());
                    double progress = runtime.toMillis() / (double) duration.toMillis();

                    if (progress >= 1.0) {
                        stopAnimation();
                    }

                    posX = (int) (getWidth() * progress);
                    repaint();
                }
            });
        }

        protected void startAnimtion() {
            ticker.start();
        }

        protected void stopAnimation() {
            ticker.stop();
        }

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

        @Override
        public void addNotify() {
            super.addNotify();
            startAnimtion();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            stopAnimation();
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            int midY = getHeight() / 2;
            g2d.fillOval(posX, midY - 5, 10, 10);
            g2d.dispose();
        }

    }
}

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

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