Swing animation optimization

一曲冷凌霜 提交于 2019-12-31 03:45:08

问题


I have been working on a simple animation using a Timer on a JComponent. However, I experience incredibly choppy motion when I view the animation. What steps should I take to optimize this code?

MyAnimationFrame

import javax.swing.*;

public class MyAnimationFrame extends JFrame {
    public MyAnimationFrame() {
        super("My animation frame!");
        setSize(300,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(new AnimationComponent(0,0,50,50));
        setVisible(true);
    }

    public static void main(String[] args) {
        MyAnimationFrame f = new MyAnimationFrame();
    }
}

AnimationComponent

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

public class AnimationComponent extends JComponent implements ActionListener {
    private Timer animTimer;
    private int x;
    private int y;
    private int xVel;
    private int yVel;
    private int width;
    private int height;
    private int oldX;
    private int oldY;

    public AnimationComponent(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;

        animTimer = new Timer(25, this);
        xVel = 5;
        yVel = 5;

        animTimer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        g.fillOval(x,y,width,height);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        oldX = x;
        oldY = y;

        if(x + width > getParent().getWidth() || x < 0) {
             xVel *= -1;
        }

        if(y + height > getParent().getHeight() || y < 0) {
            yVel *= -1;
        }

        x += xVel;
        y += yVel;

        repaint();
    }
}

Not sure if this matters, but I am using OpenJDK version 1.8.0_121.

Any help is appreciated.


回答1:


After a wonderful discussion with Yago it occurred to me that the issue revolves around number of areas, alot comes down to the ability for Java to sync the updates with the OS and the hardware, some things you can control, some you can't.

Inspired by Yago's example and my "memory" of how the Timing Framework works, I tested you code by increasing the framerate (to 5 milliseconds, ~= 200fps) and decreasing the change delta, which gave the same results as using the Timing Framework, but which leaves you with the flexibility of your original design.

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Test {

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

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new AnimationComponent(0, 0, 50, 50));
                frame.setSize(300, 300);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class AnimationComponent extends JComponent implements ActionListener {

        private Timer animTimer;
        private int x;
        private int y;
        private int xVel;
        private int yVel;
        private int width;
        private int height;
        private int oldX;
        private int oldY;

        public AnimationComponent(int x, int y, int width, int height) {
            this.x = x;
            this.y = y;
            this.height = height;
            this.width = width;

            animTimer = new Timer(5, this);
            xVel = 1;
            yVel = 1;

            animTimer.start();
        }

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            g2d.setRenderingHints(hints);
            g2d.fillOval(x, y, width, height);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            oldX = x;
            oldY = y;

            if (x + width > getParent().getWidth() || x < 0) {
                xVel *= -1;
            }

            if (y + height > getParent().getHeight() || y < 0) {
                yVel *= -1;
            }

            x += xVel;
            y += yVel;

            repaint();
        }
    }
}

If you need to slow down the speed more, then decrease the change delta more, this will mean you have to use doubles instead, which will lead into the Shape's API which supports double values

Which should you use? That's up to you. The Timing Framework is really great for linear animations over a period of time, where you know you want to go from one state to another. It's not so good for things like games, where the state of the object can change from my cycle to another. I'm sure you could do it, but it'd be a lot easier with a simple "main loop" concept - IMHO




回答2:


Timing Framework offers a way to provide animations highly optimized which may help in this case.

MyAnimationFrame

import javax.swing.*;

public class MyAnimationFrame extends JFrame {
    public MyAnimationFrame() {
        super("My animation frame!");
        setSize(300,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(new AnimationComponent(0,0,50,50));
        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyAnimationFrame f = new MyAnimationFrame();
            }
        });
    }
}

AnimationComponent

import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.TimeUnit;
import org.jdesktop.core.animation.rendering.*;
import org.jdesktop.core.animation.timing.*;
import org.jdesktop.core.animation.timing.interpolators.*;
import org.jdesktop.swing.animation.rendering.*;
import org.jdesktop.swing.animation.timing.sources.*;

@SuppressWarnings("serial")
public class AnimationComponent extends JRendererPanel {

    protected int x;
    protected int y;
    protected int width;
    protected int height;
    protected Animator xAnimator;
    protected Animator yAnimator;

    public AnimationComponent(int x, int y, int width, int height) {
        setOpaque(true);

        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;

        JRendererFactory.getDefaultRenderer(this,
                new JRendererTarget<GraphicsConfiguration, Graphics2D>() {
            @Override
            public void renderSetup(GraphicsConfiguration gc) {
                // Nothing to do
            }
            @Override
            public void renderUpdate() {
              // Nothing to do
            }
            @Override
            public void render(Graphics2D g, int w, int h) {
                Color c = g.getColor();
                g.setColor(g.getBackground());
                g.fillRect(0, 0, w, h);
                g.setColor(c);
                g.fillOval(AnimationComponent.this.x, AnimationComponent.this.y,
                        AnimationComponent.this.width, AnimationComponent.this.height);
            }
            @Override
            public void renderShutdown() {
              // Nothing to do
            }
        }, false);

        this.xAnimator = new Animator.Builder(new SwingTimerTimingSource())
                .addTargets(new TimingTargetAdapter() {
                    @Override
                    public void timingEvent(Animator source, double fraction) {
                        AnimationComponent.this.x = (int) ((getWidth() - AnimationComponent.this.width) * fraction);
                    }})
                .setRepeatCount(Animator.INFINITE)
                .setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
                .setInterpolator(LinearInterpolator.getInstance()).build();

        this.yAnimator = new Animator.Builder(new SwingTimerTimingSource())
                .addTargets(new TimingTargetAdapter() {
                    @Override
                    public void timingEvent(Animator source, double fraction) {
                        AnimationComponent.this.y = (int) ((getHeight() - AnimationComponent.this.height) * fraction);
                    }})
                .setRepeatCount(Animator.INFINITE)
                .setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
                .setInterpolator(LinearInterpolator.getInstance()).build();

        addComponentListener(new ComponentAdapter() {
            private int oldWidth = 0;
            private int oldHeight = 0;
            @Override
            public void componentResized(ComponentEvent event) {
                Component c = event.getComponent();
                int w = c.getWidth();
                int h = c.getHeight();

                if (w != this.oldWidth) {
                    AnimationComponent.this.xAnimator.stop();
                    AnimationComponent.this.xAnimator = new Animator.Builder()
                            .copy(AnimationComponent.this.xAnimator)
                            .setDuration(w * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
                            .build();
                    AnimationComponent.this.xAnimator.start();
                }
                if (h != this.oldHeight) {
                    AnimationComponent.this.yAnimator.stop();
                    AnimationComponent.this.yAnimator = new Animator.Builder()
                            .copy(AnimationComponent.this.yAnimator)
                            .setDuration(h * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
                            .build();
                    AnimationComponent.this.yAnimator.start();
                }

                this.oldWidth = w;
                this.oldHeight = h;
            }
        });
    }

}

I'm getting good results but has one issue: any item you resize, the animation is reset.



来源:https://stackoverflow.com/questions/42942888/swing-animation-optimization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!