用按钮在屏幕上移动球。 无法编程初始位置。

所以我正在做这个练习,我需要创建一个程序,通过按下四个按钮之一在屏幕上移动一个小球。 我已经完成了,但后来我想让初始位置位于屏幕的中心,所以我将值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); } } } 

请参阅代码中的注释。

中心球

 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); } } } 

你可以

ComponentResized事件使用ComponentListener和listener,但是在组件达到最终屏幕大小之前,可以多次调用它。

你可以

使用AncestorListener监听ancestorAdded ,但它遇到与ComponentListener相同的问题

你可以

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

你可以

覆盖doLayout ,但这会遇到与ComponentListenerAncestorListenerHierarchyListener相同的问题……

那么该怎么办?

我们需要的是知道组件在第一次显示时最后一次resize的时间。 从我的测试中我发现HierarchyListener doLayouthierarchyChanged是很好的候选者。

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

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

 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毫秒减速,但那只是我;)

你是在正确的轨道上,但过早地调用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); 

那么什么时候采取行动? 您可以覆盖MovingABall类中的setSize()setVisible()或侦听componentShown()

在第一次显示组件时监听建议HierarchyListener,这可能是最可靠的方法,但这里有点过分。

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

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