2015-08-05 4 views
0

Я пытаюсь нарисовать два круга на панели с линией, соединяющей их, после нажатия кнопки. До сих пор (помимо настройки местоположения линии) это нормально. Тем не менее, я хотел бы оживить его с помощью таймера. Появится первый круг, затем постепенно появится линия, и, наконец, второй круг.Анимировать рисование линии Java с помощью таймера

Я просмотрел множество примеров таймеров, но я не могу заставить его работать для меня. Должно быть, я что-то недопонимаю.

здесь класс мяч (для каждого круга):

package twoBalls; 

import java.awt.Color; 
import java.awt.Point; 

public class Ball { 

    private int x; 
    private int y; 
    private int r; 
    private Color color; 
    private Point location; 
    private Ball parent; 

    public Ball(int x, int y, int r) { 
     this.x = x; 
     this.y = y; 
     this.r = r; 
     Point p = new Point(x, y); 
     setLocation(p); 
    } 

    public void setParent(Ball b) { 
     parent = b; 
    } 

    public Ball getParent() { 
     return parent; 
    } 

    public void setx(int x) { 
     this.x = x; 
    } 

    public void sety(int y) { 
     this.y = y; 
    } 

    public int getx() { 
     return x; 
    } 

    public int gety() { 
     return y; 
    } 

    public int getr() { 
     return r; 
    } 

    public void setPreferedSize() { 

    } 

    public void setLocation(Point p) { 
     setx(p.x); 
     sety(p.y); 
     location = p; 
    } 

    public Point getLocation() { 
     return location; 
    } 

    public void setColor(Color color) { 
     this.color = color; 
    } 

    public Color getColor() { 
     return color; 
    } 

} 

то класс, который будет хранить шары в ArrayList. И я думаю, что это то, где должен быть фактический рисунок, вместе с таймером. Я пытаюсь установить начальную и конечную точки линии одинаковыми и увеличивать конечную точку до тех пор, пока она не окажется там, где она должна быть, используя таймер. Я, наверное, способ отслеживания, но это было намерение!

Я изменил этот класс, теперь могут быть введены операторы if в цикле while, так как теперь я сравниваю разные точки. Но линия все равно не нарисована.

package twoBalls; 

import java.awt.Color; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.Point; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.geom.Ellipse2D; 
import java.awt.geom.Line2D; 
import java.util.ArrayList; 

import javax.swing.JPanel; 
import javax.swing.Timer; 

public class BallsArray extends JPanel implements ActionListener { 

    private ArrayList<Ball> balls; 
    private Timer timer; 
    private final int DELAY = 25; 
    private int xDest; 
    private int yDest; 
    private Point dest; 
    private Point starts; 
    private int xStart; 
    private int yStart; 

    public BallsArray() { 
     balls = new ArrayList<Ball>(); 
     timer = new Timer(DELAY, this); 
     yDest = 0; 
     xDest = 0; 
     dest = new Point(xDest, yDest); 
     starts = new Point(xStart, yStart); 

    } 

    public void setDestXY(int x, int y) { 
     xDest = x; 
     yDest = y; 
     dest = new Point(xDest, yDest); 
     setDest(dest); 
    } 

    public void setDest(Point p) { 
     dest = p; 

    } 

    public Point getDest() { 
     return dest; 
    } 

    public void setStartsXY(int x, int y) { 
     xStart = x; 
     yStart = y; 
     starts = new Point(xStart, yStart); 
     setStarts(starts); 
    } 

    public void setStarts(Point p) { 
     starts = p; 
    } 

    public Point getStarts() { 
     return starts; 
    } 

    public void addBall(Ball b) { 
     balls.add(b); 
    } 

    public void addBall(int x, int y, int r) { 
     balls.add(new Ball(x, y, r)); 

    } 

    public void paintComponent(Graphics g) { 

     Graphics2D g2 = (Graphics2D) g; 
     for (int i = 0; i < balls.size(); i++) { 
      if (i == 0) { 
       paintBall(balls.get(0), g2); 
      } 
      if (i != 0) { 
       int j = i - 1; 
       Ball bp = balls.get(j); 
       Ball bc = balls.get(i); 
       bc.setParent(bp); 
       paintLine(bc, g2); 
       paintBall(bc, g2); 
      } 

     } 
    } 

    public void paintBall(Ball b, Graphics2D g2d) { 
     Ellipse2D circ = new Ellipse2D.Float(b.getx(), b.gety(), b.getr(), 
       b.getr()); 
     g2d.draw(circ); 
    } 

    public void paintLine(Ball b, Graphics2D g2d) { 
     timer.start(); 
     if (b != null && b.getLocation() != null) { 
      Ball parent = b.getParent(); 
      if (parent != null) { 
       g2d.setColor(Color.GRAY); 
       if (parent.getLocation() != null && b.getLocation() != null) { 
        setDest(parent.getLocation()); 
        setStarts(parent.getLocation()); 
        g2d.draw(new Line2D.Float(starts, dest)); 
       } 
      } 
     } 

    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     // Not sure what I need to do here 
     // increment second location somehow 
     // Point s = getStarts(); 
     Point p = getDest(); 
     Point t = this.getLocation(); 
     while (p != t) { 

      if (p.x != t.x && p.y != t.y) { 
       System.out.println("hello"); 
       int x = dest.x; 
       int y = dest.y; 
       x++; 
       y++; 
       setDestXY(x, y); 
       p = getDest(); 
       repaint(); 
      } else if (p.x == t.x && p.y != t.y) { 
       System.out.println("part 2"); 
       int y = dest.y; 
       y++; 
       setDestXY(dest.x, y); 
       p = getDest(); 
       repaint(); 
      } else if (p.x != t.x && p.y == t.y) { 
       System.out.println("part 3"); 
       int x = dest.x; 
       x++; 
       setDestXY(x, dest.y); 
       p = getDest(); 
       repaint(); 
      } 
      repaint(); 
     } 
    } 
} 

У меня была большая помощь в онлайн-доступе до сих пор, я беспокоюсь, что сейчас я за пределами моей глубины !. Я не уверен в части EventQueue/run ниже. Вот класс, чтобы установить все это:

package twoBalls; 

import java.awt.BorderLayout; 
import java.awt.EventQueue; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.JButton; 
import javax.swing.JFrame; 

public class Display implements ActionListener { 

    private JFrame frame; 
    private JButton button; 
    private BallsArray b; 

    public static void main(String[] args) { 

     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       Display ex = new Display(); 

      } 
     }); 

    } 

    public Display() { 
     b = new BallsArray(); 
     frame = new JFrame(); 
     frame.setSize(800, 500); 
     frame.setTitle("Show balls"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setLayout(new BorderLayout()); 
     button = new JButton("New Ball"); 
     frame.add(button, BorderLayout.SOUTH); 
     frame.setVisible(true); 
     button.addActionListener(this); 

    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 

     Ball ball1 = new Ball(100, 100, 50); 
     b.addBall(ball1); 
     b.addBall(200, 200, 50); 
     frame.add(b, BorderLayout.CENTER); 
     frame.revalidate(); 
     frame.repaint(); 

    } 
} 

На данный момент он рисует два круга, но не линия вообще.

ответ

2

Когда вы делаете анимацию, это помогает использовать model/view/controller pattern.

Вот графический интерфейс, который я создал из вашего кода.

Show Balls Animation

Я упростил свой класс мяча. Это все, что вам нужно, чтобы определить мяч.

package twoBalls; 

import java.awt.Color; 
import java.awt.Point; 

public class Ball { 

    private final int radius; 

    private final Color color; 

    private final Point center; 

    public Ball(int x, int y, int radius, Color color) { 
     this(new Point(x, y), radius, color); 
    } 

    public Ball(Point center, int radius, Color color) { 
     this.center = center; 
     this.radius = radius; 
     this.color = color; 
    } 

    public int getRadius() { 
     return radius; 
    } 

    public Color getColor() { 
     return color; 
    } 

    public Point getCenter() { 
     return center; 
    } 

} 

Я создал класс GUIModel для хранения всей информации, необходимой вашему GUI. Это отделяет модель от вида.

package twoBalls; 

import java.awt.Point; 
import java.util.ArrayList; 
import java.util.List; 

public class GUIModel { 

    private double direction; 
    private double distance; 

    private List<Ball> balls; 

    private Point lineStartPoint; 
    private Point lineEndPoint; 

    public GUIModel() { 
     this.balls = new ArrayList<>(); 
    } 

    public void addBall(Ball ball) { 
     this.balls.add(ball); 
    } 

    public List<Ball> getBalls() { 
     return balls; 
    } 

    public void calculatePoints() { 
     this.lineStartPoint = balls.get(0).getCenter(); 
     this.lineEndPoint = balls.get(1).getCenter(); 

     this.distance = Point.distance(lineStartPoint.x, lineStartPoint.y, 
       lineEndPoint.x, lineEndPoint.y); 
     this.direction = Math.atan2(lineEndPoint.y - lineStartPoint.y, 
       lineEndPoint.x - lineStartPoint.x); 
    } 

    public Point getCurrentPoint(int pos, int total) { 
     double increment = distance/total; 
     double length = increment * pos; 

     double x = lineStartPoint.x + Math.cos(direction) * length; 
     double y = lineStartPoint.y - Math.sin(direction) * length; 

     x = Math.round(x); 
     y = Math.round(y); 

     return new Point((int) x, (int) y); 
    } 

    public Point getLineStartPoint() { 
     return lineStartPoint; 
    } 

} 

Этот класс содержит два экземпляра шара и вычисляет длину и направление линии, разделенные на полные приращения.

Теперь, когда мы определили классы моделей, давайте посмотрим на классы представления. Первый - ваш класс Display.

package twoBalls; 

import java.awt.BorderLayout; 
import java.awt.Color; 
import java.awt.EventQueue; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 

import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 

public class Display implements Runnable { 

    private GUIModel guiModel; 

    private JFrame frame; 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Display()); 
    } 

    public Display() { 
     this.guiModel = new GUIModel(); 
     Ball ball1 = new Ball(150, 200, 50, Color.BLUE); 
     Ball ball2 = new Ball(450, 200, 50, Color.GREEN); 
     guiModel.addBall(ball1); 
     guiModel.addBall(ball2); 
     guiModel.calculatePoints(); 
    } 

    @Override 
    public void run() { 
     frame = new JFrame(); 
     frame.setTitle("Show Balls Animation"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     JPanel panel = new JPanel(); 
     panel.setLayout(new BorderLayout()); 

     DrawingPanel drawingPanel = new DrawingPanel(guiModel); 
     panel.add(drawingPanel, BorderLayout.CENTER); 

     panel.add(createButtonPanel(drawingPanel), BorderLayout.SOUTH); 

     frame.add(panel); 

     frame.pack(); 
     frame.setVisible(true); 
    } 

    private JPanel createButtonPanel(DrawingPanel drawingPanel) { 
     JPanel panel = new JPanel(); 

     JButton startButton = new JButton("Start Animation"); 
     startButton.addActionListener(new StartAnimation(drawingPanel)); 
     panel.add(startButton); 

     return panel; 
    } 

    public class StartAnimation implements ActionListener { 

     private DrawingPanel drawingPanel; 

     public StartAnimation(DrawingPanel drawingPanel) { 
      this.drawingPanel = drawingPanel; 
     } 

     @Override 
     public void actionPerformed(ActionEvent event) { 
      LineRunnable runnable = new LineRunnable(drawingPanel); 
      new Thread(runnable).start(); 
     } 

    } 

} 

Конструктор класса Display устанавливает модель GUI.

Метод запуска класса Display создает GUI и запускает анимацию.

Посмотрите, как я отделил модель и ее вид.

Класс StartAnimation - ваш контроллер. Он запускает анимацию, когда вы нажимаете на JButton. Ниже я расскажу о классе LineRunnable.

Далее, давайте посмотрим на класс DrawingPanel.

package twoBalls; 

import java.awt.BasicStroke; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.Point; 

import javax.swing.JPanel; 

public class DrawingPanel extends JPanel { 

    private static final long serialVersionUID = -3709678584255542338L; 

    private boolean drawLine; 

    private int pos; 
    private int total; 

    private GUIModel guiModel; 

    public DrawingPanel(GUIModel guiModel) { 
     this.guiModel = guiModel; 
     this.drawLine = false; 
     this.setPreferredSize(new Dimension(600, 400)); 
    } 

    public boolean isDrawLine() { 
     return drawLine; 
    } 

    public void setDrawLine(boolean drawLine) { 
     this.drawLine = drawLine; 
    } 

    public void setPos(int pos) { 
     this.pos = pos; 
     repaint(); 
    } 

    public void setTotal(int total) { 
     this.total = total; 
    } 

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

     Graphics2D g2d = (Graphics2D) g; 

     for (Ball ball : guiModel.getBalls()) { 
      g2d.setColor(ball.getColor()); 
      Point center = ball.getCenter(); 
      int radius = ball.getRadius(); 
      g2d.fillOval(center.x - radius, center.y - radius, radius + radius, 
        radius + radius); 
     } 

     if (isDrawLine()) { 
      g2d.setColor(Color.BLACK); 
      g2d.setStroke(new BasicStroke(5.0F)); 
      Point a = guiModel.getLineStartPoint(); 
      Point b = guiModel.getCurrentPoint(pos, total); 
      g2d.drawLine(a.x, a.y, b.x, b.y); 
     } 
    } 

} 

Единственное, что делает этот класс вида, это нарисовать шары и линию. Ответственность за вычисление длины линии принадлежит модели.

Здесь я устанавливаю предпочтительный размер и использую метод pack в классе Display, чтобы получить размер JFrame. Обычно вы хотите знать размеры области рисования, а не всего окна.

Наконец, давайте посмотрим на класс LineRunnable. Это класс, который управляет анимацией.

package twoBalls; 

import java.awt.EventQueue; 

public class LineRunnable implements Runnable { 

    private int total; 

    private DrawingPanel drawingPanel; 

    public LineRunnable(DrawingPanel drawingPanel) { 
     this.drawingPanel = drawingPanel; 
     this.total = 240; 
    } 

    @Override 
    public void run() { 
     setDrawLine(); 
     for (int pos = 0; pos <= total; pos++) { 
      setPos(pos); 
      sleep(50L); 
     } 
    } 

    private void setDrawLine() { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       drawingPanel.setDrawLine(true); 
       drawingPanel.setTotal(total); 
      } 
     }); 
    } 

    private void setPos(final int pos) { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       drawingPanel.setPos(pos); 
      } 
     }); 
    } 

    private void sleep(long delay) { 
     try { 
      Thread.sleep(delay); 
     } catch (InterruptedException e) { 

     } 
    } 

} 

В методе run мы разделим линию на 240 сегментов и нарисуем сегмент каждые 50 миллисекунд. Для рисования линии требуется GUI 12 секунд. Вы можете играть с этими цифрами, если хотите.

Цикл for представляет собой классический цикл анимации. Сначала вы обновляете модель, которую я делаю с помощью панели рисования. Затем вы спите.

Этот цикл анимации работает в другом потоке из потока графического интерфейса. Это обеспечивает гибкость графического интерфейса пользователя. Поскольку цикл работает в другом потоке, мы должны использовать метод invokeLater для рисования потока Thread Dispatch.

Я надеюсь, что это было полезно для вас. Разделите и победите. Не позволяйте классу делать больше, чем одно.

+0

Спасибо! Это очень помогает мне, особенно ваши объяснения. Если бы я хотел сделать так, чтобы шары не рисовались до тех пор, пока не будет нажата кнопка, я бы изменил метод paintComponent в классе DrawingPanel, чтобы рисовать шары были в другом if-заявлении? – Ash

+0

@ Аш: Да. Я бы использовал еще один логический сигнал, чтобы сигнализировать методу paintComponent, когда рисовать шары. –