移动黑白球

时间:2014-11-28 16:57:16

标签: java swing

我是Java和OO编程的新手,这里是移动黑白球问题的代码。首先让我在输出中解释我想要的程序:窗口上有n个球(例如6个球),一个黑色和一个白色,每次移动我们只允许移动一个球并且 < em>这个动作应该显示在屏幕上 ,最后所有的白球应该在一侧,所有的黑球应该在另一侧。这是六个球的例子:

image

我已经编写了程序,看起来效果很好而且算法没有任何缺陷,但我的问题是我无法显示球的运动动画,在每个动作中,一个球应该用它交换位置它的邻居球,但我得到的只是球的最终安排。请有人帮我搞动画部分。我真的很感激。

代码:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.*;

public class DrawPanel extends JPanel implements ActionListener
{
Timer myTimer = new Timer(2000, this);
public static final int NUMBER_OF_CIRCLES = 10; //number of circles which are to moved
static int[] circles = new int[NUMBER_OF_CIRCLES];

public void paintComponent(Graphics g)
{
    int x = 0; //start point of circles;
    int length = 40; //diagonal of the circles

    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    Ellipse2D circle;

    //painting n circles based on the array
    for(int index = 0; index<10; index++)
    {
        if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle

            circle = new Ellipse2D.Double(x, 120, length, length);
            g2.draw(circle);
        }
        else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
            circle = new Ellipse2D.Double(x, 120, length, length);
            g2.fill(circle);
        }
        x += 45; //increas start pont of the next circle 45 pixles
    }
    myTimer.start();
}

public void actionPerformed(ActionEvent e)
{
    int tmp; //template for swaping elements
    int condition; //condition of the forS

    arrayFill(circles); //fills the array based on the writen method, one 1 and one 0 like: 0 1 0 1 0 1 0 1

    //here is the part which works good, it changes palces of an elemen at time.
    //at the end of this part the array would be like: 1 1 1 1 0 0 0 0
    if(NUMBER_OF_CIRCLES % 2 == 0)
        condition = circles.length/2 -1;
    else
        condition = circles.length/2;
    for(int i = circles.length-1, k = 1; i>condition; i--, k++)
    {
        for(int j = i - k; j<i ;j++)
        {
            tmp = circles[j];
            circles[j] = circles[j+1];
            circles[j+1] = tmp;
            //if we call arrayPrint method it will print the array but I don't know why repaint is not working here
            //arrayPrint(circles);
            repaint();
        }
    }
}

//fills the array, one 1 and one 0. Example: 0 1 0 1 0 1 0 1 0 1
public static void arrayFill(int[] array)
{
    for(int i = 0; i<array.length; i++)
    {
        if( i%2 == 0)
            array[i] = 0;
        else 
            array[i] = 1;
    }
}

}//end of class

主要班级:

import javax.swing.JFrame;
public class BlackAndWhiteBallsMoving {

public static void main(String[] args)
{
    DrawPanel myPanel = new DrawPanel();
    JFrame myFrame = new JFrame();

    myFrame.add(myPanel);
    myFrame.setSize(600, 500);
    myFrame.setTitle("Black And White Balls Moving");
    myFrame.setVisible(true);
    myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

}//end of class

3 个答案:

答案 0 :(得分:1)

Timer触发的事件在与重绘相同的事件线程上执行。调用repaint不会主动执行绘制事件,而是将其排队等待以后。当您在计时器事件中调用重新绘制时,它们将仅在计时器事件完成后执行。

您需要做的是重构循环,以便每次定时器触发时只执行一次交换。我已经为你做了这个例子:

public class DrawPanel extends JPanel implements ActionListener {
    public static final int NUMBER_OF_CIRCLES = 10;

    Timer myTimer = new Timer(500, this);
    int[] circles = new int[NUMBER_OF_CIRCLES];

    public DrawPanel() {
        arrayFill(circles);

        if(NUMBER_OF_CIRCLES % 2 == 0) {
            condition = circles.length/2 -1;
        } else {
            condition = circles.length/2;
        }

        i = circles.length - 1;
        k = 1;

        myTimer.start();
    }

    int i, j, k;
    int condition;
    boolean outer = true;

    @Override
    public void actionPerformed(ActionEvent e) {
        if(outer) {
            if(i > condition) {
                j = i - k;      // set j
                outer = false;  // and move to the inner loop swap
            } else {
                myTimer.stop(); // the outer loop is done so stop the timer
            }
        }
        if(!outer) {
            int tmp = circles[j];
            circles[j] = circles[j+1];
            circles[j+1] = tmp;
            repaint();

            j++;
            if(j >= i) {
                i--;
                k++;
                outer = true; // move to the outer condition
            }                 // next time the timer triggers
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        int x = 0;
        int length = 40;

        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        Ellipse2D circle;
        for(int index = 0; index<10; index++) {
            if(circles[index] == 0){
                circle = new Ellipse2D.Double(x, 120, length, length);
                g2.draw(circle);
            } else if(circles[index] == 1){
                circle = new Ellipse2D.Double(x, 120, length, length);
                g2.fill(circle);
            }
            x += 45;
        }
        //myTimer.start();
    }

    public static void arrayFill(int[] array) {
        for(int i = 0; i<array.length; i++) {
            if( i%2 == 0) {
                array[i] = 0;
            } else {
                array[i] = 1;
            }
        }
    }
}

(我确定它可以用另一种方式考虑。)

此外:

  • 我添加了您应该使用的@Override注释。当你犯某些错误时,这样做会警告你。 (如拼写错误的方法名称或错误地声明其签名。)
  • 我将circles移动到了一个实例变量,因为我没有看到它应该是静态的原因。它是DrawPanel实例状态的一部分。
  • 我创建了一个构造函数,用于初始化变量,例如circles
  • paintComponent是一种protected方法,除非有理由将其推广到public,否则它应保持不变。

(我删除了你的评论并改变了支撑方式,只是为了压缩我的答案的代码。)

作为旁注,您应该阅读教程Initial Threads。您没有在Swing事件线程上创建GUI。基本上,您需要在main的调用中将代码包装在invokeLater中:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            // create and show your GUI
        }
    });
}

答案 1 :(得分:0)

基本问题在于您的actionPerformed方法。您的两个for循环正在非常快速地将阵列重新排列到其最终排列。每次循环迭代都需要几毫秒到几毫秒才能完成(这取决于repaint()方法的工作方式)。整个过程在不到50毫秒左右的时间内完成。这太快了,你的眼睛跟不上。

基本上,repaint()方法正在运行,但它的工作速度太快,人眼无法跟上。

如果你将for循环分解成每次调用算法的一步,你就可以从计时器触发,并以人类可检测的速度看动画。

答案 2 :(得分:0)

添加绘画线程。它应该总是调用repaint(),

new Thread(){  // this can be started on main or constructor of object
 public void run(){
    while(true){
       repaint();
       try {
         Thread.sleep(50);
        } catch(Exception e){ } 
     }
   }
  }.start();

然后,在执行的操作中,标记移动对象(如movingObjects),保持animate_x = 0并保留一个布尔变量,如existAnimation

然后在paintComponent上增加animate_x

animate_x = animate_x + 1;
if (animate_x >= MAX_WIDTH_OF_ANIMATION){
  existAnimation = false;
}

并使用此existsAnimation,animate_x和movingObjects

public void paintComponent(Graphics g)
{
    int x = 0; //start point of circles;
    int length = 40; //diagonal of the circles

    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    Ellipse2D circle;

    //painting n circles based on the array
    for(int index = 0; index<10; index++)
    {

        int paint_x = x;
        if (movingObjects.has(circles[index])){
            paint_x += animate_x;
        }
        if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle

            circle = new Ellipse2D.Double(paint_x, 120, length, length);
            g2.draw(circle);
        }
        else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
            circle = new Ellipse2D.Double(paint_x, 120, length, length);
            g2.fill(circle);
        }
        x += 45; //increas start pont of the next circle 45 pixles
    }
    myTimer.start();
}