java中用于快速移动对象的平滑动画

java中用于快速移动对象的平滑动画,java,animation,javafx,Java,Animation,Javafx,我正在创建一个简单的动画,球以不同的速度从屏幕的一侧移动到另一侧。问题是,随着球的速度加快,我可以看到球明显的闪烁,实际上这很难解释,但当球的一部分仍在前一步时,我可以看到重新绘制 我尝试了很多东西,包括: 本机swing动画使用第一个线程/sleep/repain,然后移动到计时器 切换到swing jframe内的javafx画布/窗格。尝试了过渡和动画计时器 修补CreateBufferStrategy,对于1,2,3-老实说,没有看到任何区别(可能我做错了什么…) 我的问题是如何提高平滑

我正在创建一个简单的动画,球以不同的速度从屏幕的一侧移动到另一侧。问题是,随着球的速度加快,我可以看到球明显的闪烁,实际上这很难解释,但当球的一部分仍在前一步时,我可以看到重新绘制

我尝试了很多东西,包括:

  • 本机swing动画使用第一个线程/sleep/repain,然后移动到计时器

  • 切换到swing jframe内的javafx画布/窗格。尝试了过渡和动画计时器

  • 修补CreateBufferStrategy,对于1,2,3-老实说,没有看到任何区别(可能我做错了什么…)

  • 我的问题是如何提高平滑度,以及我想用本机java实现什么,还是使用一些外部库更好?如果是的话,你能推荐一些吗

    下面显示了我第二次/第三次尝试的示例代码

    import java.awt.Dimension;
    import java.awt.GraphicsDevice;
    import java.awt.GraphicsEnvironment;
    
    import javafx.animation.Interpolator;
    import javafx.animation.Timeline;
    import javafx.animation.TranslateTransition;
    import javafx.application.Platform;
    import javafx.embed.swing.JFXPanel;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.util.Duration;
    import javax.swing.JFrame;
    
    public class FXTrackerPanel extends JFrame {
    
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        public int crSize = 30;
        public double xPos = crSize;
        public double yPos = 100;
        public int xSize = 100;
        public int ySize = 100;
        public Circle r;
        int dir = 1;
    
        public void updateScreenSize() {
            int screen = 0;
            GraphicsEnvironment ge = GraphicsEnvironment
                .getLocalGraphicsEnvironment();
            GraphicsDevice[] gs = ge.getScreenDevices();
            if( screen > -1 && screen < gs.length )
            {           
                xSize = gs[screen].getDisplayMode().getWidth();
                ySize = gs[screen].getDisplayMode().getHeight();
            }
            else if( gs.length > 0 )
            {
                xSize = gs[0].getDisplayMode().getWidth();
                ySize = gs[0].getDisplayMode().getHeight();
            }
            else
            {
                throw new RuntimeException( "No Screens Found" );
            }
    
            yPos = ySize / 2;
        }
    
        private void initFXPanel(JFXPanel fxPanel) {
            updateScreenSize();
            xPos = crSize;
    
            Group root = new Group();
    
            double speed = 5;
    
            int repeats = Timeline.INDEFINITE;
    
            r = new javafx.scene.shape.Circle(xPos, yPos, crSize / 2, Color.RED);
            TranslateTransition tt = new TranslateTransition(Duration.seconds(speed), r);
            tt.setFromX(xPos);
            tt.setToX(xSize - crSize * 3);
            tt.setCycleCount(repeats);
            tt.setAutoReverse(true);
            tt.setInterpolator(Interpolator.EASE_BOTH);
            tt.play();
    
            root.getChildren().add(r);
    
    //      new AnimationTimer() {
    //          
    //          @Override
    //          public void handle(long now) {
    //              double speed = 20;
    //              try {
    //                  speed = Double.valueOf(TETSimple.mp.speedSinus.getText());
    //              }
    //              catch (Exception ex) {
    //                  speed = 20;
    //              }
    //              double xMov = (speed * 4 * Math.sin( xPos * Math.PI / xSize ) );
    //              if (xMov <= 0) {
    //                  xMov = 1;
    //              }
    //              if (dir == 1) {
    //                  if (xPos >= xSize - crSize)
    //                      dir = 0;
    //                  xPos += xMov;
    //              } else {
    //                  if (xPos <= 1)
    //                      dir = 1;
    //                  xPos -= xMov;
    //              }
    //              
    //              r.setTranslateX(xPos);              
    //          }
    //      }.start();
    
            fxPanel.setScene(new Scene(root));
        }
    
        public FXTrackerPanel() {
            updateScreenSize();
            this.setSize(new Dimension(xSize, ySize));
            this.setPreferredSize(new Dimension(xSize, ySize));
            this.setVisible(true);
            this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            JFXPanel fxPanel = new JFXPanel();
            this.add(fxPanel);
            this.createBufferStrategy(3);
    
            Platform.runLater(new Runnable() {
    
                @Override
                public void run() {
                    initFXPanel(fxPanel);               
                }
            });
        }
    
        public static void main(String[] args)
        {   
            new FXTrackerPanel();
        }
    }
    
    导入java.awt.Dimension;
    导入java.awt.GraphicsDevice;
    导入java.awt.GraphicsEnvironment;
    导入javafx.animation.Interpolator;
    导入javafx.animation.Timeline;
    导入javafx.animation.TranslateTransition;
    导入javafx.application.Platform;
    导入javafx.embed.swing.JFXPanel;
    导入javafx.scene.Group;
    导入javafx.scene.scene;
    导入javafx.scene.canvas.canvas;
    导入javafx.scene.paint.Color;
    导入javafx.scene.shape.Circle;
    导入javafx.util.Duration;
    导入javax.swing.JFrame;
    公共类FXTrackerPanel扩展JFrame{
    /**
    * 
    */
    私有静态最终长serialVersionUID=1L;
    公共int crSize=30;
    公共双XPO=crSize;
    公共双YPO=100;
    公共int xSize=100;
    公共实体=100;
    公众圈;
    int dir=1;
    public void updateScreenSize(){
    int屏幕=0;
    GraphicsEnvironment ge=GraphicsEnvironment
    .getLocalGraphicsEnvironment();
    GraphicsDevice[]gs=ge.getScreenDevices();
    如果(屏幕>-1&&screen0)
    {
    xSize=gs[0].getDisplayMode().getWidth();
    ySize=gs[0].getDisplayMode().getHeight();
    }
    其他的
    {
    抛出新的RuntimeException(“未找到屏幕”);
    }
    yPos=ySize/2;
    }
    私有void initFXPanel(JFXPanel fxPanel){
    updateScreenSize();
    xPos=crSize;
    组根=新组();
    双速=5;
    int repeats=Timeline.unfinite;
    r=新的javafx.scene.shape.Circle(xPos、yPos、crSize/2、Color.RED);
    TranslateTransition tt=新的TranslateTransition(持续时间。秒(速度),r);
    tt.setFromX(xPos);
    tt.setToX(xSize-crSize*3);
    tt.setCycleCount(重复);
    tt.setAutoReverse(真);
    tt.setInterpolator(Interpolator.EASE_两者);
    tt.play();
    root.getChildren().add(r);
    //新的AnimationTimer(){
    //          
    //@覆盖
    //公共无效句柄(长){
    //双速=20;
    //试一试{
    //speed=Double.valueOf(TETSimple.mp.speedSinus.getText());
    //              }
    //捕获(例外情况除外){
    //速度=20;
    //              }
    //double xMov=(速度*4*Math.sin(xPos*Math.PI/xSize));
    //if(xMov=xSize-crSize)
    //dir=0;
    //xPos+=xMov;
    //}其他{
    //如果(xPos-1&&屏幕长度0){
    xSize=gs[0].getDisplayMode().getWidth();
    ySize=gs[0].getDisplayMode().getHeight();
    }否则{
    抛出新的RuntimeException(“未找到屏幕”);
    }
    yPos=ySize/2;
    yPosPrev=ySize/2;
    }
    已执行的公共无效操作(操作事件arg0){
    if(方法==1)
    线条运动();
    其他的
    正弦运动();
    重新绘制(0,ySize/2,xSize,crSize);
    }
    专用双解析文本2int(字符串文字){
    试一试{
    返回Double.valueOf(literal);
    }捕获(例外情况除外){
    例如printStackTrace();
    }
    返回10.0;
    }
    私有void checkFinishCondition(){
    如果(通过+1>重复&&repeats!=0){
    如果(!单击关闭){
    TETSimple.mp.bStop.doClick();
    clickedClose=true;
    }
    回来
    }
    }
    私人行动{
    此参数为.updateScreenSize();
    this.repeats=parseText2Int(TETSimple.mp.repeatsCount.getText()).intValue();
    checkFinishCondition();
    double speed=parseText2Int(TETSimple.mp.speedSinus.getText());
    double xMov=(speed*Math.sin(xPos*Math.PI/xSize));
    if(xMov=xSize-crSize)
    dir=0;
    xPosPrev=xPos;
    xPos+=xMov;
    }否则{
    
    如果(xPos我认为问题是由AWT中呈现的
    JFXPanel
    引起的:在幕后发生了一些复杂的事情,需要在两个不同的系统线程(AWT事件调度线程和FX应用程序线程)之间进行同步

    如果您可以将其编写为一个“纯”JavaFX应用程序(即没有Swing/AWT代码),那么它运行起来会更平稳:

    import javafx.animation.Interpolator;
    import javafx.animation.Timeline;
    import javafx.animation.TranslateTransition;
    import javafx.application.Application;
    import javafx.geometry.Rectangle2D;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.stage.Screen;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class FXAnimationTest extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            Group root = new Group();
    
            double speed = 5;
    
            int repeats = Timeline.INDEFINITE;
    
            Screen screen = Screen.getPrimary();
            Rectangle2D screenBounds = screen.getBounds();
            double xSize = screenBounds.getWidth();
            double ySize = screenBounds.getHeight();
    
            double crSize = 30 ;
            double xPos = crSize ;
            double yPos = ySize / 2 ;
    
            Circle r = new Circle(xPos, yPos, crSize / 2, Color.RED);
            TranslateTransition tt = new TranslateTransition(Duration.seconds(speed), r);
            tt.setFromX(xPos);
            tt.setToX(xSize - crSize * 3);
            tt.setCycleCount(repeats);
            tt.setAutoReverse(true);
            tt.setInterpolator(Interpolator.EASE_BOTH);
            tt.play();
    
            root.getChildren().add(r);
    
            Scene scene = new Scene(root, xSize, ySize);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    如果您必须在Swing应用程序中嵌入JavaFX,并且您使用的是Java 8(我在8u20上测试过),那么有一个系统属性可以在同一线程上运行两个UI工具包。我认为这目前仍然是实验性的,因此使用风险自担,
    import javafx.animation.Interpolator;
    import javafx.animation.Timeline;
    import javafx.animation.TranslateTransition;
    import javafx.application.Application;
    import javafx.geometry.Rectangle2D;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.stage.Screen;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class FXAnimationTest extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            Group root = new Group();
    
            double speed = 5;
    
            int repeats = Timeline.INDEFINITE;
    
            Screen screen = Screen.getPrimary();
            Rectangle2D screenBounds = screen.getBounds();
            double xSize = screenBounds.getWidth();
            double ySize = screenBounds.getHeight();
    
            double crSize = 30 ;
            double xPos = crSize ;
            double yPos = ySize / 2 ;
    
            Circle r = new Circle(xPos, yPos, crSize / 2, Color.RED);
            TranslateTransition tt = new TranslateTransition(Duration.seconds(speed), r);
            tt.setFromX(xPos);
            tt.setToX(xSize - crSize * 3);
            tt.setCycleCount(repeats);
            tt.setAutoReverse(true);
            tt.setInterpolator(Interpolator.EASE_BOTH);
            tt.play();
    
            root.getChildren().add(r);
    
            Scene scene = new Scene(root, xSize, ySize);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    java -Djavafx.embed.singleThread=true FXTrackerPanel
    
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.RenderingHints;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JSlider;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.event.ChangeEvent;
    import javax.swing.event.ChangeListener;
    
    public class BouncyBall {
    
        public static void main(String[] args) {
            new BouncyBall();
        }
    
        public BouncyBall() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new ControlPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class ControlPane extends JPanel {
    
            private JSlider speed;
            private JSlider quanity;
    
            private BallPitPane ballPitPane;
    
            public ControlPane() {
                setLayout(new BorderLayout());
                ballPitPane = new BallPitPane();
                add(ballPitPane);
    
                JPanel controls = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.anchor = GridBagConstraints.WEST;
    
                speed = new JSlider(1, 100, 4);
                quanity = new JSlider(1, 100, 1);
    
                controls.add(new JLabel("Speed:"), gbc);
                gbc.gridy++;
                controls.add(new JLabel("Quanity:"), gbc);
    
                gbc.gridx++;
                gbc.gridy = 0;
                gbc.weightx = 1;
                gbc.fill = GridBagConstraints.HORIZONTAL;
    
                controls.add(speed, gbc);
                gbc.gridy++;
                controls.add(quanity, gbc);
                add(controls, BorderLayout.SOUTH);
    
                speed.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        ballPitPane.setSpeed(speed.getValue());
                    }
                });
    
                quanity.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        ballPitPane.setQuanity(quanity.getValue());
                    }
                });
            }
    
        }
    
        public class BallPitPane extends JPanel {
    
            private List<Ball> balls;
            private int speed;
    
            public BallPitPane() {
                balls = new ArrayList<>(25);
                setSpeed(2);
                setQuanity(1);
    
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (Ball ball : balls) {
                            ball.update(getWidth(), speed);
                        }
                        repaint();
                    }
                });
                timer.start();
            }
    
            public void setSpeed(int speed) {
                this.speed = speed;
            }
    
            public void setQuanity(int quanity) {
    
                while (balls.size() > quanity) {
                    balls.remove(0);
                }
                while (balls.size() < quanity) {
                    int radius = 4 + (int) (Math.random() * 48);
                    Ball ball = new Ball(
                            randomColor(),
                            (int) Math.abs(Math.random() * getWidth() - radius),
                            (int) Math.abs(Math.random() * getHeight() - radius),
                            radius
                    );
                    balls.add(ball);
                }
    
            }
    
            protected Color randomColor() {
    
                int red = (int) Math.abs(Math.random() * 255);
                int green = (int) Math.abs(Math.random() * 255);
                int blue = (int) Math.abs(Math.random() * 255);
    
                return new Color(red, green, blue);
    
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(400, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
                g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
                for (Ball ball : balls) {
                    ball.paint(g2d);
                }
                g2d.dispose();
            }
    
            public class Ball {
    
                private Color color;
                private int x;
                private int y;
                private int radius;
                private int delta;
    
                public Ball(Color color, int x, int y, int radius) {
                    this.color = color;
                    this.x = x;
                    this.y = y;
                    this.radius = radius;
                    delta = Math.random() > 0.5 ? 1 : -1;
                }
    
                public void update(int width, int speed) {
                    x += speed * delta;
                    if (x + radius >= width) {
                        x = width - radius;
                        delta *= -1;
                    } else if (x < 0) {
                        x = 0;
                        delta *= -1;
                    }
                }
    
                public void paint(Graphics g) {
                    g.setColor(color);
                    g.fillOval(x, y, radius, radius);
                }
    
            }
    
        }
    
    }