不移动鼠标光标时Java动画口吃

Posted

技术标签:

【中文标题】不移动鼠标光标时Java动画口吃【英文标题】:Java animation stutters when not moving mouse cursor 【发布时间】:2013-10-29 02:13:15 【问题描述】:

我有一个非常简单的动画,一个大字体的文本连续(逐个像素)向左移动。首先将文本转换为图像,然后启动计时器任务,该任务重复(每 10-20 毫秒)将图像的 x 坐标减 1,并执行 repaint()。

此程序在某些系统上显示出奇怪的行为。在我的带有 nVidia 卡的 PC 上,它运行流畅。在我的 Vaio 笔记本、BeagleBoneBlack 和朋友的 Mac 上,它严重卡顿。它似乎暂停了一会儿,然后向左跳了大约 10 个像素,再次暂停,依此类推。

让我感到困惑的是,在这些系统上,动画只有在您不触摸鼠标时会卡顿。只要您在窗口内移动鼠标光标,无论多慢,或拖动窗口本身,动画都非常流畅!

谁能解释一下?这是程序:

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;

class Textimg extends JComponent

    String      str;
    Font        font;
    int         x = 0;
    final int   ytext = 136;
    Image       img;

    public Textimg(String s)
    
        str = s;
        font = new Font("Noserif", Font.PLAIN, 96);
        setLayout(null);
    

    protected void paintComponent(Graphics g)
    
        if (img == null)
        
            img = createImage(4800, 272);
            Graphics gr = img.getGraphics();

            gr.setFont(font);
            gr.setColor(Color.BLACK);
            gr.fillRect(0, 0, 4800, 272);
            gr.setColor(new Color(135, 175, 0));
            gr.drawString(str, 0, ytext);
            gr.dispose();
        

        g.drawImage(img, x, 0, this);
    

    public void addX(int dif)
    
        if (isVisible())
        
            x = x + dif;

            Graphics g = getGraphics();

            if (g != null) paintComponent(g);
        
    
 


public class Banner extends JFrame 
 
    StringBuffer    buf;
    int             sleeptime = 10;

    Banner(String path) throws IOException 
     
        setSize(new Dimension(480, 272));
        setTitle("Java Test");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(null);

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(path), "UTF-8"));

        buf = new StringBuffer();

        while (true) 
        
           String line = reader.readLine();

           if (line == null) break;
           buf.append(line);
        

        final Textimg textimg = new Textimg(buf.toString());

        add(textimg);
        textimg.setBounds(0, 0, 480, 272);

        final javax.swing.Timer timer = new javax.swing.Timer(200, new ActionListener()
        
            public void actionPerformed(ActionEvent e)
            
                textimg.addX(-1);
            
        );

        timer.setDelay(sleeptime);
        timer.start();
    

    //----------------------------------------------------------------------

    public static void main(String[] args) throws Exception
    
        new Banner(args[0]).setVisible(true);
    

【问题讨论】:

【参考方案1】:

Profiling 表明您正在使javax.swing.Timer 使用的共享线程饱和。一种缓解策略是使用更长的周期和/或更大的增量/减量,如 here 所示。

附录:此外,您在每次调用 paintComponent() 时都在费力地重新渲染整个图像。相反,每次只使用TextLayout、看到here 和draw() 渲染它一次

【讨论】:

另见MarqueePanel,引用here。 谢谢。我将研究 TextLayout,以前从未使用过。但是,恕我直言,每当我移动鼠标时动画运行流畅这一事实清楚地表明资源瓶颈不是原因。 结果会因平台而异;在我的系统上,鼠标没有任何作用。我会从隐式计时器循环中提升相对昂贵的渲染。您的基本方法是合理的,并且可能比基于字符的方案更流畅。 我实验了一点。效果完全独立于文本长度,即使在循环中我除了x = x -1; setLocation(x, 0);什么都不做时也会出现。 我尝试了其他Java版本:Java 6类似,Java 8 EA更差,但在Java 5中它在我的笔记本上运行良好,动画运行流畅,无需移动鼠标。所以我想这是一个潜入 Java 6 的错误。现在我有一个不同的问题 - Oracle 不为 ARM 处理器提供 Java 5 JRE。【参考方案2】: 永远不要使用getGraphics,而且您永远也不应该自己调用paintComponent,这不是在Swing 中自定义绘制的方式。相反,更新状态并调用repaint。 不要依赖幻数,使用手头的信息(getWidthgetHeight) Swing 组件是双缓冲的,因此您不太可能需要创建自己的缓冲策略。这个简单的动作可能会减慢您的绘画速度 您必须致电super.paintComponent。对于JComponent,这一点更为重要,因为它不是不透明的,否则可能会导致一些令人讨厌的油漆伪影。 您应该覆盖JComponent#getPreferredSize,以便它可以与布局管理器有效地协同工作。 您可能会发现较短的延迟会产生更好的错觉,例如 40 毫秒(大约 25fps)

看看Swing animation running extremely slow,它通过一些对象管理和优化,能够从 500 个动画对象增加到 4500 个。

还请特别查看Performing Custom Painting 和Painting in AWT and Swing

【讨论】:

【参考方案3】:

问题解决了!

回答我自己的问题:在意识到任何连续输入(鼠标或键盘)可以使动画顺利运行后,我记得输入可以由程序本身生成,使用类java.awt.Robot。这导致了一个简单的解决方法: 创建一个机器人,让它在每个动画循环中按下一个键或鼠标移动。

final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()

    public void actionPerformed(ActionEvent e)
    
        // update your image...

        robot.keyPress(62);
     
);

这是一个杂牌,但效果很好。

【讨论】:

62这个数字是怎么选的? 哈哈,好方法【参考方案4】:

绘制完成后尝试调用此方法:

 Toolkit.getDefaultToolkit().sync();

这会刷新某些系统(如 Linux)使用的图形缓冲区。请参阅 Javadoc:http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()

【讨论】:

这为我解决了口吃问题(在 Chrubuntu 14.04 Acer C720 上),但我将其切换为 frame.getToolkit().sync(); 以避免全局变量。谢谢! 天哪,非常感谢,我已经尝试修复这个错误好几个小时了!在调用repaint() 之后的框架中也使用了getToolkit().sync()。 (Ubuntu 14.04) 今天这个答案对我有帮助。 您将如何为 Nimbus LAF 的 indeterminate JProgressBar 动画“实现”这一点?这个问题听起来更像是一个真正的错误。 你拯救了我的一天!

以上是关于不移动鼠标光标时Java动画口吃的主要内容,如果未能解决你的问题,请参考以下文章

纯JS制作跟随鼠标移动的炫彩气泡,光标动画效果ui

使用更改的 CSS 光标属性更新鼠标光标而不移动鼠标

使用 jQuery 制作动画时失去悬停(不移动鼠标)

使用jQuery动画远离光标时,停止“:悬停”元素保持其状态?

在鼠标光标平面上绘图或创建自定义动画系统光标?

怎样用键盘移动光标?