如何让线条动画更流畅?

2023-11-23

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

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

我在画布上的每个矩形都有这种方法来绘制自己。它每 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);
        }
    }
}

有什么办法可以解决这个问题吗?我环顾四周好一会儿。


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

enter image description here

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

    }

}

UPDATE

您将面临的最大问题是屏幕只能显示整数......

private class Animator implements ActionListener {

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

我相信这两个方块实际上都是“抖动”的,但由于绘制的方块有如此明显的身体缺失,所以它更加突出。我以大约 24 fps 的速度运行此测试,没有任何问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何让线条动画更流畅? 的相关文章

随机推荐

  • 未找到活动数据。使用谷歌分析 v4

    我收到此错误 I GAV4 7915 Thread GAThread 5 main 未找到活动数据 有人知道我出了什么问题吗 我正在使用 Android API 级别 20 谷歌分析 v4 安卓设备4 2 2 Constants TRACK
  • 对 HTML 实体进行编码但忽略 HTML 标签 - 在 PHP 中

    我有一个可能看起来像这样的字符串 str p Me Mrs Jones br live in strong style color FFF Espa a strong p htmlentities str ENT COMPAT UTF 8
  • 在node.js中使用formidable和express时如何更改上传路径

    实际上我遇到了两个问题 一 如何更改上传路径 我的文件夹结构是这样的 app js upload 我的节点代码位于 app js 中并从中启动 所以我想将上传图片上传到上传文件夹 我更改路径 var form new formidable
  • iOS 11 禁用密码自动填充附件视图选项?

    截至目前 我想选择退出 iOS 11 提供的新选项 即在应用程序中建议密码 当我在 iOS 11 上运行该应用程序时 我在键盘顶部看到自动填充选项 但我的用户名和密码文本字段甚至不显示 所以 我的问题是 如何一起禁用新密码自动填充功能 以便
  • WPF 中的 DialogResult 与FolderBrowserDialog

    我第一次在 WPF 中实现FolderBrowserDialog 但我一点也不喜欢它 除了我发现我的项目中没有引用 Windows Forms 的问题之外 现在我在尝试查看 DialogResult 返回值是什么时遇到了麻烦 对于 Open
  • MSBuild如何找到Delphi搜索路径?

    如果我启动 RAD Studio 命令提示符并运行 msbuild t Rebuild 在项目目录中 msbuild 将显示调用 dcc32 的完整命令行 包括所有路径设置 MSBuild 使用哪种魔法来根据 IDE 设置 存储在注册表中
  • 多次加密(MD5)可以提高安全性吗?

    我看到有人用 MD5 对用户密码进行多次加密以提高安全性 我不确定这是否有效 但看起来不太好 那么 这有意义吗 我们假设您使用的哈希函数是一个完美的单向函数 然后你可以像查看它的输出一样查看它的输出 随机神谕 其输出值在有限的值范围内 MD
  • Titanium appcelerator 是否值得在 ipad、iphone 和 android 上开发基于相机的应用程序? [关闭]

    Closed 这个问题是基于意见的 目前不接受答案 我想构建一个手机 平板电脑应用程序 其核心功能是用相机拍照 查看图片和接收通知 我还想针对 iphone ipad 和 android 平台 Titanium appcelerator 因
  • 如何使用 boto3 在 2 个不同帐户的 S3 存储桶之间复制文件

    我正在尝试使用 boto3 将文件从供应商 S3 存储桶传输到我的 S3 存储桶 我正在使用 sts 服务来承担访问供应商 s3 存储桶的角色 我能够连接到供应商存储桶并获取存储桶的列表 我遇到CopyObject operation Ac
  • form.reset() 是如何工作的?

    我知道form reset 会将所有表单字段重置为其默认值 但这是如何工作的呢 是浏览器的DOM实现吗 即浏览器知道上次回发 获取中从服务器发送的最后一个值是什么以及何时reset 称为浏览器重置这些值 DOM 规范tells us它 执行
  • 如何将 Magento 库存设置更改为网站范围而不是全局范围?

    我们有一个多商店设置 magento 跨多个域运行 其中一些网站是批发 B2B 网站 一些是零售 B2C 网站 我们对每个零售和批发网站有不同的价格 这在 magento 配置中很容易做到 我们这样做是因为我们强制批发客户使用购物车中允许的
  • “cout<<(char*)NULL”在这里执行“close(1)”吗? [复制]

    这个问题在这里已经有答案了 在下面的代码中我使用了cout lt lt char NULL 在这一行之后 我的程序没有在输出屏幕上打印任何内容 这是否意味着我已经做到了close 1 with cout这里 这里到底发生了什么 这是一个错误
  • 数据表更改界面语言

    我目前正在使用角度数据表 如何查看其他语言的表格界面 我的意思是西班牙语中的 显示条目 搜索 显示 20 个条目中的 1 到 10 个 文字 您需要定义这样的语言结构 丹麦实现 我在我的角度数据表应用程序中使用的 var language
  • 自动禁用特定主要模式的全局次要模式

    我已全局激活居中光标模式 如下所示 require centered cursor mode global centered cursor mode 1 它工作正常 但有一些主要模式我想自动禁用它 例如 slime repl 和 shell
  • 谷歌地图 API;禁止地图平移以启用页面滚动

    我有一个移动网页 客户可以通过表单提交或当前位置弹出窗口输入邮政编码 然后查找附近的中心 响应包括这些中心的列表以及显示其位置的谷歌地图插入以及指示列表中指示的中心的图钉 问题是地图占用了页面上的大量空间 向下滚动到地图正下方的列表很困难
  • 如何使用批处理脚本对目录中的每个文件执行多项操作

    这是这个问题的直接延伸 如何使用批处理脚本对目录中的每个文件执行某些操作 从上面我学习了如何对文件夹中的每个文件执行命令 如何对每个文件执行多个命令 我想先使用lame压缩文件 然后将原始文件移动到不同的目录 这是我到目前为止所拥有的 FO
  • 颤动 || W/DynamiteModule(4887):未找到 com.google.android.gms.providerinstaller.dynamite 的本地模块描述符类

    当我从 Firebase Firestore 获取数据时 不显示数据 这给了我这个问题 它与Firebase或其他什么有关吗 W DynamiteModule 4887 Local module descriptor class for c
  • initializer_list 不可变性质导致过度复制

    为什么可以访问std initializer list不允许我们更改其内容 这是一个很大的缺点std initializer list当将它用于其主要目的 初始化容器 时 因为它的使用会导致过多的复制构造 复制赋值 而不是移动构造 移动赋值
  • 将图形导出为 PDF 时,可以导出绘图标签中的特殊符号/西里尔字母吗?

    我正在尝试将图形列表导出为 PDF 格式的单独帧 以便随后借助外部实用程序 例如 pdf2swf 编译矢量 SWF 动画 不幸的是 导出的 PDF 文件中的一些特殊字符 例如度数符号或三点 被损坏 这也是所有俄语字母的命运 请注意 当直接从
  • 如何让线条动画更流畅?

    我正在用 Java 制作一个简单的动画 并试图使其尽可能流畅 我仅使用每个 Shape 对象的 Double 内部类 并在 Graphics2D 对象中设置抗锯齿功能 只要我只使用 fill 方法 这一切都有效 但如果我还使用 draw 方