【问题标题】:How to make line animation smoother?如何让线条动画更流畅?
【发布时间】:2012-11-12 11:52:11
【问题描述】:

我正在用 Java 制作一个简单的动画,并试图让它尽可能流畅。

我只使用每个 Shape 对象的 *.Double 内部类,并在 Graphics2D 对象中设置抗锯齿。只要我只使用 fill() 方法,这一切都可以工作,但如果我还使用 draw() 方法在同一个 Shape 周围绘制线条,这些线条的动画就会断断续续 - 逐个像素。

我在画布上的每个矩形都有这种方法来绘制自己。它每 20 毫秒移动一次,并使用 Timer 和 TimerListener 重新绘制整个画布。

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

public class AnimationTest {
    public static void main(String[] args) {
        JFrame frm = new JFrame("Test");
        frm.setBounds(200, 200, 400, 400);
        frm.setResizable(false);
        frm.setLocationRelativeTo(null);

        AnimationCanvas a = new AnimationCanvas();
        frm.add(a);

        frm.setVisible(true);

        a.startAnimation();
    }
}

class AnimationCanvas extends JPanel {

    SimpleSquare[] squares = new SimpleSquare[2];

    AnimationCanvas() {

        squares[0] = new SimpleSquare(50, 80, true);
        squares[1] = new SimpleSquare(160, 80, false);

    }

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

        for (SimpleSquare c : squares) {
            c.paintSquare(g);
        }
    }

    Timer t;
    public void startAnimation() {
        t = new Timer(30, new Animator());
        t.start();
    }

    private class Animator implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            squares[0].y += 0.10;
            squares[1].y += 0.10;
            repaint();
        }
    }
}


class SimpleSquare {
    double x;
    double y;
    Color color = Color.black;
    boolean fill;

    SimpleSquare(double x, double y, boolean fill) {
        this.x = x;
        this.y = y;
        this.fill = fill;
    }

    void paintSquare(Graphics g) {
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        Shape s = new Rectangle.Double(x, y, 100, 100);

        g.setColor(color);
        ((Graphics2D) g).setStroke(new BasicStroke(2));

        if (fill) {
            ((Graphics2D) g).fill(s);
        } else {
            ((Graphics2D) g).draw(s);
        }
    }
}

有没有办法解决这个问题?我环顾了好一会儿。

【问题讨论】:

  • 你开启双缓冲了吗?

标签: java animation timer graphics2d smooth


【解决方案1】:

我进行了这个小测试,没有发现任何重大问题,即使 1000 个矩形都以随机速度在随机方向上移动,我也基本上能够保持 50fps。

public class SimpleAnimationEngine {

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

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

                AnimationPane pane = new AnimationPane();

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(pane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                pane.init();
                pane.start();
            }

        });
    }

    public static class AnimationPane extends JPanel implements AnimationCanvas {

        private AnimationModel model;

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

        public AnimationModel getModel() {
            return model;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Animatable animatable : getModel().getAnimatables()) {
                animatable.paint(g2d);
            }
            g2d.dispose();
        }

        @Override
        public synchronized void updateState() {

            Runnable update = new Runnable() {
                @Override
                public void run() {
                    AnimationModel model = getModel();
                    for (Animatable animatable : model.getAnimatables()) {
                        animatable.copy();
                    }
                    repaint();
                }

            };

            if (EventQueue.isDispatchThread()) {
                update.run();
            } else {
                try {
                    EventQueue.invokeAndWait(update);
                } catch (InterruptedException | InvocationTargetException ex) {
                    ex.printStackTrace();
                }
            }
        }

        public void init() {
            model = new DefaultAnimationModel();
            for (int index = 0; index < 1000; index++) {
                model.add(new AnimatableRectangle(this));
            }
            updateState();
        }

        public void start() {
            AnimationEngine engine = new AnimationEngine(this, getModel());
            engine.start();
        }

    }

    public static interface Animatable {

        public void copy();

        public void update(AnimationCanvas canvas, float progress);

        public void paint(Graphics2D g2d);

    }

    public static class AnimationEngine extends Thread {

        private AnimationModel model;
        private AnimationCanvas canvas;

        public AnimationEngine(AnimationCanvas canvas, AnimationModel model) {
            setDaemon(true);
            setName("AnimationThread");
            this.model = model;
            this.canvas = canvas;
        }

        public AnimationCanvas getCanvas() {
            return canvas;
        }

        public AnimationModel getModel() {
            return model;
        }

        @Override
        public void run() {
            float progress = 0;
            long cylceStartTime = System.currentTimeMillis();
            long cylceEndTime = cylceStartTime + 1000;
            int updateCount = 0;
            while (true) {
                long frameStartTime = System.currentTimeMillis();
                getModel().update(getCanvas(), progress);
                getCanvas().updateState();
                long frameEndTime = System.currentTimeMillis();
                long delay = 20 - (frameEndTime - frameStartTime);
                if (delay > 0) {
                    try {
                        sleep(delay);
                    } catch (InterruptedException ex) {
                    }
                }
                long now = System.currentTimeMillis();
                long runtime = now - cylceStartTime;
                progress = (float)runtime / (float)(1000);
                updateCount++;
                if (progress > 1.0) {
                    progress = 0f;
                    cylceStartTime = System.currentTimeMillis();
                    cylceEndTime = cylceStartTime + 1000;
                    System.out.println(updateCount + " updates in this cycle");
                    updateCount = 0;
                }
            }
        }

    }

    public interface AnimationCanvas {

        public void updateState();

        public Rectangle getBounds();

    }

    public static interface AnimationModel {

        public void update(AnimationCanvas canvas, float progress);

        public void add(Animatable animatable);

        public void remove(Animatable animatable);

        public Animatable[] getAnimatables();

    }

    public static class AnimatableRectangle implements Animatable {

        private Rectangle bounds;
        private int dx, dy;
        private Rectangle copyBounds;
        private Color foreground;
        private Color backColor;

        public AnimatableRectangle(AnimationCanvas canvas) {
            bounds = new Rectangle(10, 10);
            Rectangle canvasBounds = canvas.getBounds();
            bounds.x = canvasBounds.x + ((canvasBounds.width - bounds.width) / 2);
            bounds.y = canvasBounds.y + ((canvasBounds.height - bounds.height) / 2);

            dx = (getRandomNumber(10) + 1) - 5;
            dy = (getRandomNumber(10) + 1) - 5;

            dx = dx == 0 ? 1 : dx;
            dy = dy == 0 ? 1 : dy;

            foreground = getRandomColor();
            backColor = getRandomColor();

        }

        protected int getRandomNumber(int range) {
            return (int) Math.round(Math.random() * range);
        }

        protected Color getRandomColor() {
            return new Color(getRandomNumber(255), getRandomNumber(255), getRandomNumber(255));
        }

        @Override
        public void copy() {
            copyBounds = new Rectangle(bounds);
        }

        @Override
        public void update(AnimationCanvas canvas, float progress) {
            bounds.x += dx;
            bounds.y += dy;
            Rectangle canvasBounds = canvas.getBounds();
            if (bounds.x + bounds.width > canvasBounds.x + canvasBounds.width) {
                bounds.x = canvasBounds.x + canvasBounds.width - bounds.width;
                dx *= -1;
            }
            if (bounds.y + bounds.height > canvasBounds.y + canvasBounds.height) {
                bounds.y = canvasBounds.y + canvasBounds.height - bounds.height;
                dy *= -1;
            }
            if (bounds.x < canvasBounds.x) {
                bounds.x = canvasBounds.x;
                dx *= -1;
            }
            if (bounds.y < canvasBounds.y) {
                bounds.y = canvasBounds.y;
                dy *= -1;
            }
        }

        @Override
        public void paint(Graphics2D g2d) {
            g2d.setColor(backColor);
            g2d.fill(copyBounds);
            g2d.setColor(foreground);
            g2d.draw(copyBounds);
        }

    }

    public static class DefaultAnimationModel implements AnimationModel {

        private List<Animatable> animatables;

        public DefaultAnimationModel() {
            animatables = new ArrayList<>(25);
        }

        @Override
        public synchronized void update(AnimationCanvas canvas, float progress) {
            for (Animatable animatable : animatables) {
                animatable.update(canvas, progress);
            }
        }

        @Override
        public synchronized void add(Animatable animatable) {
            animatables.add(animatable);
        }

        @Override
        public synchronized void remove(Animatable animatable) {
            animatables.remove(animatable);
        }

        @Override
        public synchronized Animatable[] getAnimatables() {
            return animatables.toArray(new Animatable[animatables.size()]);
        }

    }

}

更新

您将面临的最大问题是屏幕只能以整数工作......

private class Animator implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        squares[0].y += 1;
        squares[1].y += 1;
        repaint();
    }
}

我相信这两个方块其实都是在“抖动”,但是因为绘制出来的方块有这么明显的体不足,所以更加突出。我以大约 24fps 的速度运行此测试,没有任何问题。

【讨论】:

  • 嗯,这是一个相当复杂的代码,而我的代码要简单得多,也不是很正确。我只是使用一个简单的 Timer 并每 20 毫秒 repaint() 一次画布。但是尝试制作 2 个简单的正方形,一个填充一个,一个只绘制,然后在您的代码中移动它们的速度要慢得多。以大约 2px / sec 的速度移动它们。这样你会看到 AA 使填充的正方形看起来模糊而不波涛汹涌,但绘制的正方形会很锐利但不连贯。
  • 我会尝试复制你的问题,但这就是为什么一个工作示例如此重要
  • 我编辑了原始问题并为其提供了工作代码。随时对此发表评论。您可以清楚地看到左边的方块移动平稳,右边的方块移动波涛汹涌。
【解决方案2】:

你有:

private class Animator implements ActionListener {
       @Override
       public void actionPerformed(ActionEvent e) {
           squares[0].y += 0.10;
           squares[1].y += 0.10;
           repaint();
       }
   }

public void startAnimation() {
    t = new Timer(30, new Animator());
    t.start();
}

但是你可以通过改变来达到同样的效果

private class Animator implements ActionListener {
       @Override
       public void actionPerformed(ActionEvent e) {
           squares[0].y += 1;
           squares[1].y += 1;
           repaint();
       }
   }

public void startAnimation() {
    t = new Timer(300, new Animator());
    t.start();
}

效果相同,但循环次数减少了 10 倍。

我没有测试代码,但值得一试。

MadProgrammer 提供了适当的解决方案,他测量延迟和偏移量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-17
    • 2022-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多