2012-03-25 161 views
7

Estoy escribiendo una aplicación java que necesita desplazar una forma de onda suavemente sobre la pantalla. Pasé varias eras pasando por varios tutoriales para descubrir cómo hacer que esta animación sea lo más fluida posible. Por lo que puedo ver, he hecho todas las cosas normales para eliminar el parpadeo (dibujar en el búfer y renderizado fuera de pantalla en un solo paso, además de anular la actualización para que la pantalla no esté en blanco), pero mi animación aún parpadea, y la pantalla parece que está siendo borrado antes de cada actualización.Cómo eliminar el parpadeo de animación java

Estoy seguro de que hay algo fundamental (y probablemente simple) que me falta, pero me he quedado sin ideas. Publicaré una clase debajo que ilustra el problema. Cualquier ayuda sería muy apreciada.

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

public class FlickerPanel extends JPanel implements Runnable { 

    private float [] pixelMap = new float[0]; 

    /** Cached graphics objects so we can control our animation to reduce flicker **/ 
    private Image screenBuffer; 
    private Graphics bufferGraphics; 

    public FlickerPanel() { 
     Thread t = new Thread(this); 
     t.start(); 
    } 

    private float addNoise() { 
     return (float)((Math.random()*2)-1); 
    } 

    private synchronized void advance() { 
     if (pixelMap == null || pixelMap.length == 0) return; 
     float [] newPixelMap = new float[pixelMap.length]; 
     for (int i=1;i<pixelMap.length;i++) { 
      newPixelMap[i-1] = pixelMap[i]; 
     } 

     newPixelMap[newPixelMap.length-1] = addNoise();  

     pixelMap = newPixelMap; 
    } 

    public void run() { 
     while (true) { 
      advance(); 
      repaint(); 

      try { 
       Thread.sleep(25); 
      } catch (InterruptedException e) {} 

     } 
    } 

    private int getY (float height) { 
     double proportion = (1-height)/2; 
     return (int)(getHeight()*proportion); 
    } 

    public void paint (Graphics g) { 

     if (screenBuffer == null || screenBuffer.getWidth(this) != getWidth() || screenBuffer.getHeight(this) != getHeight()) { 
      screenBuffer = createImage(getWidth(), getHeight()); 
      bufferGraphics = screenBuffer.getGraphics(); 
     } 

     if (pixelMap == null || getWidth() != pixelMap.length) { 
      pixelMap = new float[getWidth()]; 
     } 

     bufferGraphics.setColor(Color.BLACK); 

     bufferGraphics.fillRect(0, 0, getWidth(), getHeight()); 

     bufferGraphics.setColor(Color.GREEN); 

     int lastX = 0; 
     int lastY = getHeight()/2; 

     for (int x=0;x<pixelMap.length;x++) { 
      int y = getY(pixelMap[x]); 
      bufferGraphics.drawLine(lastX, lastY, x, y); 
      lastX = x; 
      lastY = y; 
     } 

     g.drawImage(screenBuffer, 0, 0, this); 
    } 

    public void update (Graphics g) { 
     paint(g); 
    } 

    public static void main (String [] args) { 
     JFrame frame = new JFrame("Flicker test"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setContentPane(new FlickerPanel()); 
     frame.setSize(500,300); 
     frame.setVisible(true); 
    } 


} 
+0

No puedo encontrar ningún problema con su código. Se ve bastante suave en mi escritorio. PD. Puede hacer esto mucho más eficientemente al tener solo un pixelMap y usarlo como un búfer cíclico, y proteger el acceso a él con un bloque sincronizado. Tal como está creando una nueva estructura, cada actualización debería estar bien. – Adam

+0

+1 para [sscce] (http://sscce.org/). Brilla muy mal en mi plataforma. – trashgod

+0

Después de haber jugado un poco más, parece que parte del problema puede deberse a que la estructura de datos de pixelMap se modificó durante el curso de una pintura, por lo que estoy rompiendo entre diferentes partes de la pantalla. Hacer un clon() de pixelMap al comienzo del método de pintura parece mejorar las cosas, pero el parpadeo todavía está allí, así que no creo que esta sea la respuesta completa. –

Respuesta

6

JPanel se doble buffer por defecto, y "programas Swing debería anular paintComponent() en lugar de anular paint()." - Painting in AWT and Swing: The Paint Methods. Contraste esto example que anula paintComponent() con este example que anula paint().

Adición: El corte se produce al crear y copiar una imagen completa en cada actualización. En su lugar, acumule puntos en GeneralPath y draw() en Shape en cada iteración, como se muestra en here.

+0

He intentado reemplazar a paintComponent y también configurar el panel para que no tenga doble buffer, pero tampoco parece haber ninguna diferencia aquí, el parpadeo aún se produce. –

+0

Este [ejemplo] relacionado (http://stackoverflow.com/a/5048863/230513) no parpadea. Editar: depende de 'javax.swing.Timer' para actualizar en el EDT. – trashgod

+0

He elaborado más arriba. También considere ['nextGaussian()'] (http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#nextGaussian%28%29) para el ruido. – trashgod

7
  1. En un JPanel, anulan paintComponent(Graphics) en lugar de paint(Graphics)
  2. En lugar de llamar Thread.sleep(n) implementar un columpio Timer para la repetición de tareas o una SwingWorker para tareas de larga ejecución. Ver Concurrency in Swing para más detalles.
+1

Sí; aunque el 'sleep()' ocurre en un hilo diferente y 'repaint()' es thread-safe, 'advance()' tendría que estar sincronizado. – trashgod

+0

OK, ese es un buen punto, pero no creo que tenga ningún efecto directo sobre el parpadeo, parece el mismo método que anule. –

+1

@trashgod Decidí eliminar la 1ra oración del punto 2 * "No bloquear el EDT (Hilo de envío de evento) - la GUI se 'congelará' cuando eso ocurra." * Dado que es verdadero, no parece aplicarse aquí . –

Cuestiones relacionadas