2012-03-25 12 views
7

Piszę aplikację java, która musi płynnie przesuwać przebieg na ekranie. Spędziłem całe wieki, przechodząc przez różne samouczki, aby dowiedzieć się, jak uczynić tę animację tak płynną, jak to tylko możliwe. O ile widzę, zrobiłem wszystkie normalne rzeczy, aby wyeliminować migotanie (rysowanie do bufora poza ekranem i renderowanie w jednym kroku, plus nadpisywanie aktualizacji, więc ekran nie jest wygaszony), ale moja animacja nadal migocze, a ekran wygląda na to, że jest on pusty przed każdą aktualizacją.Jak wyeliminować migotanie animacji java

Jestem pewien, że jest coś fundamentalnego (i prawdopodobnie prostego), którego mi brakuje, ale brakuje mi pomysłów. Opublikuję poniżej klasę, która ilustruje problem. Każda pomoc byłaby bardzo cenna.

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

Nie mogę znaleźć żadnego problemu z Twoim kodem. Wygląda całkiem gładko na moim pulpicie. ps. Możesz to zrobić znacznie wydajniej, mając tylko jedną pikselapię i używając jej jako cyklicznego bufora oraz chroniąc dostęp do niej za pomocą zsynchronizowanego bloku. W trakcie tworzenia nowej struktury każda aktualizacja powinna być OK. – Adam

+0

+1 dla [sscce] (http://sscce.org/). Na mojej platformie mieni się bardzo źle. – trashgod

+0

Po zagraniu nieco więcej wygląda na to, że niektóre problemy mogą wynikać z modyfikacji struktury danych pixelMap w trakcie malowania, więc rozdzierałem się między różnymi częściami ekranu. Wykonanie clone() z pixelMap na początku metody malowania wydaje się poprawiać, ale migotanie wciąż istnieje, więc nie sądzę, że to jest cała odpowiedź. –

Odpowiedz

6

JPanel jest podwójnie buforowane domyślnie i "programy Swing powinny przesłonić paintComponent() zamiast przesłanianie paint()." - Painting in AWT and Swing: The Paint Methods. Porównaj ten numer example, który zastępuje paintComponent() tym example, który zastępuje paint().

Dodatek: ścinanie jest spowodowane tworzeniem i kopiowaniem całego obrazu przy każdej aktualizacji. Zamiast tego, zbieraj punkty na GeneralPath i draw() w każdej iteracji, jak pokazano here.

+0

Próbowałem przesłonić paintComponent, a także ustawienie panelu, aby nie być podwójnie buforowane, ale nie wydaje się tu żadnej różnicy, migotanie nadal występuje. –

+0

Ten powiązany [przykład] (http://stackoverflow.com/a/5048863/230513) nie migocze. Edycja: polega na "javax.swing.Timer", aby zaktualizować na EDT. – trashgod

+0

Wymyśliłem powyżej. Weź również pod uwagę ['nextGaussian()'] (http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#nextGaussian%28%29) na hałas. – trashgod

7
  1. W JPanel, zastępują paintComponent(Graphics) zamiast paint(Graphics)
  2. Zamiast wywoływać Thread.sleep(n) wdrożyć Swing Timer za powtarzanie zadań lub SwingWorker długich uruchomionych zadań. Aby uzyskać więcej informacji, patrz Concurrency in Swing.
+1

Tak; nawet jeśli 'sleep()' występuje w innym wątku, a 'repaint()' jest wątkowo bezpieczny, 'advance()' musiałby być zsynchronizowany. – trashgod

+0

OK, to dobra uwaga, ale nie sądzę, że ma to bezpośredni wpływ na migotanie - wygląda na to samo, niezależnie od tego, którą metodę przesłonię. –

+1

@trashgod Zdecydowałem się usunąć pierwsze zdanie punktu 2 * "Nie blokuj EDT (wątku wysyłki zdarzeń) - GUI" zamrozi ", kiedy to się stanie." * Odtąd, choć prawda, nie ma tu zastosowania . –

Powiązane problemy