Java-анимация заикается, когда не перемещается курсор мыши

У меня довольно простая анимация, текст в большом шрифте перемещается непрерывно (пиксель за пикселем) влево. Текст сначала преобразуется в изображение, затем запускается задача таймера, которая несколько раз (каждые 10-20 мс) уменьшает координату x изображения на 1 и выполняет перерисовку().

Эта программа показывает странное поведение некоторых систем. На моем ПК с картой nVidia он работает гладко. На моем ноутбуке 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

Попробуйте вызвать этот метод, когда закончите рисовать:

 Toolkit.getDefaultToolkit().sync();

Это флеширует графический буфер, который используют некоторые системы, такие как Linux. См. Javadoc: http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()

Ответ 2

Профилирование показывает, что вы насыщаете общий поток, используемый javax.swing.Timer. Одна стратегия предотвращения состоит в том, чтобы использовать более длительный период и/или больший прирост/декремент, как показано здесь .

Добавление: Кроме того, вы трудоемко перерисовываете все изображение в каждом вызове paintComponent(). Вместо этого сделайте его один раз, используя TextLayout, увидев здесь и draw() только вновь видимую часть каждый раз.

image

Ответ 3

  • Никогда не используйте getGraphics, и вы НИКОГДА не назовете paintComponent самостоятельно, это не так, как обычная картина выполняется в Swing. Вместо этого обновите состояние и вызовите repaint.
  • Не полагайтесь на магические числа, используйте имеющуюся у вас информацию (getWidth и getHeight)
  • Компоненты Swing дублируются буфером, поэтому вряд ли вам нужно будет создать собственную стратегию буферизации. Этот простой акт может замедлить вашу живопись.
  • Вы должны позвонить super.paintComponent. Это еще более важно с JComponent, поскольку оно не непрозрачно и не может этого сделать, может привести к некоторым неприятным артефактам краски.
  • Вы должны переопределить JComponent#getPreferredSize, чтобы он мог эффективно работать с менеджерами макетов.
  • Вы можете найти более короткую задержку, создающую лучшую иллюзию, например 40 миллисекунд (примерно 25 кадров в секунду), например

Взгляните на анимацию Swing, которая работает очень медленно, которая благодаря некоторому управлению объектами и оптимизации могла увеличиться с 500 анимированных объектов до 4500.

Также посмотрите Выполнение пользовательской живописи и Покраска в AWT и Swing, в частности

Ответ 4

Проблема решена!

Чтобы ответить на мой собственный вопрос: осознав, что любой непрерывный вход (мышь или клавиатура) делает анимацию плавным, я вспомнил, что входы могут быть сгенерированы самой программой, используя объект класса 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);
     }
});

Это kludge, но отлично работает.