如何使线条animation更stream畅?
我正在Java中制作一个简单的animation,我正在尽可能地使它变得stream畅。
我只使用每个Shape对象的* .Double内部类,并在Graphics2D对象上设置抗锯齿。 只要我只使用fill()方法,但是如果我也使用draw()方法在同一个Shape的周围绘制线条,这一切都可以工作,这些线条的animation是逐个像素的。
canvas上的每个矩形都具有此方法来绘制自己。 它每隔20ms移动一次,整个canvas使用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); } } }
有没有办法解决这个问题? 我环顾了好一阵子。
我把这个小testing放在一起,并没有得到任何重要的问题,我基本上能够保持50fps,即使有1000个矩形都以随机方向随机移动。
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的速度运行这个testing,没有任何问题。
你有:
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(); }
这是相同的,但使循环less10倍。
我没有testing代码,但值得一试。
MadProgrammer提供了适当的解决scheme,通过它来测量延迟和偏移。