Java 动画

标签 java swing animation

我开始对用 Java 制作动画(幻灯片、背景等)感兴趣。我知道 JavaFX 做这件事要好得多,但我只是固执地懒得切换。

这是我到目前为止得到的结果。

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;

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

public class BlurredLightCells extends JPanel {

    private static final long serialVersionUID = 4610174943257637060L;

    private Random random = new Random();

    private ArrayList<LightCell> lightcells;

    private float[] blurData = new float[500];

    public static void main(String[] args) {
        JFrame frame = new JFrame("Swing animated bubbles");
        frame.setSize(1000, 750);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.add(new BlurredLightCells(60));

        frame.setVisible(true);
    }

    public BlurredLightCells(int amtOfBCells) {
        setSize(1000, 750);
        /**
         * Below we initiate all the cells that are going to be drawn on screen
         */
        Arrays.fill(blurData, 1f / 20f);
        lightcells = new ArrayList<LightCell>(amtOfBCells);
        for (int i = 0; i < amtOfBCells; i++) {

            /**
             * Below we generate all the values for each cell(SHOULD be random for each one)
             */
            int baseSpeed = random(0, 3);
            int xSpeed = (int) Math.floor((Math.random() * (baseSpeed - -baseSpeed + baseSpeed)) + -baseSpeed);
            int ySpeed = (int) Math.round((Math.random() * baseSpeed) + 0.5);
            int radius = random(25, 100);
            int x = (int) Math.floor(Math.random() * getWidth());
            int y = (int) Math.floor(Math.random() * getHeight());
            int blurrAmount = (int) (Math.floor(Math.random() * 10) + 5);
            int alpha = (int) ((Math.random() * 15) + 3);

            /**
             * Now we draw a image, and apply transparency and a slight blur to it
             */
            Kernel kernel = new Kernel(blurrAmount, blurrAmount, blurData);
            BufferedImageOp op = new ConvolveOp(kernel);
            BufferedImage circle = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
            Graphics2D circlegfx = circle.createGraphics();
            circlegfx.setColor(new Color(255, 255, 255, alpha));
            circlegfx.fillOval(20, 20, radius, radius);
            circle = op.filter(circle, null);
            LightCell bubble = new LightCell(x, y, xSpeed, ySpeed, radius, getDirection(random.nextInt(3)), circle);
            lightcells.add(bubble);
        }
    }

    public int random(int min, int max) {
        final int n = Math.abs(max - min);
        return Math.min(min, max) + (n == 0 ? 0 : random.nextInt(n));
    }

    @Override
    public void paint(Graphics g) {
        int w = getWidth();
        int h = getHeight();
        final Graphics2D g2 = (Graphics2D) g;
        GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
        g2.setPaint(gp);
        g2.fillRect(0, 0, w, h);
        long start = System.currentTimeMillis();
        for (int i = 0; i < lightcells.size(); i++) {
            LightCell cell = lightcells.get(i);
            cell.process(g2);
        }
        System.out.println("Took " + (System.currentTimeMillis() - start) + " milliseconds to draw ALL cells.");
        repaint();
    }

    public String getDirection(int i) {
        switch (i) {
            case 0:
                return "right";
            case 1:
                return "left";
            case 2:
                return "up";
            case 3:
                return "down";
        }
        return "";
    }

    private class LightCell {

        private int x, y, xSpeed, ySpeed, radius;
        private String direction;
        private BufferedImage image;

        public LightCell(int x, int y, int xSpeed, int ySpeed, int radius, String direction, BufferedImage image) {
            this.x = x;
            this.y = y;
            this.xSpeed = xSpeed;
            this.ySpeed = ySpeed;
            this.radius = radius;
            this.direction = direction;
            this.image = image;
        }

        public void process(Graphics g) {
            switch (direction) {
                case "right":
                    moveRight();
                    break;
                case "left":
                    moveLeft();
                    break;
                case "up":
                    moveUp();
                    break;
                case "down":
                    moveDown();
                    break;
            }
            g.drawImage(image, x, y, null);
        }

        private void moveUp() {
            x += xSpeed;
            y -= ySpeed;
            if (y + (radius / 2) < 0) {
                y = getHeight() + (radius / 2);
                x = (int) Math.floor(Math.random() * getWidth());
            }

            if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
                y = radius + (radius / 2);
                x = (int) Math.floor(Math.random() * getWidth());
            }
        }

        private void moveDown() {
            x += xSpeed;
            y += ySpeed;
            if (y - (radius / 2) > getHeight()) {
                y = 0 - (radius / 2);
                x = (int) Math.floor(Math.random() * getWidth());
            }

            if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
                y = getHeight() + (radius / 2);
                x = (int) Math.floor(Math.random() * getWidth());
            }
        }

        private void moveRight() {
            x += ySpeed;
            y += xSpeed;
            if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
                x = 0 - (radius / 2);
                y = (int) Math.floor(Math.random() * getHeight());
            }

            if ((x - radius / 2) > getWidth()) {
                x = 0 - (radius / 2);
                y = (int) Math.floor(Math.random() * getWidth());
            }
        }

        private void moveLeft() {
            x -= ySpeed;
            y -= xSpeed;
            if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
                x = getWidth() + (radius / 2);
                y = (int) Math.floor(Math.random() * getHeight());
            }

            if ((x + radius / 2) < 0) {
                x = getWidth() + (radius / 2);
                y = (int) Math.floor(Math.random() * getWidth());
            }
        }
    }
}

如果你运行它,你会看到细胞以非常高的速度移动,如果你查看代码,你会看到我调用了 repaint()paint我重写的方法。我知道这样做不好。但我的问题是,我是否可以通过其他方式在 repaint() 之外绘制每个单元格?我现在有一个循环,因为当我在带有其他组件的 JFrame 中使用它时,这会导致其他组件闪烁。

仅供引用:最终我想实现与此类似的目标:Click Here

谢谢!

最佳答案

闪烁问题与顶层容器不是双缓冲这一事实有关。不要从 JFrame(或其他顶级容器)扩展,您应该考虑使用更像 JPanel 的东西并覆盖它的 paintComponent

nb- 在我的脑海中,OP 是从 JFrame 扩展的...

有两个问题可能导致闪烁。第一个是覆盖 paint,第二个是不调用 super.paint(g) 和更新之间的时间。更好的解决方案是覆盖 paintComponent 并确保调用 super.paintComponent。此外,使用类似 javax.swing.Timer 的东西来安排更新和定期间隔也有助于...

只有在您希望RepaintManager 更新您的组件时才调用repaint。不要从任何 paintXxx 方法中调用 repaint,这将导致一个永无止境的绘画请求循环调度到事件队列中,最终消耗您的 CPU

我会避免在您的 paintXxx 方法中执行任何可能需要时间执行的操作,这会减慢渲染过程。相反,我会使用 javax.swing.Timer 进行简单更新或进行更复杂的处理,一个 Thread 可用于在模型呈现给屏幕。

这是一个 example我做了一些简单的优化过程,将 500 个对象的动画提升到 4500 个,整体性能只有轻微下降。

已更新

我稍微修改了你的代码,它工作正常......

enter image description here

我将您的paint 方法更改为paintComponent 并添加了super.paintComponent(g)

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    int w = getWidth();
    int h = getHeight();
    final Graphics2D g2 = (Graphics2D) g;
    GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
    g2.setPaint(gp);
    g2.fillRect(0, 0, w, h);
    for (int i = 0; i < lightcells.size(); i++) {
        LightCell cell = lightcells.get(i);
        cell.process(g2);
    }
}

在构造函数的末尾,我添加了...

Timer timer = new Timer(40, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();
    }
});
timer.start();

要定期更新 UI...

关于Java 动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18818753/

相关文章:

java - 如何管理调用另一个 JInternalFrame 的 JInternalFrame?

java - 如何将 Swing 组件添加到 SWT 中?

java - 如何在我的 Android 应用程序中实现 GIF?

java - 访问方法调用者捕获的异常

java - 如何找到具有 2 个状态 : clear and class ="error" 的占位符

java - 将所有输入转换为大写

ios - 更新屏幕外的 UILabels 导致它们返回 "Snap"返回

java - Swing 动画的计时

java - 将 spring-mvc AbstractExcelView 与已创建的文档一起使用

java - mvn 集成测试期间没有运行 selenium 测试