Java 防止摆动动画跳跃
我已经用一个JPanel和一个javax.swing.Timer制作了这个基本的例子,我希望动画相对平滑 如果我保持鼠标在窗口上移动,动画将平滑。如果我根本不与窗口交互,则动画开始跳跃Java 防止摆动动画跳跃,java,swing,Java,Swing,我已经用一个JPanel和一个javax.swing.Timer制作了这个基本的例子,我希望动画相对平滑 如果我保持鼠标在窗口上移动,动画将平滑。如果我根本不与窗口交互,则动画开始跳跃 import javax.swing.*; import java.awt.*; public class SmoothSwing{ double x = 0; double y = 0; double theta = 0; public void step(){
import javax.swing.*;
import java.awt.*;
public class SmoothSwing{
double x = 0;
double y = 0;
double theta = 0;
public void step(){
x = 175 + 175*Math.sin( theta );
y = 175 + 175*Math.cos( theta );
theta += 0.02;
if(theta > 6.28) theta = 0;
}
public void buildGui(){
JFrame frame = new JFrame("smooth");
JPanel panel = new JPanel(){
Dimension dim = new Dimension(400, 400);
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval((int)x, (int)y, 50, 50);
}
@Override
public Dimension getMinimumSize(){
return dim;
}
@Override
public Dimension getPreferredSize(){
return dim;
}
};
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(30, evt->{
step();
panel.repaint();
});
timer.start();
}
public static void main(String[] args){
EventQueue.invokeLater( new SmoothSwing()::buildGui );
}
}
在我看来,重新粉刷的颜色在不断累积,并成团地重新粉刷。所以我注释掉了super.paintcomponent这行;这将导致面板无法清除
当我这样做时,我可以看到paintComponent正在运行,因为所有的圆都在绘制中,但显示仍然只有在绘制了多个圆之后才会更新
我在使用Linux的jdk-14+36、jdk-11.0.6和1.8.0_181上遇到了这个问题。Ubuntu 18.04
下面是比较差异的图表。我很难控制GIF的速度,但这说明了正在发生的事情
从左到右,如果我把鼠标移到窗口上,我会看到左边的行为,当我停下来后,中间会有点跳跃,然后右边会更跳跃
我可以做一件事来避免这个问题,那就是把我的计时器分成两个任务
Timer t1 = new Timer(30, evt->step());
Timer t2 = new Timer(3, evt->panel.repaint());
t1.start();
t2.start();
可以通过以下方式实现更平滑的动画: -使用更精细的θ步 -更新频率更高 -使用双值绘制:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SmoothSwing{
double x = 0;
double y = 0;
double theta = 0;
public void step(){
x = 175 + 175*Math.sin( theta );
y = 175 + 175*Math.cos( theta );
theta += 0.002; //finer theta step
if(theta > 6.28) {
theta = 0;
}
}
public void buildGui(){
JFrame frame = new JFrame("smooth");
JPanel panel = new JPanel(){
Dimension dim = new Dimension(400, 400);
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
}
@Override
public Dimension getMinimumSize(){
return dim;
}
@Override
public Dimension getPreferredSize(){
return dim;
}
};
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(3, evt->{ //higher frequency update
step();
panel.repaint();
});
timer.start();
}
public static void main(String[] args){
EventQueue.invokeLater( new SmoothSwing()::buildGui );
}
}
可以通过以下方式实现更平滑的动画: -使用更精细的θ步 -更新频率更高 -使用双值绘制:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SmoothSwing{
double x = 0;
double y = 0;
double theta = 0;
public void step(){
x = 175 + 175*Math.sin( theta );
y = 175 + 175*Math.cos( theta );
theta += 0.002; //finer theta step
if(theta > 6.28) {
theta = 0;
}
}
public void buildGui(){
JFrame frame = new JFrame("smooth");
JPanel panel = new JPanel(){
Dimension dim = new Dimension(400, 400);
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
}
@Override
public Dimension getMinimumSize(){
return dim;
}
@Override
public Dimension getPreferredSize(){
return dim;
}
};
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(3, evt->{ //higher frequency update
step();
panel.repaint();
});
timer.start();
}
public static void main(String[] args){
EventQueue.invokeLater( new SmoothSwing()::buildGui );
}
}
当我运行you示例或cOder示例时,我没有注意到运动随时间的变化。尽我所能,它保持不变 在我看来,重新粉刷的颜色在不断累积,并成团地重新粉刷 我现在要绕过Swing中的RepaitManager,唯一的方法就是使用paintInstance…方法。这会导致立即绘制零部件,而不会添加到EDT的末尾。不建议使用它,因为它会降低整体喷漆性能 任何更新类状态的方法都应该是类的一部分,而不是类的外部。所以,我修改了你的原始代码。这允许我在步骤方法中使用绘画逻辑:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SmoothSwing2 extends JPanel
{
double x = 0;
double y = 0;
double theta = 0;
Dimension dim = new Dimension(400, 400);
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
}
@Override
public Dimension getMinimumSize()
{
return dim;
}
@Override
public Dimension getPreferredSize()
{
return dim;
}
public void step()
{
x = 175 + 175*Math.sin( theta );
y = 175 + 175*Math.cos( theta );
theta += 0.008; //finer theta step
if(theta > 6.28) {
theta = 0;
}
repaint();
// paintImmediately( getBounds() ); // repaints the entire panel
// paintImmediately(x - 5, y - 5, 60, 60); // repaint only the circle
}
public void buildGui()
{
SmoothSwing panel = new SmoothSwing();
JFrame frame = new JFrame("smooth");
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(3, evt-> panel.step)
timer.start();
}
public static void main(String[] args){
EventQueue.invokeLater( new SmoothSwing2()::buildGui );
}
}
我没有注意到使用这三种方法中的任何一种都有任何区别
如果您继续注意到重绘的累积,那么可能是您的操作系统没有不断地为应用程序提供CPU
我在Windows 10上使用JDK 11。当我运行you示例或cOder示例时,我没有注意到运动随时间的变化。尽我所能,它保持不变 在我看来,重新粉刷的颜色在不断累积,并成团地重新粉刷 我现在要绕过Swing中的RepaitManager,唯一的方法就是使用paintInstance…方法。这会导致立即绘制零部件,而不会添加到EDT的末尾。不建议使用它,因为它会降低整体喷漆性能 任何更新类状态的方法都应该是类的一部分,而不是类的外部。所以,我修改了你的原始代码。这允许我在步骤方法中使用绘画逻辑:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SmoothSwing2 extends JPanel
{
double x = 0;
double y = 0;
double theta = 0;
Dimension dim = new Dimension(400, 400);
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
}
@Override
public Dimension getMinimumSize()
{
return dim;
}
@Override
public Dimension getPreferredSize()
{
return dim;
}
public void step()
{
x = 175 + 175*Math.sin( theta );
y = 175 + 175*Math.cos( theta );
theta += 0.008; //finer theta step
if(theta > 6.28) {
theta = 0;
}
repaint();
// paintImmediately( getBounds() ); // repaints the entire panel
// paintImmediately(x - 5, y - 5, 60, 60); // repaint only the circle
}
public void buildGui()
{
SmoothSwing panel = new SmoothSwing();
JFrame frame = new JFrame("smooth");
frame.add(panel, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(3, evt-> panel.step)
timer.start();
}
public static void main(String[] args){
EventQueue.invokeLater( new SmoothSwing2()::buildGui );
}
}
我没有注意到使用这三种方法中的任何一种都有任何区别
如果您继续注意到重绘的累积,那么可能是您的操作系统没有不断地为应用程序提供CPU
我在Windows 10上使用JDK 11。如果您提前完成所有计算,并将结果存储在ArrayList(可能是ArrayList)中,然后简单地在计时器中遍历一个或多个列表,会发生什么情况?这样会更平滑吗?Graphics2D g2d=Graphics2D g;g2d.setRenderingHintRenderingHints.KEY\u抗锯齿,RenderingHints.VALUE\u抗锯齿\u开启;g2d.SetRenderingHintrenderingHingHints.KEY\u STROKE\u控件,RenderingHints.VALUE\u STROKE\u PURE;g2d.drawnew Ellipse2D.Doublex,y,50,50@HovercraftFullOfEels我尝试制作一个点列表,但它没有改变任何东西。@c0我很惊讶渲染提示对帧速率有任何影响,但当我使用渲染提示时,它只会降低1级的速度。默认情况下,javax.swing.Timer合并事件。我在想,也许你之所以会有这种行为,是因为事件的结合。如果是这样,请确保在计时器上调用setCoalescefalse并重试。无论如何,我仍然无法重现错误。我只是提醒你试着用假的,甚至是真的。对于较小的延迟,跳跃可能会变得更加明显。如果您提前进行所有计算,并将结果存储在ArrayList(可能是ArrayList)中,然后简单地在计时器中的一个或多个列表中进行迭代,会发生什么情况?这样会更平滑吗?Graphics2D g2d=Graphics2D g;g2d.setRenderingHintRenderingHints.KEY\u抗锯齿,RenderingHints.VALUE\u抗锯齿\u开启;g2d.SetRenderingHintrenderingHingHints.KEY\u STROKE\u控件,RenderingHints.VALUE\u STROKE\u PURE;
g2d.drawnew Ellipse2D.Doublex,y,50,50@HovercraftFullOfEels我尝试制作一个点列表,但它没有改变任何东西。@c0我很惊讶渲染提示对帧速率有任何影响,但当我使用渲染提示时,它只会降低1级的速度。默认情况下,javax.swing.Timer合并事件。我在想,也许你之所以会有这种行为,是因为事件的结合。如果是这样,请确保在计时器上调用setCoalescefalse并重试。无论如何,我仍然无法重现错误。我只是提醒你试着用假的,甚至是真的。对于较小的延迟,跳跃可能会变得更加明显。问题不是θ太细,而是动画是分块绘制的。它开始时很好,但一旦没有输入,帧速率就会减慢,圆圈移动的步数也会变大。然后再等上一段时间,它的速度会减慢得更多,而且会走得更远。如果我将鼠标移到窗口上,动画将以预期的方式继续运行。您是否尝试了我发布的代码?这个解决方案的问题是,它增加了整体帧速率。目前我将帧速率设置为~30hz,您的解决方案将帧速率设置为~300hz。我很惊讶它解决了块中运动的问题,但是为什么我不能将帧速率设置为30hz呢?为什么它最终开始变慢。根据这个答案,我应该以我喜欢的速度运行我的动画,但只是用重绘来淹没摇摆。不是300Hz来完成它,而是更精细的θ。你可以期待粗糙的步伐和平稳的运动。不抱歉。我看不出来。我也不认为老鼠胡佛有效果。我可以看到,当x和y都改变时,圆会以小跳跃的方式移动。当x接近0时,运动更平滑圆在顶部或底部,当y左右接近0时。问题不是θ太细,动画是分块绘制的。它开始时很好,但一旦没有输入,帧速率就会减慢,圆圈移动的步数也会变大。然后再等上一段时间,它的速度会减慢得更多,而且会走得更远。如果我将鼠标移到窗口上,动画将以预期的方式继续运行。您是否尝试了我发布的代码?这个解决方案的问题是,它增加了整体帧速率。目前我将帧速率设置为~30hz,您的解决方案将帧速率设置为~300hz。我很惊讶它解决了块中运动的问题,但是为什么我不能将帧速率设置为30hz呢?为什么它最终开始变慢。根据这个答案,我应该以我喜欢的速度运行我的动画,但只是用重绘来淹没摇摆。不是300Hz来完成它,而是更精细的θ。你可以期待粗糙的步伐和平稳的运动。不抱歉。我看不出来。我也不认为老鼠胡佛有效果。我可以看到,当x和y都改变时,圆会以小跳跃的方式移动。当x接近0时,运动会更平滑。圆圈在顶部或底部,当y左右接近0时。我相信它也可能是操作系统。由于未调用super.paintComponent生成的图像绘制了所有的圆,我怀疑重新绘制会导致paintComponent以所需的速率启动,但操作系统只是没有显示更新的图形。不过,这对我来说是非常系统的。我停止移动指针,一段时间后帧速率下降。我相信这也可能是操作系统造成的。由于未调用super.paintComponent生成的图像绘制了所有的圆,我怀疑重新绘制会导致paintComponent以所需的速率启动,但操作系统只是没有显示更新的图形。不过,这对我来说是非常系统的。我停止移动指针,过了一会儿帧速率下降。