2012-11-24 131 views
2

我正在用Java制作一个简单的动画,我正在尽可能使它变得流畅。如何让线条动画更流畅?

我只使用每个Shape对象的* .Double内部类,并在Graphics2D对象上设置抗锯齿。只要我只使用fill()方法,它就可以工作,但如果我也使用draw()方法在同一个Shape的周围绘制线条,则这些线条的动画会逐渐变形 - 逐个像素。

画布上的每个矩形都有这种方法来绘制自己。它每20ms移动一次,整个画布使用Timer和TimerListener重新绘制。

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 

public class AnimationTest { 
    public static void main(String[] args) { 
     JFrame frm = new JFrame("Test"); 
     frm.setBounds(200, 200, 400, 400); 
     frm.setResizable(false); 
     frm.setLocationRelativeTo(null); 

     AnimationCanvas a = new AnimationCanvas(); 
     frm.add(a); 

     frm.setVisible(true); 

     a.startAnimation(); 
    } 
} 

class AnimationCanvas extends JPanel { 

    SimpleSquare[] squares = new SimpleSquare[2]; 

    AnimationCanvas() { 

     squares[0] = new SimpleSquare(50, 80, true); 
     squares[1] = new SimpleSquare(160, 80, false); 

    } 

    @Override 
    protected void paintComponent(Graphics g) { 
     super.paintComponent(g); 

     for (SimpleSquare c : squares) { 
      c.paintSquare(g); 
     } 
    } 

    Timer t; 
    public void startAnimation() { 
     t = new Timer(30, new Animator()); 
     t.start(); 
    } 

    private class Animator implements ActionListener { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
      squares[0].y += 0.10; 
      squares[1].y += 0.10; 
      repaint(); 
     } 
    } 
} 


class SimpleSquare { 
    double x; 
    double y; 
    Color color = Color.black; 
    boolean fill; 

    SimpleSquare(double x, double y, boolean fill) { 
     this.x = x; 
     this.y = y; 
     this.fill = fill; 
    } 

    void paintSquare(Graphics g) { 
     ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
       RenderingHints.VALUE_ANTIALIAS_ON); 

     Shape s = new Rectangle.Double(x, y, 100, 100); 

     g.setColor(color); 
     ((Graphics2D) g).setStroke(new BasicStroke(2)); 

     if (fill) { 
      ((Graphics2D) g).fill(s); 
     } else { 
      ((Graphics2D) g).draw(s); 
     } 
    } 
} 

有什么办法解决这个问题吗?我环顾了很长时间。

+0

你启用了双缓冲? – sjr

回答

5

我把这个小测试放在一起,并没有得到任何重要的问题,我基本上能够保持50fps,即使有1000个矩形都以随机方向随机移动。

enter image description here

public class SimpleAnimationEngine { 

    public static void main(String[] args) { 
     new SimpleAnimationEngine(); 
    } 

    public SimpleAnimationEngine() { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       } catch (ClassNotFoundException ex) { 
       } catch (InstantiationException ex) { 
       } catch (IllegalAccessException ex) { 
       } catch (UnsupportedLookAndFeelException ex) { 
       } 

       AnimationPane pane = new AnimationPane(); 

       JFrame frame = new JFrame("Test"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.setLayout(new BorderLayout()); 
       frame.add(pane); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 

       pane.init(); 
       pane.start(); 
      } 

     }); 
    } 

    public static class AnimationPane extends JPanel implements AnimationCanvas { 

     private AnimationModel model; 

     @Override 
     public Dimension getPreferredSize() { 
      return new Dimension(400, 400); 
     } 

     public AnimationModel getModel() { 
      return model; 
     } 

     @Override 
     protected void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      Graphics2D g2d = (Graphics2D) g.create(); 
      for (Animatable animatable : getModel().getAnimatables()) { 
       animatable.paint(g2d); 
      } 
      g2d.dispose(); 
     } 

     @Override 
     public synchronized void updateState() { 

      Runnable update = new Runnable() { 
       @Override 
       public void run() { 
        AnimationModel model = getModel(); 
        for (Animatable animatable : model.getAnimatables()) { 
         animatable.copy(); 
        } 
        repaint(); 
       } 

      }; 

      if (EventQueue.isDispatchThread()) { 
       update.run(); 
      } else { 
       try { 
        EventQueue.invokeAndWait(update); 
       } catch (InterruptedException | InvocationTargetException ex) { 
        ex.printStackTrace(); 
       } 
      } 
     } 

     public void init() { 
      model = new DefaultAnimationModel(); 
      for (int index = 0; index < 1000; index++) { 
       model.add(new AnimatableRectangle(this)); 
      } 
      updateState(); 
     } 

     public void start() { 
      AnimationEngine engine = new AnimationEngine(this, getModel()); 
      engine.start(); 
     } 

    } 

    public static interface Animatable { 

     public void copy(); 

     public void update(AnimationCanvas canvas, float progress); 

     public void paint(Graphics2D g2d); 

    } 

    public static class AnimationEngine extends Thread { 

     private AnimationModel model; 
     private AnimationCanvas canvas; 

     public AnimationEngine(AnimationCanvas canvas, AnimationModel model) { 
      setDaemon(true); 
      setName("AnimationThread"); 
      this.model = model; 
      this.canvas = canvas; 
     } 

     public AnimationCanvas getCanvas() { 
      return canvas; 
     } 

     public AnimationModel getModel() { 
      return model; 
     } 

     @Override 
     public void run() { 
      float progress = 0; 
      long cylceStartTime = System.currentTimeMillis(); 
      long cylceEndTime = cylceStartTime + 1000; 
      int updateCount = 0; 
      while (true) { 
       long frameStartTime = System.currentTimeMillis(); 
       getModel().update(getCanvas(), progress); 
       getCanvas().updateState(); 
       long frameEndTime = System.currentTimeMillis(); 
       long delay = 20 - (frameEndTime - frameStartTime); 
       if (delay > 0) { 
        try { 
         sleep(delay); 
        } catch (InterruptedException ex) { 
        } 
       } 
       long now = System.currentTimeMillis(); 
       long runtime = now - cylceStartTime; 
       progress = (float)runtime/(float)(1000); 
       updateCount++; 
       if (progress > 1.0) { 
        progress = 0f; 
        cylceStartTime = System.currentTimeMillis(); 
        cylceEndTime = cylceStartTime + 1000; 
        System.out.println(updateCount + " updates in this cycle"); 
        updateCount = 0; 
       } 
      } 
     } 

    } 

    public interface AnimationCanvas { 

     public void updateState(); 

     public Rectangle getBounds(); 

    } 

    public static interface AnimationModel { 

     public void update(AnimationCanvas canvas, float progress); 

     public void add(Animatable animatable); 

     public void remove(Animatable animatable); 

     public Animatable[] getAnimatables(); 

    } 

    public static class AnimatableRectangle implements Animatable { 

     private Rectangle bounds; 
     private int dx, dy; 
     private Rectangle copyBounds; 
     private Color foreground; 
     private Color backColor; 

     public AnimatableRectangle(AnimationCanvas canvas) { 
      bounds = new Rectangle(10, 10); 
      Rectangle canvasBounds = canvas.getBounds(); 
      bounds.x = canvasBounds.x + ((canvasBounds.width - bounds.width)/2); 
      bounds.y = canvasBounds.y + ((canvasBounds.height - bounds.height)/2); 

      dx = (getRandomNumber(10) + 1) - 5; 
      dy = (getRandomNumber(10) + 1) - 5; 

      dx = dx == 0 ? 1 : dx; 
      dy = dy == 0 ? 1 : dy; 

      foreground = getRandomColor(); 
      backColor = getRandomColor(); 

     } 

     protected int getRandomNumber(int range) { 
      return (int) Math.round(Math.random() * range); 
     } 

     protected Color getRandomColor() { 
      return new Color(getRandomNumber(255), getRandomNumber(255), getRandomNumber(255)); 
     } 

     @Override 
     public void copy() { 
      copyBounds = new Rectangle(bounds); 
     } 

     @Override 
     public void update(AnimationCanvas canvas, float progress) { 
      bounds.x += dx; 
      bounds.y += dy; 
      Rectangle canvasBounds = canvas.getBounds(); 
      if (bounds.x + bounds.width > canvasBounds.x + canvasBounds.width) { 
       bounds.x = canvasBounds.x + canvasBounds.width - bounds.width; 
       dx *= -1; 
      } 
      if (bounds.y + bounds.height > canvasBounds.y + canvasBounds.height) { 
       bounds.y = canvasBounds.y + canvasBounds.height - bounds.height; 
       dy *= -1; 
      } 
      if (bounds.x < canvasBounds.x) { 
       bounds.x = canvasBounds.x; 
       dx *= -1; 
      } 
      if (bounds.y < canvasBounds.y) { 
       bounds.y = canvasBounds.y; 
       dy *= -1; 
      } 
     } 

     @Override 
     public void paint(Graphics2D g2d) { 
      g2d.setColor(backColor); 
      g2d.fill(copyBounds); 
      g2d.setColor(foreground); 
      g2d.draw(copyBounds); 
     } 

    } 

    public static class DefaultAnimationModel implements AnimationModel { 

     private List<Animatable> animatables; 

     public DefaultAnimationModel() { 
      animatables = new ArrayList<>(25); 
     } 

     @Override 
     public synchronized void update(AnimationCanvas canvas, float progress) { 
      for (Animatable animatable : animatables) { 
       animatable.update(canvas, progress); 
      } 
     } 

     @Override 
     public synchronized void add(Animatable animatable) { 
      animatables.add(animatable); 
     } 

     @Override 
     public synchronized void remove(Animatable animatable) { 
      animatables.remove(animatable); 
     } 

     @Override 
     public synchronized Animatable[] getAnimatables() { 
      return animatables.toArray(new Animatable[animatables.size()]); 
     } 

    } 

} 

UPDATE

你要面对交易的事实,屏幕只能在整数的最大问题...

private class Animator implements ActionListener { 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     squares[0].y += 1; 
     squares[1].y += 1; 
     repaint(); 
    } 
} 

我相信这两个方格实际上都是“抖动”,但是因为这个方格有明显的身体缺陷,它更突出。我以大约24fps的速度运行这个测试,没有任何问题。

+0

嗯,这是一个相当复杂的代码,我的代码要简单得多,而且不太正确。我只是使用一个简单的Timer并每20ms对画布进行重绘()。但是试着制作2个简单的正方形,一个填充并且只绘制一个,然后在你的代码中移动它们慢得多。以约2px/sec的速度移动它们。这样你就会看到AA使填充的正方形看起来模糊不僵,但是画出的正方形会变得尖锐但不连贯。 – user1562293

+0

我会尝试复制您的问题,但这就是为什么一个工作示例如此重要的原因 – MadProgrammer

+0

我编辑了原始问题并提供了一个工作代码。随意对此发表评论。你可以清楚地看到左边的平移运动平稳,右边的平移运动波涛汹涌。 – user1562293

0

您有:

private class Animator implements ActionListener { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
      squares[0].y += 0.10; 
      squares[1].y += 0.10; 
      repaint(); 
     } 
    } 

而且

public void startAnimation() { 
    t = new Timer(30, new Animator()); 
    t.start(); 
} 

但是你可以通过改变

private class Animator implements ActionListener { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
      squares[0].y += 1; 
      squares[1].y += 1; 
      repaint(); 
     } 
    } 

而且

public void startAnimation() { 
    t = new Timer(300, new Animator()); 
    t.start(); 
} 
达到同样的

这样做相同,但循环次数少10次。

我没有测试代码,但它是值得一试。

MadProgrammer提供了正确的解决方案,通过它测量延迟和偏移量。