1

所以我正在做这个练习,我需要创建一个程序,通过按下四个按钮之一在屏幕上移动一个小球。我已经完成了它,但是后来我想让初始位置位于屏幕的中心,所以我将值 getWidth()/2 分配给 xCoord 并将 getHeight()/2 分配给 yCoord (首先我没有构造函数,然后当它不起作用时,我添加了构造函数并添加了repaint(),因此将调用paintComponent())但是当我启动程序时球仍在左上角。我怎样才能解决这个问题?PS我也会很感激对一般代码的任何评论。谢谢你。

package movingaball;
import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class MovingABall extends JFrame {
    private JButton jbtLeft = new JButton("Left");
    private JButton jbtRight = new JButton("Right");
    private JButton jbtUp = new JButton("Up");
    private JButton jbtDown = new JButton("Down");
    private BallPanel ballPanel = new BallPanel();

    public MovingABall () {
        JPanel buttonPanel = new JPanel();

        buttonPanel.add(jbtLeft);
        buttonPanel.add(jbtRight);
        buttonPanel.add(jbtUp);
        buttonPanel.add(jbtDown);


        this.add(ballPanel);
        this.add(buttonPanel, BorderLayout.SOUTH);
        jbtLeft.addActionListener(new ButtonListener());
        jbtRight.addActionListener(new ButtonListener());
        jbtUp.addActionListener(new ButtonListener());
        jbtDown.addActionListener(new ButtonListener());

    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        MovingABall mainWondow = new MovingABall();
        mainWondow.setTitle("Moving a ball");
        mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWondow.setSize(300, 200);
        mainWondow.setVisible(true);
    }

    class ButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent buttonPressed) {
            if (buttonPressed.getSource() == jbtLeft)
                ballPanel.left();
            else if (buttonPressed.getSource() == jbtRight)
                ballPanel.right();
            else if (buttonPressed.getSource() == jbtUp)
                ballPanel.up();
            else if (buttonPressed.getSource() == jbtDown)
                ballPanel.down();
        }

    }

    class BallPanel extends JPanel {
        private int xCoord = 10;
        private int yCoord = 10;
        public BallPanel() {

            xCoord = getWidth()/2;
            yCoord = getHeight()/2;
            repaint();

        }


        @Override
        public void setBackground(Color bg) {
            super.setBackground(bg); //To change body of generated methods, choose Tools | Templates.
        }
        public void left() {
            xCoord-=5;
            repaint();         
        }

        public void right() {
            xCoord+=5;
            repaint();
        }
        public void up() {
            yCoord-=5;
            repaint();
        }

        public void down() {
            yCoord+=5;
            repaint();
        }



        protected void paintComponent(Graphics aBall) {
            super.paintComponent(aBall);
            System.out.println("X" + getWidth());
            aBall.drawOval(xCoord, yCoord, 10, 10);
        }

    }
}
4

3 回答 3

2

你可以

对事件使用一个ComponentListener和监听器ComponentResized,但是在组件达到它的最终屏幕大小之前,这可能会被多次调用....

你可以

使用AncestorListener监听ancestorAdded,但它遇到与ComponentListener

你可以

使用HierarchyListener监听 for ,但它遇到与andhierarchyChanged相同的问题ComponentListenerAncestorListener

你可以

Override doLayout,但这与ComponentListenerand AncestorListener, HierarchyListener...

那么该怎么办?

我们需要知道组件在第一次显示时最后一次调整大小的时间。从我的测试中我发现doLayout并且hierarchyChangedHierarchyListener​​优秀的候选人。

现在问题出现了,因为我们只想在进入屏幕之前使用它们,然后,我们不在乎......

所以,我们需要做的第一件事就是将x/yCoords 初始化为一些“无效”的值......

private int xCoord = -1;
private int yCoord = -1;

这给了我们一些线索,我们仍然需要“设置”坐标......

接下来,我们需要一些方法来设置一个可中断的回调。某种方式可以在我们选择的“监听器”和我们实际更新坐标的时间之间注入一个短暂的延迟,但如果“监听器”被触发,则可以重置......

javax.swing.Timer是一个很好的选择。我可以在后台等待一段指定的时间,并且可以在需要时重新启动...

    private Timer resizeTimer;

    public BallPanel() {
        resizeTimer = new Timer(125, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Only update the coorinates if they are invalid...
                if (xCoord < 0 && yCoord < 0) {
                    xCoord = getWidth() / 2;
                    yCoord = getHeight() / 2;
                    repaint();
                }
            }
        });
        resizeTimer.setRepeats(false);

最后,当我们选择的“监听器”被触发时,需要“重启”定时器。

为简单起见,我去了doLayout....

@Override
public void doLayout() {
    super.doLayout();
    if (xCoord < 0 && yCoord < 0) {
        resizeTimer.restart();
    }
}

现在,您可能需要解决延迟问题,我发现要慢 250 毫秒,但这只是我 ;)

于 2013-09-26T06:54:35.377 回答
1

请参阅代码中的注释。

居中球

import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class MovingABall extends JFrame {

    private JButton jbtLeft = new JButton("Left");
    private JButton jbtRight = new JButton("Right");
    private JButton jbtUp = new JButton("Up");
    private JButton jbtDown = new JButton("Down");
    private BallPanel ballPanel = new BallPanel();

    public MovingABall () {
        JPanel buttonPanel = new JPanel();

        buttonPanel.add(jbtLeft);
        buttonPanel.add(jbtRight);
        buttonPanel.add(jbtUp);
        buttonPanel.add(jbtDown);

        ballPanel.setBackground(Color.RED);
        this.add(ballPanel);
        this.add(buttonPanel, BorderLayout.SOUTH);
        jbtLeft.addActionListener(new ButtonListener());
        jbtRight.addActionListener(new ButtonListener());
        jbtUp.addActionListener(new ButtonListener());
        jbtDown.addActionListener(new ButtonListener());
    }

    public static void main(String[] args) {
        // Should be called on the EDT!
        MovingABall mainWondow = new MovingABall();
        mainWondow.setTitle("Moving a ball");
        mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Don't pack here.  Instead return a preferred size for the
        // custom comonent end..
        //mainWondow.setSize(300, 200);
        // ..pack() the window.
        mainWondow.pack();
        mainWondow.setVisible(true);
    }

    class ButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent buttonPressed) {
            if (buttonPressed.getSource() == jbtLeft)
                ballPanel.left();
            else if (buttonPressed.getSource() == jbtRight)
                ballPanel.right();
            else if (buttonPressed.getSource() == jbtUp)
                ballPanel.up();
            else if (buttonPressed.getSource() == jbtDown)
                ballPanel.down();
        }
    }

    class BallPanel extends JPanel {
        private int xCoord = -1;
        private int yCoord = -1;
        private Dimension preferredSize = new Dimension(300,200);

/*  Harmful to our logic..
        public BallPanel() {
            xCoord = getWidth()/2;
            yCoord = getHeight()/2;
            repaint();
        }
*/
/*  A good compiler would remove this..
        @Override
        public void setBackground(Color bg) {
            super.setBackground(bg);
        } */

        public void left() {
            xCoord-=5;
            repaint();
        }

        public void right() {
            xCoord+=5;
            repaint();
        }
        public void up() {
            yCoord-=5;
            repaint();
        }

        public void down() {
            yCoord+=5;
            repaint();
        }

        /** Suggest a size to the layout manager. */
        @Override
        public Dimension getPreferredSize() {
            return preferredSize;
        }

        protected void paintComponent(Graphics aBall) {
            super.paintComponent(aBall);
            // This will center the ball if it is the first time painted
            // OR if the x or y co-ord goes off the left/top edge.
            // Further logic left to user..
            if (xCoord<0 || yCoord<0) {
                xCoord = getWidth()/2;
                yCoord = getHeight()/2;
            }
            System.out.println("X" + getWidth());
            aBall.drawOval(xCoord, yCoord, 10, 10);
        }
    }
}
于 2013-09-26T06:55:18.947 回答
0

您在正确的轨道上,但调用 getWidth() 和 getHeight() 方法太快了。由于尚未设置大小,它们仍将返回零:


GetWidth() 和 getHeight() 在构造函数中被调用:

    MovingABall mainWondow = new MovingABall();
    mainWondow.setTitle("Moving a ball");
    mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

但大小在这里设置:

    mainWondow.setSize(300, 200);
    mainWondow.setVisible(true);

那么什么时候行动呢?你可以覆盖setSize()setVisible()在你的MovingABall班级或听componentShown()

Listen for when a Component is Showing for First Time建议使用 HierarchyListener,这可能是最可靠的方法,但在这里有点矫枉过正。

编辑:可能工作的最简单的事情仍然更简单,我刚刚意识到:初始化 BallPanel 不是 10,10 而是 150,100 :)

更严重一点:将维度添加到 MainWindow 和 BallPanel 的构造函数中,而不是在 MainWindow 上调用 setSize,您将可靠地获得一致的初始状态而没有任何麻烦。

于 2013-09-26T06:31:25.703 回答