为什么这个 JLabel 有 2 个位置?

Why does this JLabel have 2 positions?

我正在制作这个程序,其中由用户控制的 JLabel 将在 500x500 帧上移动,但由于某种原因它似乎有两个位置,当它移动时它只是在它们之间闪烁。这是什么原因造成的?

This is how it currently looks like

这是主要内容 window:

package guipkg1;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.*;

import guipkg1.PlayerMove;

public class KeyListenerPage extends JFrame implements KeyListener {
    public static JLabel player = new JLabel();
    public static boolean isGoingLeft = false;
    public static boolean isGoingUp = false;
    public static boolean isGoingRight = false;
    public static boolean isGoingDown = false;
    PlayerMove pmove = new PlayerMove();
    KeyListenerPage(){
        this.setDefaultCloseOperation(HIDE_ON_CLOSE);
        this.setSize(500,500);
        this.setLayout(null);
        this.setVisible(false);
        this.addKeyListener(this);
        
        player.setBounds(0,0,50,50);
        player.setText(":)");
        this.add(player);
        
        
        
        pmove.start();
    } 
        
    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void keyPressed(KeyEvent e) {
        // TODO Auto-generated method stub
        if(e.getKeyCode()==37 || e.getKeyCode()==65) {
            isGoingLeft = true;
            System.out.println("[KeyListenerPage] going left");
        } else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
            isGoingUp = true;
            System.out.println("[KeyListenerPage] going up");
        } else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
            isGoingRight = true;
            System.out.println("[KeyListenerPage] going right");
        } else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
            isGoingDown = true;
            System.out.println("[KeyListenerPage] going down");
        }
        
    }
    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub
        if(e.getKeyCode()==37 || e.getKeyCode()==65) {
            isGoingLeft = false;
            System.out.println("[KeyListenerPage] stopped going left");
        } else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
            isGoingUp = false;
            System.out.println("[KeyListenerPage] stopped going up");
        } else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
            isGoingRight = false;
            System.out.println("[KeyListenerPage] stopped going right");
        } else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
            isGoingDown = false;
            System.out.println("[KeyListenerPage] stopped going down");
        }
    }
}

和移动播放器的线程:

package guipkg1;

import javax.swing.JLabel;

public class PlayerMove extends Thread implements Runnable {
    boolean movingLeft = KeyListenerPage.isGoingLeft;
    boolean movingUp = KeyListenerPage.isGoingUp;
    boolean movingRight = KeyListenerPage.isGoingRight;
    boolean movingDown = KeyListenerPage.isGoingDown;
    JLabel player = KeyListenerPage.player;
    boolean canMoveLeft = false;
    boolean canMoveRight = true;
    boolean canMoveUp = false;
    boolean canMoveDown = true;
    public void run() {
        System.out.println("running");
        while(true) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            movingLeft = KeyListenerPage.isGoingLeft;
            movingUp = KeyListenerPage.isGoingUp;
            movingRight = KeyListenerPage.isGoingRight;
            movingDown = KeyListenerPage.isGoingDown;
            if(movingLeft && canMoveLeft) {
                player.setBounds(player.getWidth(), player.getHeight(), player.getX()-10, player.getY());
                player.setVisible(true);
                if(player.getX()<=0) {
                    canMoveLeft = false;
                }
                if(player.getX()<490) {
                    canMoveRight = true;
                }
            } 
            if(movingRight && canMoveRight) {
                player.setBounds(player.getWidth(), player.getHeight(), player.getX()+10, player.getY());
                player.setVisible(true);
                if(player.getX()>0) {
                    canMoveLeft = true;
                }
                if(player.getX()>=490) {
                    canMoveRight = false;
                }
            }
            if(movingUp && canMoveUp) {
                player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()-10);
                player.setVisible(true);
                if(player.getY()<=0) {
                    canMoveUp = false;
                }
                if(player.getY()<490) {
                    canMoveDown = true;
                }
            } 
            if(movingDown && canMoveDown) {
                player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);
                player.setVisible(true);
                if(player.getY()>0) {
                    canMoveUp = true;
                }
                if(player.getY()>=490) {
                    canMoveDown = false;
                }
            } 
            
        }
    }
}

为什么会闪烁?

因为您对 player.setBounds() 的参数顺序错误。

根据 JLabel#setBounds() JavaDoc 参数顺序为 xywidthheight.

但是您的代码调用带有参数的方法

player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);

它应该在哪里

player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());

另请注意 Swing 不是 thread-safe。如果您想更改显示的 Swing 组件上的任何内容,您必须在 Swing EDT 上进行,例如将 PlayerMove.run() 重写为

public void run() {
    System.out.println("running");
    while(true) {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(this::move);
    }
}

并将剩余代码从 PlayerMove.run() 移动到新的 move() 方法中:

private void move() {
    movingLeft = KeyListenerPage.isGoingLeft;
    movingUp = KeyListenerPage.isGoingUp;
    movingRight = KeyListenerPage.isGoingRight;
    movingDown = KeyListenerPage.isGoingDown;
    if(movingLeft && canMoveLeft) {
        player.setBounds(player.getX()-10, player.getY(), player.getWidth(), player.getHeight());
        player.setVisible(true);
        if(player.getX()<=0) {
            canMoveLeft = false;
        }
        if(player.getX()<490) {
            canMoveRight = true;
        }
    }
    if(movingRight && canMoveRight) {
        player.setBounds(player.getX()+10, player.getY(), player.getWidth(), player.getHeight());
        player.setVisible(true);
        if(player.getX()>0) {
            canMoveLeft = true;
        }
        if(player.getX()>=490) {
            canMoveRight = false;
        }
    }
    if(movingUp && canMoveUp) {
        player.setBounds(player.getX(), player.getY()-10, player.getWidth(), player.getHeight());
        player.setVisible(true);
        if(player.getY()<=0) {
            canMoveUp = false;
        }
        if(player.getY()<490) {
            canMoveDown = true;
        }
    }
    if(movingDown && canMoveDown) {
        player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());
        player.setVisible(true);
        if(player.getY()>0) {
            canMoveUp = true;
        }
        if(player.getY()>=490) {
            canMoveDown = false;
        }
    }
}

请注意,这个解决方案仍然适用于单独的线程(即 PlayerMove 仍然扩展 Thread 并且您仍然必须在 KeyListenerPage 中调用 pmove.start();


正如 MadProgrammer 已经评论过的,更好的方法是 use a Swing Timer

这样的解决方案会稍微改变您的代码。

PlayerMoveclass会变成

public class PlayerMove implements ActionListener {
    boolean movingLeft = false;
    boolean movingUp = false;
    boolean movingRight = false;
    boolean movingDown = false;
    JLabel player = KeyListenerPage.player;
    boolean canMoveLeft = false;
    boolean canMoveRight = true;
    boolean canMoveUp = false;
    boolean canMoveDown = true;

    @Override
    public void actionPerformed(ActionEvent e) {
        // here is all the moving code, 
        // i.e. what in your solution was in the run() method
        // but without the looping
        // The Timer will call this every 20ms for you
    }
}

并且在 KeyListenerPage() 构造函数中,您将替换

    pmove.start();

    Timer timer = new Timer(20, pmove);
    timer.start();

您的代码中不再需要“永无止境的循环” - Timer 现在负责每 20 毫秒调用一次 pmove.actionPerformed()

这段代码:

public void run() {
    System.out.println("running");
    while(true) {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(this::move);
    }
}

可以通过将 move() 方法替换为 actionPerformed() 方法(方法中的代码不需要更改!)并将 pmove.start(); 替换为两个来完全删除线条 - 恕我直言,明显改进。